Codebeez

Kubernetes-beveiliging

Introductie

Als je een Python-applicatie ontwikkelt met het FastAPI-framework, wordt die hoogstwaarschijnlijk uitgevoerd als onderdeel van een microservices-architectuur op Kubernetes, of als losstaande applicatie binnen een Docker-container. De meeste mensen die niet fulltime met Docker en Kubernetes bezig zijn, gaan ervan uit dat het, vanwege zijn wortels in procesisolatie, standaard veilig is. In werkelijkheid is dat helaas niet het geval. Er zijn veel mogelijke scenario’s waarin de juiste configuratie over het hoofd is gezien en die kunnen leiden tot ernstige beveiligingsproblemen. Stel je bijvoorbeeld een scenario voor waarin je je FastAPI-applicatie hebt gebouwd en uitgerold binnen een Docker-container, in de veronderstelling dat die veilig is geïsoleerd van het hostsysteem. Maar als die niet correct is geconfigureerd, kan deze container mogelijk worden gebruikt om het hostsysteem aan te vallen, door misbruik te maken van de manier waarop Docker user-ID’s (UID’s) toewijst en het hostbestandssysteem mount.

Docker en UID-mapping begrijpen

Docker is een krachtige tool voor het creëren van geïsoleerde omgevingen, bekend als containers, die applicaties onafhankelijk van het hostsysteem kunnen draaien. Een van de belangrijkste functies die Docker gebruikt om isolatie te bieden, zijn user namespaces, die het mogelijk maken om user-ID’s (UID’s) en group-ID’s (GID’s) binnen een container te hermappen naar andere UID’s en GID’s op het hostsysteem. Deze functie is cruciaal voor de beveiliging, omdat het helpt de rechten te beperken die processen die binnen de container draaien op het hostsysteem hebben.

In een typische Linux-omgeving draait elk proces met een specifieke UID, die de rechten ervan op het systeem bepaalt. Wanneer een Docker-container wordt gedraaid, draaien de processen binnen de container standaard met dezelfde UID en GID als gespecificeerd in de Dockerfile of door de gebruiker. Als dit niet goed wordt beheerd, zou dit ertoe kunnen leiden dat de root-gebruiker van de container (UID 0) root-toegang heeft tot het hostsysteem, wat vanuit beveiligingsoogpunt buitengewoon gevaarlijk is.

Om dat te beperken, biedt Docker een mechanisme dat bekendstaat als user namespace remapping (userns-remap), waarmee je UID’s en GID’s binnen de container kunt mappen naar andere UID’s en GID’s op het hostsysteem. Zo zou de root-gebruiker binnen de container (UID 0) gemapt kunnen worden naar een niet-geprivilegieerde gebruiker op het hostsysteem (bijv. UID 1000), waardoor het risico op privilege-escalatie effectief wordt verkleind.

Mogelijke beveiligingsrisico’s bij onjuiste UID-mapping

Hoewel UID-remapping een beveiligingslaag biedt, kan een onjuiste configuratie of onwetendheid over hoe Docker met UID’s omgaat leiden tot aanzienlijke beveiligingskwetsbaarheden. Een van de cruciale aspecten om te begrijpen is hoe UID- en GID-mapping samenspelen met bestandssysteemrechten, met name wanneer je het hostbestandssysteem in een container mount.

Wanneer je een directory van de host in een container mount met de optie -v of --mount, behouden de bestanden en directories binnen het gemounte volume hun oorspronkelijke UID’s en GID’s van het hostsysteem. Als de container draait met een gebruiker die overeenkomende UID’s of GID’s heeft, kan het gecontaineriseerde proces mogelijk toegang krijgen tot gevoelige bestanden op de host.

Stel je bijvoorbeeld een situatie voor waarin je de directory /etc van de host in de container mount. Als de container met root-rechten draait en zonder juiste UID-remapping, zou die kritieke systeembestanden zoals /etc/passwd of /etc/shadow kunnen wijzigen, wat leidt tot een volledige compromittering van het hostsysteem.

Aanvalsscenario: misbruik van UID-mapping en het mounten van het hostbestandssysteem

Om de risico’s te illustreren, lopen we een hypothetisch aanvalsscenario door:

  1. Opzet van het scenario: Je draait een FastAPI-applicatie in een Docker-container. Om de applicatie configuratiebestanden van het hostsysteem te laten lezen, mount je een directory van de host (bijv. /host/config) in de /app/config-directory van de container.

  2. Zwakte: De Docker-container is geconfigureerd om als root-gebruiker (UID 0) binnen de container te draaien, en er is geen UID-remapping ingeschakeld. Daarnaast is de /host/config-directory op de host eigendom van een gebruiker met UID 1000, wat overeenkomt met een niet-geprivilegieerde gebruiker op de host.

  3. Uitvoering van de aanval: Een aanvaller krijgt toegang tot de container, hetzij door een kwetsbaarheid in de FastAPI-applicatie uit te buiten, hetzij op een andere manier (bijv. door een verkeerd geconfigureerde SSH-service die binnen de container draait uit te buiten). Eenmaal binnen de container realiseert de aanvaller zich dat hij als root-gebruiker binnen de container draait en valt de gemounte /app/config-directory hem op.

  4. Manipulatie van het hostbestandssysteem: De aanvaller merkt op dat de /app/config-directory beschrijfbaar is en gaat over tot het aanmaken of wijzigen van bestanden binnen deze directory. Omdat de root-gebruiker van de container overeenkomt met de root-gebruiker op het hostsysteem (door het ontbreken van UID-remapping), heeft de aanvaller nu de mogelijkheid om bestanden op het hostsysteem als root te wijzigen.

  5. Privilege-escalatie: De aanvaller zou nu bestanden kunnen aanmaken of wijzigen zoals .bashrc, .ssh/authorized_keys, of zelfs kritieke systeembestanden zoals /etc/passwd op de host. Hiermee zou hij privileges kunnen escaleren, nieuwe root-gebruikers kunnen aanmaken, of willekeurige commando’s met root-rechten op het hostsysteem kunnen uitvoeren.

De risico’s beperken

Om dergelijke aanvallen te voorkomen, is het cruciaal om best practices te hanteren bij het omgaan met UID-mapping en het mounten van bestandssystemen in Docker:

  1. Schakel User Namespace Remapping in:

Door user namespace remapping (userns-remap) in te schakelen, kun je ervoor zorgen dat de root-gebruiker binnen de container gemapt wordt naar een niet-geprivilegieerde gebruiker op het hostsysteem. Dit verkleint het risico dat de root-gebruiker van de container het hostsysteem beïnvloedt.

Je kunt user namespace remapping inschakelen door de volgende configuratie toe te voegen aan Docker’s daemon.json-bestand:

{ "userns-remap": "default" }

Dit hermapt de root-gebruiker van de container naar een niet-geprivilegieerde gebruiker op de host, waardoor het risico op compromittering van de host effectief wordt verkleind.

  1. Draai containers als non-root-gebruikers:

In plaats van je applicaties als root binnen de container te draaien, configureer je ze om als non-root-gebruikers te draaien. Dit kun je doen door de USER-directive op te geven in je Dockerfile:

FROM python:3.8-slim
RUN adduser --disabled-password --gecos '' appuser
USER appuser

Dit zorgt ervoor dat, zelfs als een aanvaller toegang krijgt tot de container, hij beperkte rechten heeft en niet eenvoudig privileges kan escaleren.

  1. Vermijd het mounten van gevoelige directories:

Wees voorzichtig met het mounten van host-directories in containers, met name gevoelige directories zoals /etc, /var of /root. Mount alleen de directories die absoluut noodzakelijk zijn voor het functioneren van de applicatie.

Als je een directory moet mounten, overweeg dan om die als read-only te mounten om wijzigingen vanuit de container te voorkomen:

docker run -v /host/config:/app/config:ro myapp
  1. Gebruik Docker volumes in plaats van bind mounts:

Gebruik waar mogelijk Docker volumes in plaats van bind mounts. Docker volumes worden door Docker beheerd en zijn geïsoleerd van het hostbestandssysteem, waardoor het risico op onbedoelde wijzigingen aan de host wordt verkleind.

Volumes zijn makkelijker te beheren en bieden een betere isolatie dan bind mounts.

  1. Implementeer juiste toegangscontroles:

Gebruik Docker’s beveiligingsfuncties zoals AppArmor, SELinux en seccomp om strikte toegangscontroles binnen de container af te dwingen. Deze tools kunnen beperken wat de gecontaineriseerde processen mogen doen, waardoor het aanvalsoppervlak verder wordt verkleind.

Je kunt bijvoorbeeld een seccomp-profiel toepassen op je container dat bepaalde system calls beperkt:

docker run --security-opt seccomp=default.json myapp
  1. Update en patch Docker en containers regelmatig:

Zorg ervoor dat zowel Docker als de images die je gebruikt regelmatig worden bijgewerkt naar de nieuwste versies. Updates bevatten vaak beveiligingspatches die kwetsbaarheden verhelpen die uitgebuit zouden kunnen worden.

  1. Monitor en audit de activiteit van containers:

Implementeer logging en monitoring om de activiteiten van containers bij te houden. Tools zoals de ingebouwde logging drivers van Docker, evenals oplossingen van derden zoals Fluentd of de ELK Stack, kunnen je helpen containerlogs te monitoren en verdachte activiteiten te detecteren.

Regelmatige audits van containerconfiguraties en toegangslogs kunnen je helpen om potentiële beveiligingsincidenten tijdig te identificeren en erop te reageren.

Docker biedt een krachtige en flexibele omgeving voor het draaien van applicaties zoals FastAPI in geïsoleerde containers. De aanname dat Docker-containers standaard veilig zijn, kan echter leiden tot ernstige beveiligingskwetsbaarheden, met name als het gaat om UID-mapping en bestandssysteem-mounts.

Begrijpen hoe Docker UID’s en GID’s mapt tussen de container en de host is cruciaal voor het onderhouden van een veilige omgeving. Een onjuiste configuratie, zoals het draaien van containers als root of het niet inschakelen van user namespace remapping, kan leiden tot scenario’s waarin een gecompromitteerde container privileges kan escaleren en het hostsysteem kan aanvallen.

Door de hierboven genoemde best practices te volgen, zoals het inschakelen van user namespace remapping, het draaien van containers als non-root-gebruikers, het zorgvuldig beheren van bestandssysteem-mounts, het benutten van Docker’s eigen beveiligingsfuncties en het up-to-date en goed gemonitord houden van je omgeving, kun je het risico op dergelijke aanvallen verkleinen.

Verborgen in lagen

Een ander probleem dat de meeste ontwikkelaars over het hoofd zien, is dat de informatie die wordt gebruikt bij het bouwen van een Docker-image niet noodzakelijkerwijs uit de image wordt verwijderd, zelfs niet nadat het buildproces is voltooid. Als Python-ontwikkelaar die met FastAPI werkt, zou je kunnen aannemen dat de tussenliggende bestanden, secrets of gevoelige data die tijdens het buildproces van de image zijn gebruikt, niet in de uiteindelijke image aanwezig zijn als ze in de latere stappen van je Dockerfile worden verwijderd. Door de gelaagde aard van Docker-images kan deze informatie echter nog steeds toegankelijk zijn, wat kan leiden tot beveiligingskwetsbaarheden of onbedoelde blootstelling van gevoelige data.

Het voortbestaan van data binnen Docker image-layers begrijpen

Docker-images worden in lagen opgebouwd, waarbij elke laag de staat van het bestandssysteem vertegenwoordigt nadat een bepaald commando in de Dockerfile is uitgevoerd. Deze lagen zijn onveranderlijk en worden opgeslagen als een reeks wijzigingen ten opzichte van de vorige laag. Wanneer je een image bouwt, cachet Docker deze lagen om het buildproces te optimaliseren. Dit cachemechanisme betekent echter ook dat alle bestanden die in een laag worden aangemaakt, in die laag blijven bestaan, zelfs als ze in een volgende laag worden verwijderd.

Als je bijvoorbeeld gevoelige configuratiebestanden in je Docker-image zou kopiëren, dependencies zou installeren en die bestanden vervolgens in dezelfde Dockerfile zou verwijderen, zouden de bestanden nog steeds bestaan in de laag waarin ze oorspronkelijk werden gekopieerd. Dit kan een ernstig beveiligingsrisico zijn, vooral als die lagen voor anderen toegankelijk zijn, of als de image wordt gedistribueerd via publieke of private registries.

Voorbeeld: informatie extraheren uit Docker image-layers

Stel je voor dat je een Python-ontwikkelaar bent die aan een FastAPI-applicatie werkt en een Docker-image bouwt die per ongeluk gevoelige data in zijn lagen behoudt. We lopen het proces door van het bouwen van de image, het identificeren van het probleem en het extraheren van de behouden informatie.

Stap 1: De Docker-image bouwen

Stel dat je een FastAPI-applicatie hebt die is verpakt in de Docker-image, gebouwd vanuit de volgende Dockerfile:

FROM python:3.9-slim

# Step 1: Copy sensitive files
COPY config/secrets.env /app/secrets.env

# Step 2: Install dependencies
RUN pip install -r requirements.txt

# Step 3: Delete sensitive files
RUN rm /app/secrets.env

# Step 4: Copy application code
COPY . /app

# Step 5: Set the working directory
WORKDIR /app

# Step 6: Start the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]

In deze Dockerfile wordt een secrets.env-bestand in stap 1 gekopieerd naar de /app-directory. Dit bestand kan gevoelige environment-variabelen of API-sleutels bevatten. In stap 3 wordt het bestand verwijderd om te voorkomen dat het in de uiteindelijke image wordt blootgesteld. Door de manier waarop Docker-layers werken, is dit bestand echter nog steeds aanwezig in de laag die door stap 1 is aangemaakt, ook al werd het in stap 3 verwijderd.

Stap 2: De lagen inspecteren met docker history

Om te bevestigen dat de gevoelige informatie nog steeds aanwezig is, kun je de lagen van de gebouwde image inspecteren met het commando docker history:

docker history myfastapiapp:latest

Dit commando toont een lijst van alle lagen in de image, samen met de commando’s die ze hebben aangemaakt. Elke laag komt overeen met een specifieke instructie in de Dockerfile.

IMAGE          CREATED        CREATED BY                                      SIZE      COMMENT
<image_id>     2 minutes ago  /bin/sh -c #(nop)  CMD ["uvicorn" "main:app...  0B
<image_id>     2 minutes ago  /bin/sh -c #(nop)  WORKDIR /app                 0B
<image_id>     2 minutes ago  /bin/sh -c #(nop) COPY dir:c0fdcfe94dd23dbf...  10kB
<image_id>     2 minutes ago  /bin/sh -c rm /app/secrets.env                  0B
<image_id>     3 minutes ago  /bin/sh -c pip install -r requirements.txt      20MB
<image_id>     5 minutes ago  /bin/sh -c #(nop)  COPY file:acb12345678901...  5kB
<image_id>     5 minutes ago  /bin/sh -c #(nop)  ENV DEBIAN_FRONTEND=non...   0B
<image_id>     5 minutes ago  /bin/sh -c #(nop)  ENV PYTHON_VERSION=3.9....   0B

In deze output zie je de COPY- en rm-commando’s uit de Dockerfile, maar het COPY-commando in stap 1 heeft een laag aangemaakt die het secrets.env-bestand bevat. Deze laag maakt nog steeds deel uit van de image, ook al werd het bestand in een volgende laag verwijderd.

Stap 3: De gevoelige informatie extraheren

Om de inhoud van de Docker image-layers te extraheren, kun je het commando docker save gebruiken, dat een image als tarball opslaat, en vervolgens handmatig de bestanden binnen de lagen inspecteren.

docker save -o myfastapiapp.tar myfastapiapp:latest

Nadat je de image als tarball hebt opgeslagen, kun je die uitpakken:

mkdir extracted_image
tar -xf myfastapiapp.tar -C extracted_image

Binnen de uitgepakte directory vind je een reeks mappen die overeenkomen met de lagen van de image. Deze mappen bevatten de bestanden en wijzigingen die door elke Dockerfile-instructie zijn geïntroduceerd. Je kunt door deze directories navigeren en zoeken naar het secrets.env-bestand:

find extracted_image -name "secrets.env"

Je kunt zien dat het bestand er is, wat bevestigt dat de gevoelige informatie nog steeds aanwezig is in de image-layer, ook al werd die in de uiteindelijke staat van de image verwijderd.

Best practices om informatielekkage uit de tussenliggende lagen te voorkomen

Gezien de potentiële risico’s van het behouden van gevoelige informatie in Docker image-layers, is het cruciaal dat we de volgende best practices hanteren om dergelijke problemen te voorkomen.

1. Gebruik multi-stage builds

Een van de meest effectieve manieren om informatielekkage te voorkomen, is het gebruik van multi-stage builds. In een multi-stage build kun je gevoelige bestanden of build-dependencies in één stage kopiëren en vervolgens alleen de noodzakelijke artefacten naar de uiteindelijke image kopiëren. Dit zorgt ervoor dat de tussenliggende bestanden niet in de uiteindelijke image worden opgenomen.

Hier is een voorbeeld van hoe je de vorige Dockerfile kunt aanpassen om een multi-stage build te gebruiken:

# Stage 1: Build dependencies
FROM python:3.9-slim AS builder

# Copy and install dependencies
COPY requirements.txt /app/requirements.txt
RUN pip install --no-cache-dir -r /app/requirements.txt

# Stage 2: Final image
FROM python:3.9-slim

# Copy dependencies from the builder stage
COPY --from=builder /usr/local/lib/python3.9/site-packages /usr/local/lib/python3.9/site-packages

# Copy application code
COPY . /app

# Set the working directory
WORKDIR /app

# Start the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]

In deze Dockerfile worden de dependencies geïnstalleerd in de builder-stage, en worden alleen de geïnstalleerde packages naar de uiteindelijke image gekopieerd. De uiteindelijke image bevat geen van de tussenliggende bestanden, zoals het secrets.env-bestand, waardoor gevoelige informatie niet per ongeluk wordt blootgesteld.

2. Beperk het gebruik van secrets in Dockerfiles

Een andere belangrijke praktijk is het beperken van het gebruik van secrets in Dockerfiles. Vermijd waar mogelijk het rechtstreeks kopiëren van gevoelige bestanden in de image. Overweeg in plaats daarvan om environment-variabelen, Docker secrets of externe configuratiemanagement-tools zoals HashiCorp Vault te gebruiken om gevoelige informatie te beheren.

In plaats van bijvoorbeeld een secrets.env-bestand te kopiëren, zou je environment-variabelen kunnen gebruiken die tijdens runtime worden geïnjecteerd:

FROM python:3.9-slim

# Install dependencies
RUN pip install -r requirements.txt

# Copy application code
COPY . /app

# Set the working directory
WORKDIR /app

# Set environment variables
ENV SECRET_KEY=${SECRET_KEY}

# Start the application
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "80"]

In dit scenario wordt de SECRET_KEY-environment-variabele tijdens runtime aan de container doorgegeven, waardoor het risico op het opnemen van gevoelige informatie in de image-layers wordt verkleind.

3. Scan images regelmatig op kwetsbaarheden

Het is ook essentieel om je Docker-images regelmatig te scannen op kwetsbaarheden, waaronder de aanwezigheid van gevoelige bestanden of credentials. Tools zoals het ingebouwde commando docker scan van Docker, evenals tools van derden zoals Trivy, kunnen je helpen potentiële beveiligingsproblemen in je images te identificeren en te verhelpen.

docker scan myfastapiapp:latest

Dit commando scant de image op bekende kwetsbaarheden en levert een rapport op. Het is een goede praktijk om dergelijke scans op te nemen als onderdeel van je CI/CD-pipeline, om problemen op te sporen voordat ze in productie terechtkomen.

4. Ruim op na elke laag

Hoewel het niet altijd waterdicht is, kan het direct opruimen van gevoelige bestanden nadat ze binnen dezelfde Dockerfile-instructie zijn gebruikt het risico verkleinen dat er gevoelige data in de image-layers achterblijft. Houd er echter rekening mee dat deze methode niet zo effectief is als het gebruik van multi-stage builds, omdat er nog steeds ruimte voor fouten overblijft.

FROM python:3.9-slim

# Copy and install dependencies, then clean up
COPY requirements.txt /app/requirements.txt
RUN pip install -r /app/requirements.txt && rm /app

RBAC

Het volgende onderwerp dat vaak over het hoofd wordt gezien, is Role-Based Access Control (RBAC) die niet correct is geconfigureerd voor je Python-applicatie. Stel je dit scenario voor: je hebt een FastAPI-applicatie ontwikkeld die verschillende gebruikersrollen afhandelt, zoals administrators, reguliere gebruikers en gastaccounts. Je applicatie is uitgerold op een Kubernetes-cluster, en je gaat ervan uit dat je RBAC-configuratie ervoor zorgt dat elke gebruiker of service de juiste rechten heeft. Maar wat als een kleine vergissing in je RBAC-instellingen een reguliere gebruiker toegang zou geven tot functies op administratorniveau? Of wat als een verkeerd geconfigureerde Kubernetes RBAC-policy per ongeluk brede toegang tot gevoelige resources verleent? Zulke fouten kunnen leiden tot ernstige beveiligingsinbreuken, datalekken of zelfs een volledige compromittering van het systeem.

RBAC in applicatie en infrastructuur begrijpen

Role-Based Access Control (RBAC) is een cruciale beveiligingsfunctie waarmee de administrator kan bepalen wie toegang heeft tot specifieke resources in de applicatie en infrastructuur. Door rollen te definiëren en rechten aan individuele rollen toe te wijzen, zorg je ervoor dat alleen geautoriseerde gebruikers bepaalde acties kunnen uitvoeren. RBAC kan op meerdere niveaus worden toegepast: binnen de applicatie, op databaseniveau en in je hele Kubernetes-cluster.

In de context van een FastAPI-applicatie wordt RBAC doorgaans gebruikt om gebruikerstoegang tot verschillende API-endpoints te beheren. Een administrator zou bijvoorbeeld volledige toegang tot alle endpoints kunnen hebben, terwijl een reguliere gebruiker alleen toegang heeft tot zijn eigen data. In Kubernetes wordt RBAC gebruikt om de toegang tot de resources van het cluster te beheren, zoals pods, services en secrets.

De risico’s van onjuist geconfigureerde RBAC

Een onjuiste RBAC-configuratie kan tot diverse beveiligingsproblemen leiden, waaronder:

  1. Privilege-escalatie: Een gebruiker zou meer rechten kunnen krijgen dan bedoeld, waardoor hij acties kan uitvoeren die beperkt zouden moeten zijn. Een reguliere gebruiker zou bijvoorbeeld toegang kunnen krijgen tot administratieve endpoints.
  2. Ongeautoriseerde toegang: Gebruikers of services zouden toegang kunnen krijgen tot resources waartoe ze geen toegang zouden mogen hebben. Een service account zou bijvoorbeeld toegang kunnen krijgen tot secrets of configmaps die gevoelige data bevatten.
  3. Datalekkage: Verkeerd geconfigureerde RBAC kan ertoe leiden dat ongeautoriseerde gebruikers gevoelige data bekijken of wijzigen, wat tot datalekken leidt.
  4. Compliance-overtredingen: Veel branches vereisen strikte controle over de toegang tot data om te voldoen aan regelgeving zoals de AVG of HIPAA. Onjuiste RBAC zou kunnen leiden tot non-compliance en juridische sancties.

Voorbeeld: verkeerd geconfigureerde RBAC in een FastAPI-applicatie

Laten we een voorbeeld bekijken waarin RBAC onjuist is geconfigureerd in een FastAPI-applicatie, wat tot potentiële beveiligingsproblemen leidt.

Stap 1: Rollen en rechten definiëren

Stel je voor dat je een FastAPI-applicatie hebt ontwikkeld die API-endpoints biedt voor het beheren van een systeem van records. Je hebt drie rollen gedefinieerd: admin, manager en employee.

  • admin: Heeft volledige toegang tot alle records en kan elke bewerking uitvoeren, waaronder het aanmaken, lezen, bijwerken en verwijderen van records.
  • manager: Kan records van medewerkers binnen hun afdeling bekijken en bijwerken, maar kan geen records verwijderen of systeembrede instellingen benaderen.
  • employee: Kan alleen zijn eigen records bekijken en kan deze niet wijzigen.

De RBAC-configuratie zou als volgt geïmplementeerd kunnen worden:

roles_permissions = {
    "admin": ["read_all", "write_all", "delete_all", "manage_settings"],
    "manager": ["read_department", "write_department"],
    "employee": ["read_own"]
}

Stap 2: RBAC implementeren in FastAPI

Je implementeert een dependency in FastAPI om rollen en rechten te controleren:

from fastapi import FastAPI, Depends, HTTPException, Security
from fastapi.security import OAuth2PasswordBearer

app = FastAPI()

oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")

def get_current_user(token: str = Depends(oauth2_scheme)):
    # Decode the JWT token and return the user object
    user = decode_token(token)
    return user

def check_permissions(required_permissions: list):
    def permission_checker(current_user: dict = Depends(get_current_user)):
        user_permissions = roles_permissions.get(current_user["role"], [])
        if not any(permission in user_permissions for permission in required_permissions):
            raise HTTPException(status_code=403, detail="Forbidden")
        return current_user
    return permission_checker

@app.get("/records", dependencies=[Security(check_permissions(["read_all"]))])
def read_records():
    return {"message": "Reading all records"}

@app.post("/records", dependencies=[Security(check_permissions(["write_all"]))])
def write_record():
    return {"message": "Writing a record"}

@app.delete("/records", dependencies=[Security(check_permissions(["delete_all"]))])
def delete_record():
    return {"message": "Deleting a record"}

Stap 3: Misconfiguratie die tot beveiligingsproblemen leidt

Laten we nu verkennen wat er mis zou kunnen gaan als RBAC verkeerd is geconfigureerd. Stel dat je tijdens de uitrol van je FastAPI-applicatie per ongeluk de rol manager de rechten delete_all toewijst, in de veronderstelling dat dit noodzakelijk was voor hun rol. Deze misconfiguratie betekent dat elke gebruiker met de rol manager nu de mogelijkheid heeft om records in het hele systeem te verwijderen, niet alleen binnen hun afdeling.

Daarnaast zou je, als je de rol employee per ongeluk configureert met de rechten read_all in plaats van read_own, medewerkers in staat stellen om records van andere medewerkers te bekijken, wat leidt tot een privacy-inbreuk.

Kubernetes RBAC en het belang ervan begrijpen

Bij het uitrollen van de FastAPI-applicatie in een Kubernetes-omgeving wordt RBAC nog belangrijker, omdat Kubernetes RBAC gebruikt om de toegang tot de API-server te beheren, die het hele cluster bestuurt. Kubernetes RBAC-policy’s bepalen welke gebruikers of service accounts acties kunnen uitvoeren zoals het aanmaken of wijzigen van pods, het benaderen van secrets, of het beheren van network policies.

In Kubernetes wordt RBAC geconfigureerd met behulp van roles, role bindings, cluster roles en cluster role bindings:

  • Role: Definieert rechten voor resources binnen een specifieke namespace.
  • ClusterRole: Definieert rechten voor resources in het hele cluster.
  • RoleBinding: Koppelt een role aan een gebruiker of groep binnen een specifieke namespace.
  • ClusterRoleBinding: Koppelt een cluster role aan een gebruiker of groep in het hele cluster.

Voorbeeld van verkeerd geconfigureerde Kubernetes RBAC

Laten we een Kubernetes RBAC-configuratie bekijken waarin een service account die door de FastAPI-applicatie wordt gebruikt buitensporige rechten krijgt.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: fastapi-admin
rules:
  - apiGroups: [""]
    resources: ["pods", "services", "secrets", "configmaps"]
    verbs: ["get", "list", "watch", "create", "update", "delete"]

In dit voorbeeld krijgt de fastapi-admin ClusterRole volledige toegang tot kritieke resources zoals pods, services, secrets en configmaps in het hele cluster. Als je FastAPI-applicatie wordt gecompromitteerd, zou de aanvaller het service account van de applicatie kunnen gebruiken om volledige controle over deze resources te krijgen, wat mogelijk tot clusterbrede inbreuken leidt.

Een veiligere aanpak zou inhouden dat je een Role aanmaakt met meer beperkte rechten binnen een specifieke namespace:

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: my-namespace
  name: fastapi-role
rules:
  - apiGroups: [""]
    resources: ["pods", "services"]
    verbs: ["get", "list", "watch"]

Deze Role beperkt de toegang tot pods en services binnen de my-namespace-namespace, waardoor het risico wordt verkleind dat een gecompromitteerd service account tot een clusterbrede inbreuk leidt.

Best practices voor het configureren van RBAC

Om de risico’s van een onjuiste RBAC-configuratie te voorkomen, is het essentieel om best practices te volgen bij het definiëren van rollen en rechten in zowel je FastAPI-applicatie als je Kubernetes-omgeving.

1. Principe van least privilege

Het principe van least privilege schrijft voor dat gebruikers, rollen en services de minimale rechten zouden moeten hebben die nodig zijn om hun taken uit te voeren. Dit verkleint het risico op onbedoelde of kwaadwillende acties die tot beveiligingsinbreuken kunnen leiden.

  • Applicatie-RBAC: Zorg ervoor dat elke rol alleen de rechten krijgt die nodig zijn voor zijn functies. Een “manager”-rol zou bijvoorbeeld alleen toegang moeten hebben tot de data en acties die nodig zijn om hun afdeling te beheren, niet tot het hele systeem.
  • Kubernetes RBAC: Beperk de rechten van service accounts en gebruikers tot de specifieke acties die ze binnen het cluster moeten uitvoeren. Vermijd het verlenen van brede rechten die uitgebuit zouden kunnen worden als het account wordt gecompromitteerd.

2. Audit en review van rollen en rechten

Rollen en rechten zouden niet statisch moeten zijn. Audit en review de RBAC-configuraties in je FastAPI-applicatie en Kubernetes-omgeving regelmatig om ervoor te zorgen dat ze aansluiten bij de huidige beveiligingseisen.

  • Applicatie-RBAC: Bekijk periodiek de rollen en rechten die in je applicatie zijn gedefinieerd om er zeker van te zijn dat ze nog relevant zijn en dat geen enkele gebruiker meer toegang heeft dan nodig.
  • Kubernetes RBAC: Gebruik tools zoals kubectl auth can-i om te testen en verifiëren welke acties een bepaalde rol of gebruiker kan uitvoeren. Daarnaast kunnen Kubernetes audit logs helpen om toegangspatronen bij te houden en te analyseren om potentiële misconfiguraties of ongeautoriseerde toegang te detecteren.

3. Implementeer Attribute-Based Access Control (ABAC)

Hoewel RBAC breed wordt gebruikt, biedt het niet altijd de granulariteit die nodig is voor complexe toegangscontrolescenario’s. Attribute-Based Access Control (ABAC) kan worden geïmplementeerd als een extra beveiligingslaag. ABAC maakt het mogelijk om toegangsbeslissingen te baseren op attributen zoals gebruikersrollen, resource-typen en omgevingsfactoren.

In FastAPI kun je ABAC implementeren door aangepaste logica toe te voegen aan je toegangscontrole-checks, waarbij je factoren meeweegt zoals gebruikersattributen, de context van het verzoek en meer.

4. Gebruik namespaced roles waar mogelijk

Geef in Kubernetes waar mogelijk de voorkeur aan namespaced roles (Role en RoleBinding) boven clusterbrede roles (ClusterRole en ClusterRoleBinding). Namespaced roles beperken de scope van de rechten tot een specifieke namespace, waardoor de impact van een gecompromitteerde rol wordt verkleind.

Standaard network policies

Het volgende kritieke aspect dat in Kubernetes-beveiliging vaak over het hoofd wordt gezien, is de configuratie van network policies, of nauwkeuriger gezegd, het ontbreken daarvan. Veel standaard Kubernetes-netwerkinstellingen bevatten geen restrictieve network policies, wat leidt tot een situatie waarin pods binnen een cluster vrij met elkaar kunnen communiceren. Deze onbeperkte communicatie kan een aanzienlijk beveiligingsrisico vormen, met name voor een FastAPI-applicatie, waarbij een aanvaller deze open netwerkkanalen zou kunnen misbruiken om zich lateraal binnen het cluster te bewegen, toegang te krijgen tot gevoelige data of services te verstoren.

Kubernetes network policies begrijpen

Kubernetes network policies zijn in wezen firewallregels die de verkeersstroom tussen pods en tussen pods en andere netwerkendpoints regelen. Met deze policy’s kun je specificeren welke pods met elkaar mogen communiceren en onder welke omstandigheden. Zonder network policies staat Kubernetes onbeperkte communicatie tussen alle pods binnen een cluster toe, wat gevaarlijk kan zijn in een productieomgeving.

In een typische deployment heb je mogelijk verschillende services die in verschillende pods binnen je Kubernetes-cluster draaien. Je FastAPI-applicatie zou bijvoorbeeld kunnen communiceren met een backend-database, een caching-laag en andere microservices. Als je geen network policies configureert, zou elke pod in het cluster, inclusief de pods die geen toegang zouden moeten hebben, mogelijk verbinding kunnen maken met elke andere pod, wat tot een breed aanvalsoppervlak leidt.

De risico’s van onbeperkte pod-communicatie

Wanneer network policies niet correct zijn geconfigureerd, ontstaan er verschillende beveiligingskwetsbaarheden:

  1. Laterale beweging: Als een aanvaller toegang krijgt tot één pod, zou hij zich lateraal binnen het cluster kunnen bewegen, andere services kunnen onderzoeken en benaderen waartoe hij geen toegang zou mogen hebben. Als je FastAPI-pod bijvoorbeeld wordt gecompromitteerd, zou de aanvaller toegang kunnen krijgen tot andere kritieke services zoals je database of interne API’s.

  2. Toegang tot data: Zonder network policies kan gevoelige data uit het cluster worden gekopieerd. Een aanvaller zou data van de ene gecompromitteerde pod naar de andere kunnen sturen, of zelfs buiten het cluster, zonder enige beperking.

  3. Denial of Service (DoS): Een aanvaller zou een andere service kunnen overspoelen met verkeer, wat een denial of service veroorzaakt. Zonder network policies om te beperken welke pods met elke service kunnen communiceren, is het lastig om dit soort aanvallen te voorkomen.

  4. Verstoring van services: Als een aanvaller kwaadaardig verkeer tussen pods kan sturen, zou hij services kunnen verstoren, wat leidt tot downtime van de applicatie of inconsistent gedrag.

Voorbeeld: het ontbreken van network policies in een FastAPI-applicatie misbruiken

Laten we een scenario bekijken waarin een FastAPI-applicatie is uitgerold op een Kubernetes-cluster zonder enige network policies. We lopen door hoe een aanvaller deze situatie kan misbruiken om het hele cluster te compromitteren.

Stap 1: Initiële compromittering

Stel je voor dat je FastAPI-applicatie een kwetsbaarheid heeft in een van zijn API-endpoints, waardoor een aanvaller willekeurige code binnen de pod kan uitvoeren. De aanvaller buit deze kwetsbaarheid uit en krijgt voet aan de grond binnen de FastAPI-pod. Op dit punt heeft de aanvaller toegang tot het bestandssysteem, de environment-variabelen en het netwerk binnen de pod.

Stap 2: Scannen naar andere services

Met toegang tot de FastAPI-pod kan de aanvaller nu het netwerk gaan scannen om andere services te ontdekken die binnen het cluster draaien. Omdat er geen network policies zijn, kan de aanvaller andere pods vrijelijk onderzoeken, op zoek naar open poorten en kwetsbare services.

Zo zou de aanvaller kunnen ontdekken dat er een PostgreSQL-database in een andere pod draait, bereikbaar op een bekende poort (5432). De aanvaller zou vervolgens kunnen proberen verbinding te maken met deze database met standaard of zwakke credentials.

Stap 3: Beweging tussen pods en toegang tot data

Nadat hij de database-pod heeft ontdekt, kan de aanvaller proberen verbinding ermee te maken, en als dat lukt, kan de aanvaller dankzij het ontbreken van netwerksegmentatie nu de database bevragen en gevoelige informatie extraheren zoals gebruikersdata, applicatielogs of andere kritieke data.

De aanvaller zou ook aanvullende tools binnen de gecompromitteerde FastAPI-pod kunnen inzetten om het netwerk verder te verkennen, op zoek naar andere kritieke services, zoals interne API’s, message queues of zelfs managementinterfaces die mogelijk blootgesteld zijn.

Stap 4: Escalatie en persistente toegang

Met onbeperkte toegang kan de aanvaller zich lateraal naar andere pods bewegen, aanvullende services compromitteren en zijn controle over het cluster uitbreiden. Hij zou een backdoor kunnen inzetten om persistente toegang te behouden, waardoor hij de omgeving opnieuw kan betreden, zelfs als de oorspronkelijke kwetsbaarheid is gepatcht.

Bovendien zou de aanvaller de gecompromitteerde services kunnen gebruiken om een geavanceerdere aanval op te zetten, zoals het escaleren van privileges om toegang te krijgen tot de Kubernetes API-server, of het verstoren van services door belangrijke componenten te overspoelen met kwaadaardig verkeer.

Best practices voor het configureren van network policies

Om dergelijke aanvallen te voorkomen, is het cruciaal om network policies te implementeren die de communicatie tussen pods beperken tot alleen wat noodzakelijk is. Hier zijn enkele best practices voor het configureren van network policies in een Kubernetes-omgeving waarin een FastAPI-applicatie draait:

1. Implementeer het principe van least privilege

Net als bij RBAC zou je het principe van least privilege moeten toepassen op netwerkcommunicatie. Sta alleen de minimaal noodzakelijke communicatie tussen pods toe. Als je FastAPI-applicatie bijvoorbeeld alleen hoeft te communiceren met een database en een caching-service, zou de network policy dit verkeer expliciet moeten toestaan en al het andere moeten blokkeren.

Hier is een voorbeeld van een eenvoudige network policy die het verkeer beperkt tot alleen communicatie tussen de FastAPI-pod en de PostgreSQL-database-pod:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-fastapi-db
  namespace: mynamespace
spec:
  podSelector:
    matchLabels:
      app: fastapi
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: postgres
      ports:
        - protocol: TCP
          port: 5432

Deze policy zorgt ervoor dat de FastAPI-pod alleen verkeer van de PostgreSQL-pod op poort 5432 kan ontvangen. Al het andere inkomende verkeer wordt geweigerd.

2. Standaard al het verkeer weigeren

Een goede praktijk is om een standaard deny all-network policy aan te maken, die standaard al het verkeer blokkeert. Vervolgens kun je specifieke network policies aanmaken om het noodzakelijke verkeer tussen je services toe te staan. Deze aanpak zorgt ervoor dat alle services die niet expliciet toestemming hebben om te communiceren van elkaar geïsoleerd zijn.

Hier is een voorbeeld van een standaard deny-policy:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: mynamespace
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

Deze policy blokkeert al het inkomende en uitgaande verkeer voor alle pods in de mynamespace-namespace, tenzij dit expliciet wordt toegestaan door andere network policies.

3. Gebruik namespace-isolatie

In Kubernetes bieden namespaces een manier om resources binnen hetzelfde cluster te isoleren. Door network policies te implementeren die het verkeer tussen namespaces beperken, kun je je services verder segmenteren en het risico op laterale beweging binnen het cluster verkleinen.

Je zou bijvoorbeeld aparte namespaces kunnen hebben voor frontend-, backend- en database-services. Je kunt network policies configureren om de communicatie tussen deze namespaces te beperken, zodat alleen het noodzakelijke verkeer wordt toegestaan.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: restrict-cross-namespace
  namespace: frontend
spec:
  podSelector:
    matchLabels:
      app: frontend-app
  policyTypes:
    - Ingress
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              name: backend
      ports:
        - protocol: TCP
          port: 80

In dit voorbeeld mogen pods in de frontend-namespace alleen verkeer ontvangen van de backend-namespace op poort 80.

4. Audit network policies regelmatig

Naarmate je applicatie evolueert, evolueren ook je netwerkeisen. Audit je network policies regelmatig om er zeker van te zijn dat ze nog steeds geschikt zijn voor je huidige architectuur. Verwijder eventuele verouderde policy’s die niet langer nodig zijn, en werk bestaande policy’s bij om eventuele wijzigingen in je netwerktopologie te weerspiegelen.

Kubernetes biedt tools om je te helpen network policies te auditen en te monitoren. Je kunt bijvoorbeeld kubectl-commando’s gebruiken om alle network policies in een namespace te bekijken:

kubectl get networkpolicies -n mynamespace

Je kunt ook individuele policy’s inspecteren om hun configuraties te verifiëren:

kubectl describe networkpolicy <policy-name> -n mynamespace

5. Benut logging en monitoring van network policies

Schakel logging en monitoring in voor je network policies om verkeersstromen bij te houden en eventuele anomalieën te detecteren. Tools zoals Cilium, Calico en Weave Net bieden geavanceerde mogelijkheden voor network policies, waaronder logging en monitoring.

Door netwerklogs te analyseren, kun je onverwachte verkeerspatronen, potentiële beveiligingsincidenten en gebieden identificeren waar je policy’s mogelijk moeten worden bijgesteld.

Conclusie

In een Kubernetes-omgeving kan het ontbreken van correct geconfigureerde network policies je FastAPI-applicatie en het hele cluster kwetsbaar maken voor aanvallen. Zonder deze policy’s kan een aanvaller die toegang krijgt tot één pod zich mogelijk lateraal binnen het cluster bewegen, toegang krijgen tot gevoelige data, services verstoren en privileges escaleren.

Door restrictieve network policies te implementeren op basis van het principe van least privilege, kun je het aanvalsoppervlak van je applicatie aanzienlijk verkleinen. Network policies zouden een integraal onderdeel van je Kubernetes-beveiligingsstrategie moeten zijn, zodat alleen geautoriseerde communicatie tussen pods wordt toegestaan en je applicatie veilig blijft.

Secrets-beheer

Waarschijnlijk is het eerste waar je aan denkt bij het beveiligen van de deployment van je Python-applicatie het beheer van secrets. Zeker wanneer er gevoelige data bij betrokken is, zoals API-sleutels, wachtwoorden en certificaten. In Kubernetes worden secrets standaard vaak als platte tekst opgeslagen, wat aanzienlijke risico’s met zich meebrengt als dit niet goed wordt aangepakt. Dit hoofdstuk gaat in op de uitdagingen en best practices van secrets-beheer binnen de context van een FastAPI-applicatie die op Kubernetes is uitgerold. In dit deel verkennen we hoe een aanvaller misbruik kan maken van onjuist beheerde secrets, en geven we praktische voorbeelden van hoe je ze effectief kunt beveiligen.

Kubernetes secrets begrijpen

Kubernetes secrets zijn ontworpen om gevoelige informatie op te slaan zoals API-sleutels, database-credentials en TLS-certificaten. Deze secrets worden meestal in pods geïnjecteerd als environment-variabelen of als bestanden gemount. Hoewel Kubernetes een handige manier biedt om gevoelige data te beheren, heeft de standaardconfiguratie verschillende beveiligingstekortkomingen.

  1. Opslag in platte tekst: Standaard worden Kubernetes secrets opgeslagen als base64-gecodeerde strings, die niet versleuteld zijn. Dit betekent dat iedereen met toegang tot de etcd-database (waar Kubernetes zijn clusterstaat opslaat) of met voldoende rechten deze secrets eenvoudig kan decoderen en benaderen.

  2. Brede toegang: Secrets in Kubernetes kunnen worden benaderd door elke pod of gebruiker met voldoende rechten. Als toegangscontroles niet correct zijn geconfigureerd, kunnen secrets worden blootgesteld aan ongeautoriseerde gebruikers of services.

  3. Gebrek aan auditing: Zonder goede auditing is het lastig om bij te houden wie welke secrets wanneer heeft benaderd. Dit maakt het moeilijk om ongeautoriseerde toegang of verdachte activiteit met gevoelige data te detecteren.

Risico’s van onveilig secrets-beheer

Wanneer secrets niet goed beveiligd zijn in een Kubernetes-omgeving, kunnen ze eenvoudig worden gecompromitteerd. Laten we enkele van de belangrijkste risico’s bekijken die met onveilig secrets-beheer samenhangen:

  1. Blootstelling van etcd: Kubernetes slaat secrets op in etcd, de key-value-store die de clusterstaat bewaart. Als etcd niet versleuteld of goed beveiligd is, kan iedereen met toegang tot etcd secrets ophalen en decoderen, wat leidt tot een volledige compromittering van gevoelige informatie.

  2. Gecompromitteerde pods: Als een pod wordt gecompromitteerd (bijv. via een kwetsbaarheid in je FastAPI-applicatie), zou de aanvaller secrets kunnen extraheren die als environment-variabelen of bestanden zijn gemount. Deze secrets zouden vervolgens kunnen worden gebruikt om verder toegang te krijgen tot andere services of systemen.

  3. Ongeautoriseerde toegang: Verkeerd geconfigureerde RBAC (Role-Based Access Control) kan ongeautoriseerde gebruikers of services toegang tot secrets geven. Dit kan leiden tot datalekken of privilege-escalatie binnen het cluster.

  4. Gebrek aan rotatie: Als secrets niet regelmatig worden geroteerd, zou een aanvaller die toegang krijgt tot een secret langdurig toegang kunnen hebben tot gevoelige data, zelfs nadat de oorspronkelijke kwetsbaarheid is gepatcht.

Voorbeeld: onveilig secrets-beheer in een FastAPI-applicatie misbruiken

Laten we een scenario doorlopen waarin een FastAPI-applicatie die op Kubernetes is uitgerold wordt gecompromitteerd door onveilig secrets-beheer. We verkennen hoe een aanvaller deze zwakte kan misbruiken om toegang te krijgen tot gevoelige data en zijn privileges binnen het cluster te escaleren.

Stap 1: Initieel gecompromitteerde pod

Stel je voor dat je FastAPI-applicatie een kwetsbaarheid heeft in een van zijn API-endpoints, waardoor een aanvaller willekeurige code vanuit de pod kan uitvoeren. De aanvaller buit deze kwetsbaarheid uit en krijgt voet aan de grond binnen de FastAPI-pod. Op dit punt heeft de aanvaller toegang tot het bestandssysteem, de environment-variabelen en het netwerk binnen de pod.

# Vulnerable FastAPI endpoint
@app.post("/upload")
async def upload_file(file: UploadFile = File(...)):
    file_location = f"/tmp/{file.filename}"
    with open(file_location, "wb+") as file_object:
        file_object.write(file.file.read())
    return {"info": f"file '{file.filename}' saved at '{file_location}'"}

In het bovenstaande voorbeeld zou een onjuist afgehandelde file-upload de aanvaller in staat kunnen stellen een kwaadaardig script te uploaden en het binnen de pod uit te voeren.

Stap 2: Secrets extraheren uit environment-variabelen

De FastAPI-applicatie gebruikt secrets die als environment-variabelen zijn opgeslagen om verbinding te maken met een PostgreSQL-database. Deze secrets worden in de pod geïnjecteerd via een Kubernetes Secret-object, dat als environment-variabelen wordt gemount.

apiVersion: v1
kind: Secret
metadata:
  name: db-secrets
type: Opaque
data:
  POSTGRES_USER: cG9zdGdyZXM=
  POSTGRES_PASSWORD: c2VjdXJlcGFzcw==
  POSTGRES_DB: ZGF0YWJhc2U=

Deze base64-gecodeerde secrets worden als environment-variabelen in de FastAPI-pod gemount:

apiVersion: v1
kind: Pod
metadata:
  name: fastapi-app
spec:
  containers:
    - name: fastapi
      image: my-fastapi-app:latest
      env:
        - name: POSTGRES_USER
          valueFrom:
            secretKeyRef:
              name: db-secrets
              key: POSTGRES_USER
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: db-secrets
              key: POSTGRES_PASSWORD
        - name: POSTGRES_DB
          valueFrom:
            secretKeyRef:
              name: db-secrets
              key: POSTGRES_DB

Eenmaal binnen de gecompromitteerde pod kan de aanvaller deze environment-variabelen eenvoudig benaderen en de secrets decoderen:

echo $POSTGRES_USER | base64 --decode
echo $POSTGRES_PASSWORD | base64 --decode
echo $POSTGRES_DB | base64 --decode

Met deze credentials kan de aanvaller verbinding maken met de PostgreSQL-database, gevoelige data extraheren en zelfs de database wijzigen als er schrijftoegang is verleend.

Stap 3: Secrets benaderen vanuit het bestandssysteem

Naast environment-variabelen kunnen Kubernetes secrets ook als bestanden in het bestandssysteem van de pod worden gemount. De aanvaller, met toegang tot de shell van de pod, kan deze bestanden eenvoudig lokaliseren en lezen.

Als de secrets bijvoorbeeld als bestanden onder /etc/secrets zijn gemount, kan de aanvaller de bestanden weergeven en hun inhoud lezen:

ls /etc/secrets
cat /etc/secrets/POSTGRES_USER
cat /etc/secrets/POSTGRES_PASSWORD
cat /etc/secrets/POSTGRES_DB

Dit stelt dezelfde gevoelige data bloot die als environment-variabelen waren opgeslagen, maar nu benaderd via het bestandssysteem. De aanvaller kan deze secrets gebruiken om verder in de backend-services van de applicatie te infiltreren.

Stap 4: Laterale beweging en privilege-escalatie

Met de database-credentials in handen zou de aanvaller kunnen proberen verbinding te maken met de PostgreSQL-database vanuit de pod. Afhankelijk van de beveiligingsconfiguratie van de database zou dit de aanvaller toegang kunnen geven tot gebruikersdata, configuratie-instellingen en andere gevoelige informatie.

De aanvaller zou ook kunnen proberen privileges te escaleren door te zoeken naar aanvullende secrets of credentials binnen de pod of andere verbonden services. Als de pod bijvoorbeeld toegang heeft tot andere Kubernetes secrets of service accounts met verhoogde rechten, zou de aanvaller deze kunnen gebruiken om verdere controle over het cluster te krijgen.

Best practices voor veilig secrets-beheer

Om dergelijke aanvallen te voorkomen, is het cruciaal om veilige praktijken voor secrets-beheer binnen je Kubernetes-omgeving te implementeren. Hier zijn enkele best practices om te volgen:

1. Versleutel secrets at rest

Standaard slaat Kubernetes secrets op in etcd als base64-gecodeerde strings, die niet versleuteld zijn. Het is essentieel om versleuteling at rest in te schakelen voor etcd, zodat secrets veilig worden opgeslagen.

Kubernetes biedt een ingebouwd mechanisme om secrets at rest te versleutelen. Je kunt dit configureren door het EncryptionConfig-bestand dat door de API-server wordt gebruikt aan te passen.

Hier is een voorbeeldconfiguratie voor het inschakelen van versleuteling at rest:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aescbc:
          keys:
            - name: key1
              secret: <base64-encoded-32-byte-key>
      - identity: {}

Deze configuratie versleutelt alle secrets met het AES-CBC-algoritme. De versleutelingssleutel zou veilig opgeslagen en regelmatig geroteerd moeten worden.

2. Gebruik externe tools voor secrets-beheer

In plaats van uitsluitend te vertrouwen op Kubernetes secrets, kun je overwegen om een externe tool voor secrets-beheer te gebruiken zoals HashiCorp Vault, AWS Secrets Manager of Azure Key Vault. Deze tools bieden geavanceerde functies zoals secret-rotatie, auditing en fijnmazige toegangscontrole.

Met HashiCorp Vault kun je bijvoorbeeld dynamisch secrets genereren die kortlevend zijn, waardoor het risico op blootstelling wordt verkleind als een secret wordt gecompromitteerd.

Het integreren van Vault met Kubernetes houdt in dat je Vault zo configureert dat het secrets veilig in pods injecteert. Hier is een voorbeeld van hoe je een FastAPI-applicatie zou kunnen configureren om Vault te gebruiken:

  1. Configureer Vault: Zet Vault op en schakel de Kubernetes-authenticatiemethode in.

  2. Maak een Vault-policy aan: Definieer een policy die de toegang beperkt tot alleen de noodzakelijke secrets.

path "secret/data/db-secrets" {
  capabilities = ["read"]
}
  1. Annoteer de pod: Pas de pod-configuratie aan om Vault-annotaties op te nemen voor het injecteren van secrets:
apiVersion: v1
kind: Pod
metadata:
  name: fastapi-app
  annotations:
    vault.hashicorp.com/agent-inject: "true"
    vault.hashicorp.com/role: "my-fastapi-role"
    vault.hashicorp.com/agent-inject-secret-db-secrets: "secret/data/db-secrets"
spec:
  containers:
    - name: fastapi
      image: my-fastapi-app:latest
      env:
        - name: POSTGRES_USER
          value: "{{- with secret 'db-secrets' -}}{{ .Data.data.POSTGRES_USER }}{{ end }}"
        - name: POSTGRES_PASSWORD
          value: "{{- with secret 'db-secrets' -}}{{ .Data.data.POSTGRES_PASSWORD }}{{ end }}"
        - name: POSTGRES_DB
          value: "{{- with secret 'db-secrets' -}}{{ .Data.data.POSTGRES_DB }}{{ end }}"
  1. Rol de applicatie uit: Wanneer de pod start, injecteert Vault automatisch de secrets in de gespecificeerde environment-variabelen of bestanden binnen de pod. Deze aanpak zorgt ervoor dat je secrets nooit onversleuteld in Kubernetes worden opgeslagen en dat ze tijdens runtime veilig aan de applicatie worden geleverd.

Door externe tools voor secrets-beheer zoals Vault te gebruiken, bescherm je niet alleen secrets met robuuste versleuteling en toegangscontroles, maar krijg je ook aanvullende functies zoals dynamische secret-generatie, automatische rotatie en gedetailleerde auditing.

3. Beperk toegang tot secrets met RBAC

Zoals in de vorige secties is genoemd, is Role-Based Access Control (RBAC) essentieel voor het beveiligen van je Kubernetes-omgeving. Je zou zorgvuldig moeten bepalen welke gebruikers en service accounts toegang hebben tot secrets. Verkeerd geconfigureerde RBAC-instellingen kunnen leiden tot ongeautoriseerde toegang tot gevoelige data, dus het is cruciaal om ervoor te zorgen dat alleen de noodzakelijke rollen toestemming hebben om secrets te lezen of te wijzigen.

Zo kun je RBAC gebruiken om de toegang tot secrets te beperken:

  1. Maak een Role met beperkte toegang aan: Definieer een role die alleen toegang verleent tot de specifieke secrets die je applicatie nodig heeft.
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: mynamespace
  name: fastapi-secrets-reader
rules:
  - apiGroups: [""]
    resources: ["secrets"]
    resourceNames: ["db-secrets"]
    verbs: ["get"]

Deze role verleent alleen toegang tot de db-secrets-secret in de mynamespace-namespace.

  1. Koppel de Role aan een service account: Gebruik een RoleBinding om de role te associëren met een specifiek service account dat je FastAPI-applicatie zal gebruiken.
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: fastapi-secrets-binding
  namespace: mynamespace
subjects:
  - kind: ServiceAccount
    name: fastapi-service-account
    namespace: mynamespace
roleRef:
  kind: Role
  name: fastapi-secrets-reader
  apiGroup: rbac.authorization.k8s.io

Deze RoleBinding zorgt ervoor dat alleen het fastapi-service-account service account toestemming heeft om de db-secrets te benaderen.

  1. Gebruik het service account in je pod: Zorg er ten slotte voor dat je FastAPI-pod het gespecificeerde service account gebruikt.
apiVersion: v1
kind: Pod
metadata:
  name: fastapi-app
  namespace: mynamespace
spec:
  serviceAccountName: fastapi-service-account
  containers:
    - name: fastapi
      image: my-fastapi-app:latest

Door deze stappen te volgen, kun je strak bepalen welke entiteiten binnen je Kubernetes-cluster toegang hebben tot secrets, waardoor het risico op ongeautoriseerde toegang wordt verkleind.

4. Roteer secrets regelmatig

Secrets zouden niet statisch moeten zijn. Het regelmatig roteren van secrets is een best practice die het tijdvenster verkleint voor een aanvaller die mogelijk toegang heeft gekregen tot een secret. Als een secret wordt gecompromitteerd, kan het onmiddellijk roteren ervan de impact beperken.

Het automatiseren van secret-rotatie is een krachtige aanpak. Als je bijvoorbeeld HashiCorp Vault of AWS Secrets Manager gebruikt, kunnen deze tools secrets automatisch roteren met gedefinieerde intervallen of op aanvraag. Je zou er ook voor moeten zorgen dat je FastAPI-applicatie secret-rotatie zonder downtime kan afhandelen.

Hier is een voorbeeld van hoe je secret-rotatie in Vault zou kunnen implementeren:

  1. Schakel dynamische secrets in: Schakel bijvoorbeeld de PostgreSQL secrets-engine in Vault in om dynamisch database-credentials te genereren:
vault secrets enable database
vault write database/config/my-postgresql-database \
    plugin_name=postgresql-database-plugin \
    allowed_roles="fastapi-app" \
    connection_url="postgresql://{{username}}:{{password}}@db.example.com:5432/dbname?sslmode=disable"
  1. Maak een role aan voor dynamische credentials: Definieer een role in Vault die bepaalt hoe dynamische credentials worden gegenereerd:
vault write database/roles/fastapi-app \
    db_name=my-postgresql-database \
    creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}';" \
    default_ttl="1h" \
    max_ttl="24h"
  1. Gebruik dynamische secrets in je applicatie: Pas je FastAPI-applicatie aan om dynamische credentials bij Vault op te vragen:
import hvac

client = hvac.Client(url='http://127.0.0.1:8200', token='your-vault-token')
db_creds = client.secrets.database.generate_credentials(name='fastapi-app')

db_username = db_creds['data']['username']
db_password = db_creds['data']['password']

# Use these credentials to connect to your database

In deze opzet genereert Vault een nieuwe database-gebruikersnaam en -wachtwoord telkens wanneer de FastAPI-applicatie ze opvraagt. Deze credentials zijn kortlevend en verlopen automatisch, waardoor het risico op blootstelling wordt verkleind.

5. Vermijd het hardcoden van secrets in code

Een van de meest basale maar vaak over het hoofd geziene praktijken is het vermijden van het hardcoden van secrets rechtstreeks in je broncode. Het hardcoden van secrets kan leiden tot onbedoelde blootstelling, vooral als de code wordt gedeeld, naar een publieke repository wordt gepusht of wordt opgenomen in CI/CD-pipelines.

Gebruik in plaats van hardcoden environment-variabelen, externe configuratiebestanden of tools voor secrets-beheer om secrets tijdens runtime in je applicatie te injecteren.

In plaats van bijvoorbeeld een database-wachtwoord te hardcoden:

DATABASE_PASSWORD = "mysecretpassword"

Gebruik environment-variabelen:

import os

DATABASE_PASSWORD = os.getenv("DATABASE_PASSWORD")

Of, als je een tool voor secrets-beheer gebruikt:

import hvac

client = hvac.Client(url='http://127.0.0.1:8200', token='your-vault-token')
secret = client.read('secret/data/db-secrets')
DATABASE_PASSWORD = secret['data']['password']

Deze aanpak zorgt ervoor dat gevoelige informatie niet rechtstreeks in je codebase is ingebed en alleen tijdens runtime toegankelijk is.

6. Monitor en audit toegang tot secrets

Het monitoren en auditen van toegang tot secrets is cruciaal voor het detecteren van ongeautoriseerde toegang of verdacht gedrag. Schakel logging in voor alle bewerkingen die secrets benaderen en bekijk deze logs regelmatig om potentiële beveiligingsincidenten te identificeren.

Als je externe tools voor secrets-beheer gebruikt, bieden die vaak ingebouwde auditing-mogelijkheden. Vault logt bijvoorbeeld alle toegangsgebeurtenissen, inclusief welke gebruiker welke secret wanneer heeft benaderd. Deze logs kunnen worden geïntegreerd met je SIEM-systeem (Security Information and Event Management) voor realtime analyse en alerting.

In Kubernetes kun je auditing inschakelen voor API-verzoeken, inclusief die met secrets. Configureer de audit-policy om secret-toegangsgebeurtenissen te loggen en analyseer deze logs om eventuele afwijkende patronen te detecteren.

apiVersion: audit.k8s.io/v1
kind: Policy
rules:
  - level: RequestResponse
    resources:
      - group: ""
        resources: ["secrets"]

Deze policy logt zowel het verzoek als het antwoord voor alle secret-gerelateerde API-aanroepen, wat gedetailleerde informatie oplevert die kan worden gebruikt voor beveiligingsanalyse.

7. Gebruik Kubernetes secrets op de juiste manier

Hoewel Kubernetes secrets standaard niet versleuteld zijn, bieden ze nog steeds een handige manier om gevoelige data binnen het cluster te beheren. Als je Kubernetes secrets moet gebruiken, neem dan de volgende voorzorgsmaatregelen:

  • Schakel etcd-versleuteling in: Zoals eerder genoemd, zorg ervoor dat etcd-versleuteling is ingeschakeld om secrets die in de key-value-store van het cluster zijn opgeslagen te beschermen.
  • Beperk toegang tot secrets: Gebruik RBAC om te beperken welke pods en gebruikers toegang hebben tot specifieke secrets. Zorg ervoor dat alleen de pods die een secret absoluut nodig hebben er toegang toe hebben.
  • Vermijd buitensporig veel secrets: Overbelast je Kubernetes secrets niet met te veel data. Houd secrets minimaal en sla alleen op wat noodzakelijk is. Overweeg voor grote hoeveelheden gevoelige data een robuustere oplossing voor secrets-beheer.
  • Roteer en update secrets regelmatig: Zelfs als je Kubernetes secrets gebruikt, roteer en update ze regelmatig. Deze praktijk minimaliseert het risico op langdurige blootstelling als een secret wordt gecompromitteerd.

Conclusie

In deze blogpost hebben we enkele van de belangrijkste onderwerpen doorgenomen voor het beveiligen van je Python-applicatie die in Kubernetes draait. Er zijn uiteraard nog veel meer aspecten waar je op moet letten. Die komen in de komende posts aan bod.

Blog