Static Secrets
The secrets transform applies a fixed credential (API key, bearer token, basic-auth password) to outbound requests so the workload never holds the real value.
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/*"]This adds Authorization: Bearer $OPENAI_API_KEY to every POST to api.openai.com/v1/*. The workload sends a plain request; iron-proxy reads OPENAI_API_KEY from its environment and attaches the header on the way out.
Use secrets when the upstream accepts a static credential. For other patterns, see OAuth2 Token Injection, HMAC Request Signing, AWS Request Signing, or GCP Service Accounts.
How It Works
Each secret entry has three parts:
- Source. Where iron-proxy reads the credential value from. An environment variable, AWS Secrets Manager, 1Password Connect, and so on.
- Mode. How the credential gets applied. Inject adds it unconditionally. Replace swaps a placeholder token in the workload's request for the real value.
- Rules. Which destinations the secret applies to. Same host/method/path matching as allowlist rules.
When a request matches a secret's rules, iron-proxy applies the credential before forwarding upstream.
Inject Mode
iron-proxy sets a header or query parameter on every matching request. The workload doesn't carry a credential at all: it sends a plain request and the proxy adds the authentication on the way out. The opening example above is in inject mode.
Inject Options
You can inject into either a header or a query parameter:
header: sets the named header. The header name is sent upstream with the exact casing written here (HTTP/1.x only: HTTP/2 lowercases header names regardless). 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 no knowledge of credentials at all: no environment variables, no tokens, no placeholders. Reach for it when:
- You control the workload's code and it doesn't need to reference the credential directly.
- You want to rule out credential leakage from the workload's process environment.
- The upstream API takes credentials in a single header or query parameter.
Replace Mode
The workload sends a proxy token: an opaque placeholder that means nothing outside iron-proxy. The proxy scans outbound requests for this token and swaps it for the real credential.
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"The workload sends x-api-key: pk-proxy-anthropic-xyz. iron-proxy swaps the placeholder for 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. Each entry is either a literal header name (case-insensitive) or a/regex/pattern. Literal and regex entries can be mixed in the same list. Headers are forwarded upstream with the exact casing written here (HTTP/1.x only: HTTP/2 lowercases header names regardless).match_body: whentrue, the proxy also scans the request body for the token.match_path: whentrue, the proxy scansreq.URL.Pathfor the token. Off by default because URL paths often land in access logs on either side of the proxy.match_query: whentrue, the proxy scans the URL query string for the token. Off by default for the same reason asmatch_path: query strings frequently appear in access logs.
Path-Embedded Tokens
Some upstreams put the credential in the URL path. Telegram is the canonical example: clients call /bot<TOKEN>/sendMessage. Set match_path: true to swap a placeholder token in the path:
- source:
type: env
var: TELEGRAM_BOT_TOKEN
replace:
proxy_value: "proxy-tg-token-123"
match_headers: []
match_path: true
require: true
rules:
- host: "api.telegram.org"Inject mode doesn't extend to paths. There's no anchor for the proxy to insert at, so the secret would either duplicate or overwrite the client's path.
When to Use Replace Mode
Reach for replace mode when:
- You can't modify the workload's code, but you can set its environment variables or configuration.
- The workload or its libraries expect a credential in the environment and include it in requests automatically.
gitreadingGITHUB_TOKENis the textbook case. - You want a stable proxy token that survives secret rotation. The real value changes; the proxy token stays the same.
The require Flag
Replace mode supports a require flag. When true, iron-proxy rejects matching requests with HTTP 403 if the proxy token is missing from every scanned location. This stops workloads from bypassing the proxy with 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 reads secret values from one of five backends: environment variables, AWS Secrets Manager, AWS Systems Manager Parameter Store, 1Password Connect, and 1Password. Each source is an object on the source field, discriminated by type.
For 1Password deployments, prefer 1password_connect over 1password. The hosted 1Password SDK is rate-limited per account and can stall request handling under load. 1Password Connect runs in your own infrastructure and isn't subject to those limits.
Every source supports an optional json_key field. When set, iron-proxy parses the value as JSON and extracts the named field. Use this to pack multiple credentials into a single JSON secret.
Environment Variable (env)
Reads the secret from an environment variable on the iron-proxy process. 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. Restart iron-proxy with a new value to rotate.
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. A lower-cost alternative to Secrets Manager. 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 authenticates to both aws_sm and aws_ssm through the standard AWS credential chain: environment variables, instance profile, ECS task role, and so on. No AWS configuration goes in the iron-proxy config file. The resolved identity must be authorized to read each referenced secret. See Required AWS Permissions below.
1Password (1password)
Reads the secret from 1Password using a service account token. Values are cached locally and refreshed on a TTL. Prefer 1password_connect (below) where possible. The hosted SDK is rate-limited per account and can become a bottleneck under load.
source:
type: 1password
secret_ref: "op://Engineering/OpenAI/credential"
token_env: OP_SERVICE_ACCOUNT_TOKEN
ttl: 15m| Field | Type | Default | Description |
|---|---|---|---|
secret_ref | string | required | 1Password reference using the op://vault/item/[section/]field syntax. |
token_env | string | OP_SERVICE_ACCOUNT_TOKEN | Environment variable holding the 1Password service account token. |
ttl | duration | 0 | Re-fetch interval. Set to 0 to read the value once at startup. |
The 1Password SDK loads a Wasm module on each client construction. iron-proxy reuses one client per token_env value across all entries that reference it.
1Password Connect (1password_connect)
Reads the secret from a 1Password Connect server you run. The recommended 1Password integration: Connect runs locally and isn't subject to the per-account rate limits that throttle the hosted SDK. Values are cached locally and refreshed on a TTL.
source:
type: 1password_connect
secret_ref: "op://Engineering/OpenAI/credential"
host_env: OP_CONNECT_HOST
token_env: OP_CONNECT_TOKEN
ttl: 15m| Field | Type | Default | Description |
|---|---|---|---|
secret_ref | string | required | 1Password reference using the op://vault/item/[section/]field syntax. |
host_env | string | OP_CONNECT_HOST | Environment variable holding the Connect server URL. |
token_env | string | OP_CONNECT_TOKEN | Environment variable holding the Connect API token. |
ttl | duration | 0 | Re-fetch interval. Set to 0 to read the value once at startup. |
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, path patterns.
rules:
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/*"]If you omit rules, the secret applies to every request through the proxy. Always scope secrets to the narrowest set of destinations that work, so a misconfiguration leaks as little as possible.
Combining Multiple Secrets
One secrets transform can hold many entries. Each one is evaluated independently. A request can match zero, one, or several.
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 in the same transform.
Required AWS Permissions
The aws_sm and aws_ssm sources call AWS at runtime, so the proxy host needs an AWS identity authorized to read each referenced secret. Without that identity, the proxy starts but every fetch fails. Configure permissions on the AWS side, not in the iron-proxy config.
For Secrets Manager (aws_sm), attach a policy that grants secretsmanager:GetSecretValue (and kms:Decrypt if the secret is encrypted with a customer-managed KMS key) on each secret ARN the proxy will read:
{
"Effect": "Allow",
"Action": ["secretsmanager:GetSecretValue"],
"Resource": [
"arn:aws:secretsmanager:us-west-1:123456789:secret:openai-key-*",
"arn:aws:secretsmanager:us-west-1:123456789:secret:anthropic-key-*"
]
}For Parameter Store (aws_ssm), grant ssm:GetParameter on each parameter, plus kms:Decrypt when reading SecureString parameters with a customer-managed key:
{
"Effect": "Allow",
"Action": ["ssm:GetParameter"],
"Resource": ["arn:aws:ssm:us-east-1:123456789:parameter/myapp/*"]
}Attach the policy to whatever identity the proxy host runs under: an EC2 instance profile, an ECS task role, an EKS IRSA service account, or an IAM user whose access keys are exported in the environment. Scope the resource ARNs to exactly the secrets the proxy needs. A fleet-wide Resource: "*" defeats the point of running a proxy in front of credentials.
When the same secret is served by many proxies, give each proxy host its own role rather than sharing one. Per-host roles let you revoke a single compromised host without rotating every secret it could reach, and CloudTrail attributes the read to the right caller for audit.
Related
- OAuth2 Token Injection: mint bearers from refresh tokens, client credentials, or JWT assertions.
- HMAC Request Signing: sign requests with HMAC instead of attaching a static credential.
- Secret Policies API: manage
secretsentries through the control plane instead of YAML. - Configuration reference: the canonical schema for the
secretstransform.