Blog

Fetching Secrets in Kubernetes with the AWS Secrets Manager Agent Sidecar

29.04.2026
Reading time: 5 mins.
Last Updated: 01.07.2026

Table of Contents

Managing secrets in Kubernetes can sound straightforward-until you actually implement it at scale. Common approaches include mounting Kubernetes secrets as environment variables, using an external secrets operator, or calling cloud SDKs directly from application code. Each option introduces trade-offs around caching, auditing, security boundaries, and application coupling.

In this post, we walk through an alternative pattern: running the AWS Secrets Manager Agent as a sidecar container. With this approach, your application fetches secrets via a local HTTP call-no AWS SDK required-while benefiting from built-in caching, SSRF protection, and clean separation of concerns.

We’ll demonstrate the setup using a small Go service, but this sidecar pattern works with any language or runtime.

The Secrets Manager Agent runs as a separate container in the same pod. Your application communicates with it over localhost:2773, which provides several advantages:

  • No AWS SDK dependency in your app-just a simple HTTP GET request
  • Built-in caching with configurable TTL (secrets refresh automatically without pod restarts)
  • SSRF token protection-only containers with the token can fetch secrets
  • Improved portability-switching secret backends only requires updating the sidecar
  • Better auditing-when paired with OpenTelemetry trace IDs, you can track exactly which endpoint accessed which secret

The trade-offs are an extra container per pod and a local HTTP call. In practice, both introduce negligible overhead.

AWS Secrets Manager Agent

On pod startup, the agent generates a random SSRF token, writes it to a shared in-memory volume, and exports it as an environment variable.
The application container reads the same token from the shared volume and includes it in every request to the agent.

AWS provides the agent as a Rust project on GitHub. We build it from source and pin it to a release tag for reproducibility.

# 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 Configuration (config.toml)

log_level = "INFO"
log_to_file = false
http_port = 2773
ttl_seconds = 300
cache_size = 1000

The ttl_seconds = 300 setting means secrets are cached for five minutes. After that, the agent automatically fetches fresh values-no pod restart required.

Startup Script and SSRF Token Handling

#!/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

The token is ephemeral-generated on every pod start, stored only in memory, and destroyed with the pod. There’s nothing to rotate or persist.

Build and push to ECR (arm64 in this example for Graviton nodes):

docker buildx build --platform linux/arm64 \ -t <account>.dkr.ecr.us-east-1.amazonaws.com/aws-secrets-manager-agent:lates\--push ./secrets-manager-agent

The application does not use the AWS SDK. Instead, it performs an HTTP GET against the local agent and passes the SSRF token in a 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)
}

Key Implementation Details:

  • Secret names come from environment variables, never hardcoded
  • The SSRF token is read from the shared in-memory volume
  • The agent returns a JSON payload containing SecretString, which the app parses

This deployment runs two containers in a single pod, sharing an in-memory volume for the SSRF token.

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

Important Notes:

  • The emptyDir volume uses medium: Memory, so the token is never written to disk
  • The application mounts the volume as read-only
  • The sidecar mounts it read-write to generate the token

Avoid mounting the shared volume at /var/run. Kubernetes needs that path for the service account token, and overriding it will break pods-especially with distroless images.

The agent validates the SSRF token against one of these environment variables:

  • AWS_TOKEN
  • AWS_SESSION_TOKEN
  • AWS_CONTAINER_AUTHORIZATION_TOKEN

If none are set, the agent fails to start.

That’s why the startup script both writes the token to disk and exports it as AWS_TOKEN.

  1. Pod starts and the sidecar runs startup.sh
  2. A random 32-byte token is generated
  3. The token is written to /shared/awssmatoken and exported as AWS_TOKEN
  4. The agent validates incoming requests against the token
  5. The application reads the token and sends it as an HTTP header
  6. The agent verifies the token and returns the secret

The token is ephemeral, scoped to the pod lifetime, and never stored externally.

Using the AWS Secrets Manager Agent as a Kubernetes sidecar keeps application code clean and focused. Instead of embedding cloud SDKs, managing credentials, and implementing caching logic, the application makes a simple local HTTP call.

The agent handles:

  • Secret caching with TTL
  • Authentication via IRSA
  • SSRF protection out of the box

Key Takeaways:

  • Don’t mount shared volumes at /var/run
  • Always export the SSRF token as an environment variable for the agent
  • Build the agent for your target architecture (arm64 for Graviton nodes)
  • Source secret names from environment variables-not hardcoded values

This sidecar-based approach provides a secure, maintainable, and portable way to manage secrets in Kubernetes environments.

Check out more of our blog posts here.

Newsletter for Tech experts

Signal, not noise -

straight to your inbox.

Join 12,000+ engineers and business leaders getting field notes on SRE, DevOps and cloud- native reliability.

Deep-dive tech blogs & case studies
Emerging tech, curated

Your Work Email

We respect your inbox. Read our Privecy Policy

More Posts

Managing secrets securely in Kubernetes is a critical challenge for modern cloud-native environments. Application credentials, certificates, private keys, and passwords must be handled in a way that is secure, auditable,...
Reading
As IPv6 adoption continues to grow, organizations running workloads on AWS increasingly need networking architectures that can support both IPv4 and IPv6 traffic. While AWS provides native IPv6 capabilities, integrating...
Reading
Get In Touch
ITGix provides you with expert consultancy and tailored DevOps services to accelerate your business growth.
Newsletter for
Tech Experts
Join 12,000+ business leaders and engineers who receive blogs, e-Books, and case studies on emerging technology.