Verwaltung von Geheimnissen in Kubernetes kann einfach klingen – bis man es tatsächlich in großem Maßstab umsetzt. Zu den gängigen Ansätzen gehören das Einbinden von Kubernetes-Geheimnissen als Umgebungsvariablen, die Verwendung eines externen Secrets-Operators oder der direkte Aufruf von Cloud-SDKs aus dem Anwendungscode heraus. Jede Option bringt Kompromisse in Bezug auf Caching, Auditing, Sicherheitsgrenzen und Anwendungskopplung mit sich.
In diesem Beitrag stellen wir ein alternatives Muster vor: den Betrieb des AWS Secrets Manager-Agenten als Sidecar-Container. Bei diesem Ansatz ruft Ihre Anwendung Geheimnisse über einen lokalen HTTP-Aufruf ab – ein AWS SDK ist nicht erforderlich – und profitiert gleichzeitig von integrierter Zwischenspeicherung, SSRF-Schutz und einer klaren Trennung der Zuständigkeiten.
Wir werden die Einrichtung anhand eines kleinen Go-Dienstes veranschaulichen, aber dieses Sidecar-Muster funktioniert mit jeder Sprache und jeder Laufzeitumgebung.
Warum sollte man für die Verwaltung von Geheimnissen einen Sidecar verwenden?
Der Secrets Manager Agent läuft als separater Container im selben Pod. Ihre Anwendung kommuniziert mit ihm über localhost:2773, was mehrere Vorteile bietet:
- Keine AWS-SDK-Abhängigkeit in Ihrer App – nur eine einfache HTTP-GET-Anfrage
- Integriertes Caching mit konfigurierbarer TTL (Geheimnisse werden automatisch aktualisiert, ohne dass der Pod neu gestartet werden muss)
- SSRF-Token-Schutz: Nur Container, die über das Token verfügen, können Geheimnisse abrufen
- Verbesserte Portabilität – zum Wechseln der geheimen Backends muss lediglich der Sidecar aktualisiert werden
- Bessere Überwachung – in Verbindung mit OpenTelemetry-Trace-IDs können Sie genau nachverfolgen, welcher Endpunkt auf welches Geheimnis zugegriffen hat
Die Nachteile sind ein zusätzlicher Container pro Pod und ein lokaler HTTP-Aufruf. In der Praxis verursachen beide nur einen vernachlässigbaren Mehraufwand.
Architekturübersicht

Beim Start des Pods generiert der Agent ein zufälliges SSRF-Token, schreibt es in ein gemeinsames In-Memory-Volume und exportiert es als Umgebungsvariable.
Der Anwendungscontainer liest dasselbe Token aus dem gemeinsamen Volume und fügt es in jede Anfrage an den Agenten ein.
Schritt 1: Containerisierung des AWS Secrets Manager-Agenten
AWS stellt den Agenten als Rust-Projekt auf GitHub zur Verfügung. Wir erstellen ihn aus dem Quellcode und fixieren ihn zur Gewährleistung der Reproduzierbarkeit auf ein Release-Tag.
# Build stage - compile the Rust binary
FROM rust:1.82-bookworm AS builder
RUN apt-get update && apt-get install -y build-essential && rm -rf /var/lib/apt/lists/*
WORKDIR /build
# Clone the agent repo (pin to a release tag for reproducibility)
RUN git clone --branch v2.0.0 --depth 1 https://github.com/aws/aws-secretsmanager-agent.git .
RUN cargo build --release
# Runtime stage - minimal image with just the binary
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
WORKDIR /app
COPY --from=builder /build/target/release/aws_secretsmanager_agent ./secrets-manager-agent
COPY startup.sh .
COPY config.toml .
RUN chmod +x startup.sh secrets-manager-agent
ENTRYPOINT ["./startup.sh"]
Agent-Konfiguration (config.toml)
log_level = "INFO"
log_to_file = false
http_port = 2773
ttl_seconds = 300
cache_size = 1000
Die Einstellung `ttl_seconds = 300` bedeutet, dass Geheimnisse fünf Minuten lang zwischengespeichert werden. Danach ruft der Agent automatisch neue Werte ab – ein Neustart des Pods ist nicht erforderlich.
Startskript und Umgang mit SSRF-Token
#!/bin/bash
set -e
# Generate a random SSRF token, write to shared volume and export as env var
TOKEN=$(head -c 32 /dev/urandom | base64 | tr -d '=+/')
echo -n "$TOKEN" > /shared/awssmatoken
chmod 444 /shared/awssmatoken
export AWS_TOKEN="$TOKEN"
# Start the agent with logging to stdout
exec ./secrets-manager-agent --config config.toml 2>&1
Das Token wird bei jedem Start des Pods temporär generiert, ausschließlich im Arbeitsspeicher gespeichert und zusammen mit dem Pod gelöscht. Es gibt nichts, was rotiert oder dauerhaft gespeichert werden müsste.
Erstellen und an ECR übertragen (in diesem Beispiel arm64 für Graviton-Knoten):
docker buildx build --platform linux/arm64 \ -t <account>.dkr.ecr.us-east-1.amazonaws.com/aws-secrets-manager-agent:lates\--push ./secrets-manager-agent
Schritt 2: Die Go-Anwendung – Aufruf des Sidecars
Die Anwendung nutzt nicht das AWS SDK. Stattdessen führt sie einen HTTP-GET-Aufruf an den lokalen Agenten aus und übergibt das SSRF-Token in einem Request-Header.
func sidecarHandler(w http.ResponseWriter, r *http.Request) {
secretName := os.Getenv("secret")
if secretName == "" {
writeJSON(w, http.StatusOK, map[string]string{"message": "no secret env var set"})
return
}
agentURL := os.Getenv("SECRETS_MANAGER_AGENT_URL")
if agentURL == "" {
agentURL = "http://localhost:2773"
}
tokenBytes, err := os.ReadFile("/shared/awssmatoken")
if err != nil {
writeJSON(w, http.StatusInternalServerError,
map[string]string{"error": "failed to read SSRF token: " + err.Error()})
return
}
req, err := http.NewRequestWithContext(
r.Context(),
http.MethodGet,
agentURL+"/secretsmanager/get?secretId="+secretName,
nil,
)
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()})
return
}
req.Header.Set("X-Aws-Parameters-Secrets-Token", string(tokenBytes))
resp, err := http.DefaultClient.Do(req)
if err != nil {
writeJSON(w, http.StatusBadGateway, map[string]string{
"error": "failed to reach secrets manager agent sidecar",
"detail": err.Error(),
})
return
}
defer resp.Body.Close()
var secretResp struct {
SecretString string `json:"SecretString"`
}
if err := json.NewDecoder(resp.Body).Decode(&secretResp); err != nil {
writeJSON(w, http.StatusInternalServerError,
map[string]string{"error": "failed to decode secret response"})
return
}
var secretData map[string]string
if err := json.Unmarshal([]byte(secretResp.SecretString), &secretData); err != nil {
writeJSON(w, http.StatusOK, map[string]string{"secret_value": secretResp.SecretString})
return
}
writeJSON(w, http.StatusOK, secretData)
}
Wichtige Details zur Umsetzung:
- Geheime Namen stammen aus Umgebungsvariablen und sind niemals fest codiert
- Das SSRF-Token wird aus dem gemeinsam genutzten In-Memory-Volume gelesen
- Der Agent gibt eine JSON-Nutzlast zurück, die „SecretString“ enthält, die von der App ausgewertet wird
Schritt 3: Kubernetes-Deployment mit einem Sidecar-Container
Bei dieser Bereitstellung werden zwei Container in einem einzigen Pod ausgeführt, die sich ein In-Memory-Volume für das SSRF-Token teilen.
apiVersion: apps/v1
kind: Deployment
metadata:
name: go-demo
spec:
replicas: 1
selector:
matchLabels:
app: go-demo
template:
metadata:
labels:
app: go-demo
spec:
serviceAccountName: go-demo
containers:
- name: go-demo
image: <account>.dkr.ecr.<region>.amazonaws.com/go-demo:latest
ports:
- containerPort: 8080
env:
- name: secret
value: "demo-secret"
volumeMounts:
- name: ssrf-token
mountPath: /shared
readOnly: true
- name: secrets-manager-agent
image: <account>.dkr.ecr.<region>.amazonaws.com/aws-secrets-manager-agent:latest
ports:
- containerPort: 2773
volumeMounts:
- name: ssrf-token
mountPath: /shared
volumes:
- name: ssrf-token
emptyDir:
medium: Memory
Wichtige Hinweise:
- Das „emptyDir“-Volume nutzt das Medium „Memory“, sodass das Token niemals auf die Festplatte geschrieben wird
- Die Anwendung bindet das Volume schreibgeschützt ein
- Der Sidecar wird im Lese- und Schreibmodus gemountet, um das Token zu generieren
Vermeiden Sie es, das freigegebene Volume unter /var/run einzuhängen. Kubernetes benötigt diesen Pfad für das Token des Dienstkontos, und eine Überschreibung führt zu Fehlern bei den Pods – insbesondere bei distroless-Images.
Verhalten bei der SSRF-Token-Validierung
Der Agent gleicht das SSRF-Token mit einer dieser Umgebungsvariablen ab:
- AWS_TOKEN
- AWS_SESSION_TOKEN
- AWS_CONTAINER_AUTHORIZATION_TOKEN
Wenn keine festgelegt sind, kann der Agent nicht gestartet werden.
Deshalb schreibt das Startskript das Token sowohl auf die Festplatte als auch exportiert es als AWS_TOKEN.
So funktioniert der SSRF-Token-Fluss
- Pod wird gestartet und der Sidecar führt die Datei „startup.sh“ aus
- Es wird ein zufälliges 32-Byte-Token generiert
- Das Token wird in /shared/awssmatoken geschrieben und als AWS_TOKEN exportiert
- Der Agent gleicht eingehende Anfragen mit dem Token ab
- Die Anwendung liest das Token aus und sendet es als HTTP-Header
- Der Agent überprüft das Token und gibt das Geheimnis zurück
Das Token ist kurzlebig, auf die Lebensdauer des Pods beschränkt und wird niemals extern gespeichert.
Zusammenfassung
Durch den Einsatz des AWS Secrets Manager-Agenten als Kubernetes-Sidecar bleibt der Anwendungscode übersichtlich und auf das Wesentliche konzentriert. Anstatt Cloud-SDKs einzubinden, Anmeldedaten zu verwalten und Caching-Logik zu implementieren, führt die Anwendung lediglich einen einfachen lokalen HTTP-Aufruf durch.
Der Makler kümmert sich um:
- Verstecktes Zwischenspeichern mit TTL
- Authentifizierung über IRSA
- SSRF-Schutz von Haus aus
Wichtigste Erkenntnisse:
- Binde freigegebene Volumes nicht unter /var/run ein
- Exportiere das SSRF-Token für den Agenten stets als Umgebungsvariable
- Erstellen Sie den Agenten für Ihre Zielarchitektur (arm64 für Graviton-Knoten)
- Geheime Namen aus Umgebungsvariablen beziehen – keine fest codierten Werte
Dieser auf Sidecars basierende Ansatz bietet eine sichere, wartungsfreundliche und portable Möglichkeit, Geheimnisse in Kubernetes-Umgebungen zu verwalten.
Weitere Blogbeiträge findest duhier.
