File size: 10,700 Bytes
7f0ec95 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | # gateway.yaml.example β Claude apps gateway config template, AWS-shaped (walkthrough Β§4).
#
# Okta IdP + Bedrock upstream, following the walkthrough at
# https://code.claude.com/docs/en/claude-apps-gateway-on-aws. The active sections
# below are a strict subset of the full configuration reference at
# https://code.claude.com/docs/en/claude-apps-gateway-config; optional keys are
# included commented-out.
#
# USAGE β this is the shippable TEMPLATE. Copy it to gateway.yaml and fill it in:
# cp gateway.yaml.example gateway.yaml
# setup.sh and terraform/ read gateway.yaml (your filled-in copy, which is
# gitignored). Unlike the GCP example it is NOT published to a secret store:
# the Dockerfile bakes it into the image at /etc/claude/gateway.yaml β the
# container ENTRYPOINT runs `claude gateway --config /etc/claude/gateway.yaml`.
# It holds no secret values; a config edit means an image rebuild (setup.sh
# tags images with a hash of this file, so a re-run rebuilds automatically).
#
# Secret expansion: ${ENV_VAR} reads an env var; ${file:/path} reads a mounted file.
# On ECS, the task definition injects the JWT / OIDC / Postgres secrets as ENV
# VARS via its `secrets` field (valueFrom -> Secrets Manager ARN). On EKS you
# may mount them as files instead and use ${file:/secrets/...}.
#
# BEFORE BUILD β replace every REPLACE_ME placeholder below (setup.sh refuses to
# build the image while any remain β the config is baked in, so a half-filled
# config would ship), and create the referenced secrets:
# gateway-jwt-secret (setup.sh generates this)
# gateway-oidc-client-secret (from the Okta admin console OIDC web app)
# gateway-postgres-url (setup.sh generates this)
# ββ Listener βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
listen:
host: 0.0.0.0
port: 8080 # the target group forwards ALB :443 -> :8080
# Required. Fixes the IdP redirect_uri, the OIDC discovery doc, and the
# gateway-token issuer so none are derived from the client-controlled Host
# header (X-Forwarded-Host/-Proto are likewise never trusted). Set it to the
# internal hostname you picked in the prerequisites β the Route 53 private
# zone name your ACM certificate covers (e.g.
# https://claude-gateway.internal.example.com). Unlike Cloud Run there is no
# first-deploy placeholder dance: you choose the hostname up front, alias it
# to the internal ALB after the deploy, and register the same host's
# /oauth/callback on the Okta app.
public_url: REPLACE_ME
# Register this exact redirect URI on the Okta OIDC web application:
# https://<public_url host>/oauth/callback
#
# Behind the internal ALB every request arrives via the load balancer, so the
# gateway sees ALB-node peer IPs for all developers β set trusted_proxies so
# X-Forwarded-For from those proxies is trusted and per-IP rate limiting /
# audit IPs record the real client. ALB nodes take addresses from the subnets
# the ALB is attached to, so list those subnets' CIDRs (the private subnets
# from the prerequisites).
#
# NOTE: listing the ALB subnets' CIDRs trusts every host in those subnets as a
# proxy β any co-located workload that can reach the ALB can then spoof the
# client IP via X-Forwarded-For (audit logs, per-IP rate limits, IP
# allowlists). Keep the ALB :443 ingress source (CORP_CIDR / corporate_cidr)
# from overlapping these subnets, and don't share the subnets with untrusted
# workloads.
trusted_proxies: [REPLACE_ME] # e.g. [10.0.1.0/24, 10.0.2.0/24]
#
# Alternative β terminate TLS in the gateway itself instead of at the ALB:
# tls:
# cert: /certs/gateway.crt
# key: /certs/gateway.key
# ββ Identity provider β Okta βββββββββββββββββββββββββββββββββββββββββββββββββ
oidc:
issuer: REPLACE_ME # e.g. https://example.okta.com (or your custom auth server URL)
client_id: REPLACE_ME # Okta OIDC web app client ID (not secret)
client_secret: ${OIDC_CLIENT_SECRET} # EKS file mounts: ${file:/secrets/oidc-client-secret}
allowed_email_domains: [REPLACE_ME] # e.g. [example.com] β reject id_tokens outside your org
# The Okta org authorization server returns a thin id_token that omits email
# and groups; the gateway fills them from /userinfo.
userinfo_fallback: true
# offline_access yields refresh tokens (silent renewal + the deprovision
# leash); Okta emits groups only when the `groups` scope is requested AND the
# app's groups claim filter allows them (Okta admin console -> the app's
# Sign On tab -> OpenID Connect ID Token -> Groups claim filter).
scopes: [openid, profile, email, offline_access, groups]
# groups_claim: groups # Okta default. Entra app roles=roles; see the config reference
# ca_cert_pem: ${file:/secrets/idp-ca.pem} # only for an IdP behind a private CA
# ββ Sessions βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
session:
jwt_secret: ${GATEWAY_JWT_SECRET} # >= 32 bytes; openssl rand -base64 32
# Okta issues refresh tokens (offline_access above), so sessions renew
# silently and this mainly bounds deprovision latency. 8 is a sane default;
# lower toward 1 for tighter revocation. Array form rotates keys:
# [new, old] (index 0 signs, all verify).
ttl_hours: 8
# ββ Store (REQUIRED β the gateway refuses to boot without it) βββββββββββββββββ
store:
postgres_url: ${GATEWAY_POSTGRES_URL} # private-subnet RDS; built with ?sslmode=verify-full by setup.sh
# (the image trusts the RDS CA bundle via NODE_EXTRA_CA_CERTS β see Dockerfile)
# ββ Upstreams β Amazon Bedrock βββββββββββββββββββββββββββββββββββββββββββββββ
upstreams:
- provider: bedrock
# Must equal the region you provision in (setup.sh's AWS_REGION /
# terraform's region): the IAM policy's inference-profile ARNs are scoped
# to that region, and Bedrock model access is enabled there (cross-region
# us.anthropic.* profiles need access in every spanned region). NOTE: the
# walkthrough is scoped to US regions β the built-in model catalog maps to
# us.anthropic.* (US-geo) profiles; a non-US region also needs a models:
# list below (see the model catalog section).
region: REPLACE_ME # e.g. us-east-1
auth: {} # AWS default credential chain: ECS task role / IRSA on EKS (preferred β no static keys)
# base_url: https://bedrock-runtime.us-east-1.amazonaws.com # bedrock-runtime interface VPC endpoint, to keep model traffic off the public path
# Add more upstreams for failover (tried topβbottom on 5xx/timeout/501): a
# second region, or an anthropic/vertex fallback. See
# https://code.claude.com/docs/en/claude-apps-gateway.
# ββ Telemetry fan-out (OPTIONAL) βββββββββββββββββββββββββββββββββββββββββββββ
# The CLI sends OTLP/HTTP to the gateway; the gateway fans out, stamping
# user.id/user.email/user.groups server-side. On AWS, point at an OpenTelemetry
# Collector (e.g. the AWS Distro for OpenTelemetry -> CloudWatch / Managed
# Prometheus). When forward_to and public_url are both configured the gateway
# pushes CLAUDE_CODE_ENABLE_TELEMETRY and the OTEL exporter selectors to every
# client automatically β no per-developer config needed.
# telemetry:
# forward_to:
# - url: https://otel-collector.internal.example.com:4318
# headers:
# Authorization: ${file:/secrets/otlp-token}
# metrics: true # safe aggregate counters (default)
# logs: false # carries bash commands / tool inputs β opt in deliberately
# traces: false
# ββ RBAC + managed settings (OPTIONAL; first-match-wins, top -> bottom) βββββββ
# With Okta as IdP, match on the group names the `groups` scope emits (subject
# to the app's groups claim filter), or on email_domain.
# managed:
# policies:
# - match: { groups: [engineering] }
# cli:
# availableModels: [claude-opus-4-8, claude-sonnet-4-6, claude-haiku-4-5]
# permissions: { deny: ["Read(./.env)", "Read(./secrets/**)"] }
# - match: {} # catch-all floor β keep LAST
# cli:
# availableModels: [claude-sonnet-4-6, claude-haiku-4-5]
# ββ Admin API (OPTIONAL β enables db-mode runtime config + spend caps) βββββββ
# admin_groups needs a groups claim β Okta provides one via the `groups` scope
# above β or use the bootstrap keys below instead. Named keys for attribution
# in the audit log; 32-char minimum on key values. On ECS add these to the task
# definition's `secrets` field (valueFrom -> a Secrets Manager ARN), same as the
# JWT/OIDC/Postgres secrets above; on EKS you may use ${file:...}.
# admin:
# write_keys:
# - id: terraform
# key: ${GATEWAY_ADMIN_WRITE_KEY}
# read_keys:
# - id: reporting
# key: ${GATEWAY_ADMIN_READ_KEY}
# # admin_groups: [platform-finops] # Okta group names via the groups scope
# ββ Model catalog (OPTIONAL for US regions) ββββββββββββββββββββββββββββββββββ
# Default true: every built-in Claude model is exposed and auto-translated per
# upstream (the built-in table already maps to us.anthropic.* cross-region
# inference profiles). Set false + a models: list to pin IDs (e.g. an
# application or provisioned-throughput inference-profile ARN).
# NON-US REGIONS: the built-in us.anthropic.* mappings do not exist outside
# the US geo β set auto_include_builtin_models: false and list your region's
# inference profiles (eu.anthropic.*, apac.anthropic.*, ...) here, and widen
# the geo prefix in the deploy's bedrock-invoke IAM policy to match. See the
# models: guidance in the config reference:
# https://code.claude.com/docs/en/claude-apps-gateway-config
# auto_include_builtin_models: true
# models:
# - id: claude-opus-4-8
# label: Claude Opus 4.8
# upstream_model: { bedrock: us.anthropic.claude-opus-4-8 }
|