Skip to Content
ReferenceSecret Proxying

Secret Proxying

iron-proxy can manage credentials on behalf of your workloads. Instead of giving containers or CI jobs direct access to API keys, iron-proxy holds the real values and applies them to outbound requests at the network edge. If a workload is compromised, there are no real secrets to steal.

Secret proxying is configured as a transform in your iron-proxy YAML config.

How It Works

Each secret entry in your configuration has three parts:

  1. Source: where iron-proxy reads the real credential from (an environment variable, AWS Secrets Manager, etc.).
  2. Mode: how the credential gets applied to outbound requests. Either inject (proxy adds the credential unconditionally) or replace (proxy swaps a placeholder token for the real value).
  3. Rules: which destinations the secret applies to, using the same host/method/path matching as allowlist rules.

When a request matches a secret’s rules, iron-proxy applies the credential before forwarding it upstream. The workload never sends or sees the real value.

Inject Mode

In inject mode, iron-proxy unconditionally sets a header or query parameter on every matching request. The workload does not need to know about the credential at all: it makes a plain request, and the proxy adds the authentication before forwarding.

transforms: - name: secrets config: secrets: - source: type: env var: OPENAI_API_KEY inject: header: "Authorization" formatter: "Bearer {{ .Value }}" rules: - host: "api.openai.com" methods: ["POST"] paths: ["/v1/*"]

In this example, every POST to api.openai.com/v1/* gets an Authorization: Bearer <key> header added automatically. The workload never references the key.

Inject Options

You can inject into either a header or a query parameter:

  • header: sets the named header. Use formatter to control the value format. The formatter field is a Go template that receives .Value (the resolved secret) and a base64 helper.
  • query_param: appends the named query parameter with the secret as its value. No formatter is needed.
# Inject as a query parameter inject: query_param: "api_key"

The base64 helper is useful for services that use HTTP Basic authentication. For example, GitHub’s API accepts Basic auth where the password is a personal access token. The base64 helper concatenates its arguments and base64-encodes the result:

# GitHub Basic auth: base64("x-access-token:<token>") inject: header: "Authorization" formatter: 'Basic {{ base64 "x-access-token:" .Value }}'

This produces a header like Authorization: Basic eC1hY2Nlc3MtdG9rZW46Z2hwX2FiYzEyMw==, which GitHub decodes and authenticates as a token-based login.

When to Use Inject Mode

Inject mode is the stronger isolation model. Workloads have zero knowledge of credentials: no environment variables, no tokens, no placeholders. This is ideal when:

  • You control the workload’s code and it does not need to reference the credential directly.
  • You want to prevent any possibility of credential leakage from the workload’s process environment.
  • The upstream API accepts credentials in a single header or query parameter.

Replace Mode

In replace mode, the workload sends a proxy token: an opaque placeholder string that is meaningless outside iron-proxy. The proxy scans outbound requests for this token and swaps it with the real credential before forwarding upstream.

transforms: - name: secrets config: secrets: - source: type: aws_sm secret_id: "arn:aws:secretsmanager:us-west-1:123456789:secret:anthropic-key" region: "us-west-1" ttl: 5m replace: proxy_value: "pk-proxy-anthropic-xyz" match_headers: ["x-api-key"] rules: - host: "api.anthropic.com"

In this example, the workload sends x-api-key: pk-proxy-anthropic-xyz. iron-proxy replaces pk-proxy-anthropic-xyz with the real key from AWS Secrets Manager before the request reaches Anthropic.

Replace Options

  • proxy_value (required): the placeholder token the workload uses. Choose something clearly identifiable (e.g., pk-proxy-<service>-<random>).
  • match_headers: list of header names to scan for the token. An empty list scans all headers.
  • match_body: when true, the proxy also scans the request body for the token.

Query parameters are always scanned for proxy tokens regardless of configuration.

When to Use Replace Mode

Replace mode is useful when:

  • You cannot modify the workload’s code, but you can control its environment variables or configuration files.
  • The workload or its libraries expect a credential to be present in the environment and include it in requests automatically (e.g., git reading GITHUB_TOKEN).
  • You need the proxy token as a stable reference that survives secret rotation: the real value changes, but the proxy token stays the same.

The require Flag

Replace mode supports a require flag on the replace block. When set to true, requests matching a configured rule are rejected with HTTP 403 if the proxy token is not present in any scanned location. This prevents workloads from bypassing secret management by supplying their own credentials.

- source: type: env var: ANTHROPIC_API_KEY replace: proxy_value: "pk-proxy-anthropic" match_headers: ["x-api-key"] require: true rules: - host: "api.anthropic.com"

Secret Sources

iron-proxy supports three backends for reading secret values: environment variables, AWS Secrets Manager, and AWS Systems Manager Parameter Store. Each source is declared as an object on the source field with a type discriminator.

Environment Variable (env)

Reads the secret from an environment variable on the iron-proxy process. This is the simplest option: set the variable when starting iron-proxy, and the proxy reads it at startup.

source: type: env var: MY_API_KEY

The value is read once at startup and held in memory. To rotate the secret, restart iron-proxy with the new value.

AWS Secrets Manager (aws_sm)

Reads the secret from AWS Secrets Manager. The value is cached locally and refreshed on a TTL, so you can rotate secrets in Secrets Manager without restarting iron-proxy.

source: type: aws_sm secret_id: "arn:aws:secretsmanager:us-west-1:123456789:secret:my-key" region: "us-west-1" json_key: "api_key" ttl: 15m
FieldTypeDefaultDescription
secret_idstringrequiredSecret ARN or name in AWS Secrets Manager.
regionstringAWS SDK defaultAWS region where the secret is stored.
json_keystringWhen set, parse the fetched value as JSON and extract this field.
ttlduration0Re-fetch interval. Set to 0 to read the value once at startup.

AWS Systems Manager Parameter Store (aws_ssm)

Reads the secret from AWS Systems Manager Parameter Store. Parameter Store is a lower-cost alternative to Secrets Manager and is a good fit for teams already storing application configuration there.

source: type: aws_ssm name: "/myapp/api-key" region: "us-east-1" with_decryption: true json_key: "api_key" ttl: 15m
FieldTypeDefaultDescription
namestringrequiredParameter name or ARN.
regionstringAWS SDK defaultAWS region where the parameter is stored.
with_decryptionbooleantrueDecrypt SecureString parameters. Leave enabled for encrypted parameters.
json_keystringWhen set, parse the fetched value as JSON and extract this field.
ttlduration0Re-fetch interval. Set to 0 to read the value once at startup.

iron-proxy uses the standard AWS credential chain (environment variables, instance profile, ECS task role, etc.) to authenticate with both aws_sm and aws_ssm. No additional AWS configuration is needed in the iron-proxy config file.

Scoping Secrets With Rules

Every secret entry supports a rules array that restricts which requests the secret applies to. Rules use the same format as allowlist rules: host globs, methods, and path patterns.

rules: - host: "api.openai.com" methods: ["POST"] paths: ["/v1/*"]

If no rules are specified, the secret applies to all requests passing through the proxy. Always scope secrets to the narrowest set of destinations possible to limit the blast radius of a misconfiguration.

Combining Multiple Secrets

You can define multiple secret entries in a single secrets transform. Each entry is evaluated independently: a request can match zero, one, or many secrets.

transforms: - name: secrets config: secrets: - source: type: env var: OPENAI_API_KEY inject: header: "Authorization" formatter: "Bearer {{ .Value }}" rules: - host: "api.openai.com" - source: type: aws_sm secret_id: "arn:aws:secretsmanager:us-west-1:123456789:secret:anthropic-key" region: "us-west-1" ttl: 15m replace: proxy_value: "pk-proxy-anthropic" match_headers: ["x-api-key"] rules: - host: "api.anthropic.com"

You can mix inject and replace mode secrets freely within the same transform.

Last updated on