Configuration
iron-proxy is configured via a single YAML file, passed at startup with the -config flag:
iron-proxy -config /etc/iron-proxy/proxy.yamlBelow is a complete reference for every configuration option.
Full Example
dns:
listen: ":53"
proxy_ip: "172.20.0.2"
upstream_resolver: "8.8.8.8:53"
passthrough:
- "*.internal.corp"
records:
- name: "custom.local"
type: A
value: "10.0.0.5"
proxy:
http_listen: ":80"
https_listen: ":443"
tunnel_listen: ":1080"
max_request_body_bytes: 1048576
max_response_body_bytes: 0
tls:
mode: "mitm"
ca_cert: "/certs/ca.crt"
ca_key: "/certs/ca.key"
cert_cache_size: 1000
leaf_cert_expiry_hours: 72
transforms:
- name: allowlist
config:
domains:
- "registry.npmjs.org"
cidrs:
- "10.0.0.0/8"
rules:
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/*"]
- host: "*.anthropic.com"
methods: ["POST"]
paths: ["/v1/messages", "/v1/complete"]
- 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/*"]
- 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"]
require: true
rules:
- host: "api.anthropic.com"
- source:
type: aws_ssm
name: "/myapp/openai-key"
region: "us-east-1"
ttl: 15m
replace:
proxy_value: "pk-proxy-openai-param"
match_headers: ["Authorization"]
rules:
- host: "api.openai.com"
- name: annotate
config:
annotations:
- rules:
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/*"]
headers: ["x-request-id"]
- name: grpc
config:
name: "policy-engine"
target: "localhost:9500"
send_request_body: true
send_response_body: true
rules:
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/*"]
tls:
enabled: true
ca_cert: "/etc/iron-proxy/grpc-ca.pem"
cert: "/etc/iron-proxy/grpc-client.pem"
key: "/etc/iron-proxy/grpc-client-key.pem"
metrics:
listen: ":9090"
log:
level: "info"dns
Configures the built-in DNS server. The DNS server returns proxy_ip for all lookups so that outbound traffic routes through the proxy.
| Field | Type | Default | Description |
|---|---|---|---|
listen | string | ":53" | Address and port the DNS server binds to. |
proxy_ip | string | required | IP address where iron-proxy is running. All DNS responses resolve to this IP. |
upstream_resolver | string | OS default | Upstream DNS resolver address (e.g., "8.8.8.8:53"). When set, both passthrough DNS queries and upstream HTTP connections resolve via this server instead of the OS default. Useful when iron-proxy owns the system DNS. |
passthrough | string[] | [] | Domain glob patterns that are forwarded to the upstream resolver instead of being intercepted. Useful for internal DNS names that should not route through the proxy. |
records | object[] | [] | Static DNS records. These take precedence over interception and passthrough. See below. |
dns.records[]
| Field | Type | Description |
|---|---|---|
name | string | Domain name for the record. |
type | string | Record type: A or CNAME. |
value | string | IP address (for A records) or target hostname (for CNAME records). |
proxy
Configures the HTTP/HTTPS proxy listeners.
| Field | Type | Default | Description |
|---|---|---|---|
http_listen | string | ":80" | Address and port for HTTP traffic. |
https_listen | string | ":443" | Address and port for HTTPS traffic. |
tunnel_listen | string | (disabled) | Address and port for the CONNECT/SOCKS5 tunnel listener. Accepts both HTTP CONNECT and SOCKS5 requests. See the SOCKS5 and CONNECT tunnels guide for details. |
max_request_body_bytes | integer | 1048576 (1 MiB) | Maximum request body size that the proxy will buffer. Bodies are only buffered when a transform needs to inspect them. |
max_response_body_bytes | integer | 0 (unlimited) | Maximum response body size that the proxy will buffer. Set to 0 to disable the limit. |
tls
Configures how iron-proxy handles HTTPS traffic. Two modes are supported: mitm (the default), which terminates TLS using a CA you provide and mints leaf certificates on the fly, and sni-only, which passes TLS through without termination.
| Field | Type | Default | Description |
|---|---|---|---|
mode | string | "mitm" | TLS handling mode. One of mitm or sni-only. See TLS modes below. |
ca_cert | string | required for mitm | Path to the CA certificate file (PEM format). Not used in sni-only mode. |
ca_key | string | required for mitm | Path to the CA private key file (PEM format). Not used in sni-only mode. |
cert_cache_size | integer | 1000 | Number of generated leaf certificates to keep in the LRU cache. Not used in sni-only mode. |
leaf_cert_expiry_hours | integer | 72 | Validity duration (in hours) for generated leaf certificates. Not used in sni-only mode. |
TLS Modes
mitm (default): iron-proxy terminates the client TLS connection, inspects the decrypted request, and opens a new TLS connection to the upstream server. Clients must trust iron-proxy’s CA certificate. This is the only mode that lets transforms see request methods, paths, headers, and bodies.
sni-only: iron-proxy peeks at the TLS ClientHello SNI and TCP-passthroughs the connection to the upstream without terminating TLS. Clients do not need to trust a proxy CA. The transform pipeline still runs with a host-only synthetic request: method, path, headers, and body are empty, so host-based allowlist rules are the only things that can match. Body-inspecting transforms like secrets and grpc still run but have nothing to act on. The CONNECT/SOCKS5 tunnel’s TLS branch also switches to passthrough in sni-only mode.
tls:
mode: "sni-only"Use sni-only when you need host-level egress control but cannot distribute a CA certificate to workloads. Use mitm when you need secret injection, body inspection, or method/path allowlists.
transforms
An ordered array of transforms that run on every request. All transforms must pass for the request to be forwarded upstream. Transforms execute in the order they appear in the configuration.
Each transform has a name and a config object. The available transforms are documented below.
allowlist
Controls which destinations are reachable through the proxy. Requests to destinations not in the allowlist receive an HTTP 403 response.
There are two ways to specify allowed destinations: flat lists (domains and cidrs) that allow all methods and paths, and rules that support method and path restrictions. Both can be used together in the same allowlist.
| Field | Type | Default | Description |
|---|---|---|---|
domains | string[] | [] | Hostname glob patterns to allow (e.g., registry.npmjs.org, *.anthropic.com). All methods and paths are permitted. |
cidrs | string[] | [] | CIDR ranges to allow (e.g., 10.0.0.0/8). All methods and paths are permitted. |
rules | object[] | [] | Rules with optional method and path restrictions. See below. |
warn | boolean | false | When true, violations are logged but not blocked. Useful for rolling out allowlists incrementally. |
allowlist.rules[]
Each rule matches a single host or CIDR, with optional method and path filters. A request is allowed if it matches any rule (or any flat domains/cidrs entry).
| Field | Type | Default | Description |
|---|---|---|---|
host | string | Hostname glob pattern. Mutually exclusive with cidr. | |
cidr | string | CIDR range. Mutually exclusive with host. | |
methods | string[] | all | HTTP methods to allow (e.g., ["GET", "POST"]). Omit or set to ["*"] to allow all methods. |
paths | string[] | all | Path patterns to allow (e.g., ["/v1/*"]). Must start with /. Supports * wildcards. Omit to allow all paths. |
secrets
Injects or replaces secret values at the egress boundary so that real credentials are never exposed to sandboxed workloads. Each secret declares its own source and either an inject or replace block.
| Field | Type | Default | Description |
|---|---|---|---|
secrets | object[] | [] | List of secret entries. See below. |
secrets.secrets[]
| Field | Type | Default | Description |
|---|---|---|---|
source | object | required | Where to read the secret value. Contains a type field (env, aws_sm, or aws_ssm) plus type-specific fields. See sources below. |
inject | object | Inject the secret onto matching requests unconditionally. See inject mode. Mutually exclusive with replace. | |
replace | object | Replace a proxy token with the real value. See replace mode. Mutually exclusive with inject. | |
rules | object[] | [] | Restrict this secret to specific destinations. Uses the same format as allowlist.rules[]. If empty, the secret applies to all destinations. |
Secret Sources
env
Read the secret from an environment variable on the iron-proxy process.
| Field | Type | Description |
|---|---|---|
type | string | Must be env. |
var | string | Environment variable name containing the real secret value. |
source:
type: env
var: OPENAI_API_KEYaws_sm
Read the secret from AWS Secrets Manager. The value is cached and refreshed in the background based on the configured TTL.
| Field | Type | Default | Description |
|---|---|---|---|
type | string | Must be aws_sm. | |
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 (no refresh) | Re-fetch interval. Set to 0 to read the value once at startup. |
source:
type: aws_sm
secret_id: "arn:aws:secretsmanager:us-west-1:123456789:secret:my-key"
region: "us-west-1"
ttl: 10maws_ssm
Read the secret from AWS Systems Manager Parameter Store. Like aws_sm, values are cached and refreshed in the background based on the TTL.
| Field | Type | Default | Description |
|---|---|---|---|
type | string | Must be aws_ssm. | |
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. |
json_key | string | When set, parse the fetched value as JSON and extract this field. | |
ttl | duration | 0 (no refresh) | Re-fetch interval. Set to 0 to read the value once at startup. |
source:
type: aws_ssm
name: "/myapp/api-key"
region: "us-east-1"
with_decryption: true
ttl: 15miron-proxy uses the standard AWS credential chain (environment variables, instance profile, ECS task role, etc.) to authenticate with AWS.
Inject Mode
In inject mode, the proxy unconditionally sets a header or query parameter on every request that matches the secret’s rules. The workload never sees or sends any credential. This is useful when sandboxed workloads should have no knowledge of credentials at all.
| Field | Type | Description |
|---|---|---|
header | string | Header name to set on matching requests. Mutually exclusive with query_param. |
query_param | string | Query parameter name to set on matching requests. Mutually exclusive with header. |
formatter | string | Go template for the header value. Receives .Value (the resolved secret) and a base64 helper. Not used with query_param. |
The formatter field supports Go templates. Use {{ .Value }} to insert the raw secret. The base64 helper concatenates and base64-encodes its arguments:
# Set Authorization: Bearer <secret>
inject:
header: "Authorization"
formatter: "Bearer {{ .Value }}"
# Set a query parameter (no formatter needed)
inject:
query_param: "key"Replace Mode
In replace mode, the workload sends a proxy token that the proxy swaps for the real value before forwarding upstream. Query parameters are always scanned for proxy tokens.
| Field | Type | Default | Description |
|---|---|---|---|
proxy_value | string | required | Token that the sandboxed environment sends. The proxy replaces this with the real value before forwarding. |
match_headers | string[] | [] | Header names to scan for the proxy token. An empty list scans all headers. |
match_body | boolean | false | When true, scan the request body for the proxy token. |
require | boolean | false | When true, requests matching a configured rule are rejected with HTTP 403 if the proxy token is not present in any scanned location. Prevents workloads from bypassing secret management. |
The proxy_value, match_headers, match_body, and require fields may also be set at the top level of a secret entry for backwards compatibility. New configurations should use the replace block.
annotate
Captures HTTP request header values into audit log annotations based on host, method, and path rules. This is useful for enriching audit logs with request-specific context like request IDs or authorization tokens. This transform never rejects requests.
| Field | Type | Default | Description |
|---|---|---|---|
annotations | object[] | [] | List of annotation groups. See below. |
annotate.annotations[]
| Field | Type | Description |
|---|---|---|
rules | object[] | Rules to match requests against. Uses the same format as allowlist.rules[]. |
headers | string[] | Header names to capture from matching requests. Values are written as header:<Name> entries in the transform trace. |
- name: annotate
config:
annotations:
- rules:
- host: "api.openai.com"
methods: ["POST"]
paths: ["/v1/*"]
headers: ["x-request-id", "authorization"]Example audit log annotation output:
{
"header:X-Request-Id": "req-abc123",
"header:Authorization": "Bearer sk-ant-..."
}grpc
Delegates request and response processing to an external gRPC server implementing the TransformService API. You can define multiple grpc transforms to pipeline through several servers.
| Field | Type | Default | Description |
|---|---|---|---|
name | string | required | Identifier for this transform, used in logs and error messages. |
target | string | required | gRPC server address (e.g., localhost:9500). |
send_request_body | boolean | false | When true, forward the full request body to the gRPC server. When false, only headers are sent. |
send_response_body | boolean | false | When true, forward the full response body to the gRPC server. When false, only headers are sent. |
rules | object[] | [] | Restrict which requests are forwarded to this server. Uses the same rule format as allowlist.rules[]. If empty, all requests are forwarded. |
tls | object | TLS configuration for the gRPC connection. See below. |
grpc.tls
| Field | Type | Default | Description |
|---|---|---|---|
enabled | boolean | false | Enable TLS for the gRPC connection. When false, the connection uses plaintext. |
ca_cert | string | system default | Path to a custom CA certificate for server verification. |
cert | string | Path to a client certificate for mTLS. Must be set together with key. | |
key | string | Path to the client private key for mTLS. Must be set together with cert. |
metrics
Configures the OpenTelemetry/Prometheus metrics endpoint.
| Field | Type | Default | Description |
|---|---|---|---|
listen | string | ":9090" | Address and port for the metrics endpoint. |
log
Configures logging output.
| Field | Type | Default | Description |
|---|---|---|---|
level | string | "info" | Log verbosity. One of debug, info, warn, or error. |
OpenTelemetry Environment Variables
OTEL log export is configured through environment variables, not the YAML config file. Set OTEL_EXPORTER_OTLP_ENDPOINT to enable it. See the OTEL export guide for usage examples.
| Variable | Type | Default | Description |
|---|---|---|---|
OTEL_EXPORTER_OTLP_ENDPOINT | string | (disabled) | OTLP collector URL (e.g., https://otel-collector.example.com:4318). Export is disabled when unset. |
OTEL_EXPORTER_OTLP_PROTOCOL | string | http/protobuf | Transport protocol: http/protobuf or grpc. |
OTEL_EXPORTER_OTLP_HEADERS | string | (none) | Comma-separated key=value pairs sent as headers on every export request. Typically used for authentication. |
OTEL_SERVICE_NAME | string | iron-proxy | Service name attached to all log records. |
OTEL_RESOURCE_ATTRIBUTES | string | (none) | Comma-separated key=value resource attributes added to all log records (e.g., deployment.environment=staging). |