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:
- Source: where iron-proxy reads the real credential from (an environment variable, AWS Secrets Manager, etc.).
- 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).
- 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. Useformatterto control the value format. Theformatterfield is a Go template that receives.Value(the resolved secret) and abase64helper.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: whentrue, 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.,
gitreadingGITHUB_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_KEYThe 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| Field | Type | Default | Description |
|---|---|---|---|
secret_id | string | required | Secret ARN or name in AWS Secrets Manager. |
region | string | AWS SDK default | AWS region where the secret is stored. |
json_key | string | When set, parse the fetched value as JSON and extract this field. | |
ttl | duration | 0 | Re-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| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Parameter name or ARN. |
region | string | AWS SDK default | AWS region where the parameter is stored. |
with_decryption | boolean | true | Decrypt SecureString parameters. Leave enabled for encrypted parameters. |
json_key | string | When set, parse the fetched value as JSON and extract this field. | |
ttl | duration | 0 | Re-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.