Summary
The agentgateway inference-gateway currently defaults open to the internet: when allowedSourceRanges is unset/empty, no loadBalancerSourceRanges is rendered and the LoadBalancer accepts 0.0.0.0/0. aicr bundle only prints a non-blocking warning. Apply least-privilege: make the gateway private by default — never emit a publicly-open gateway unless the operator explicitly opts in.
Motivation
An inference endpoint open to the whole internet by default is a real exposure (flagged on multiple clusters). The endpoint is plaintext HTTP and unauthenticated. Defaulting to private aligns with the project's existing "fail closed, don't Warn; continue" principle, while keeping bundles deployable.
Proposed behavior
- When
agentgateway is enabled and allowedSourceRanges is empty/unset, aicr bundle injects the private RFC1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) into the generated Service's loadBalancerSourceRanges and records a bundle note. The deployed gateway is reachable from inside the cluster/VPC but denied to the public internet — it is never emitted open to 0.0.0.0/0.
- Opening to specific clients (e.g. a corporate VPN, which egresses from a public IP not covered by the RFC1918 default) is via the existing list-aware flag:
--set-json agentgateway:allowedSourceRanges='["<cidr>"]' (or a recipe componentRef override).
- Explicit public opt-in is still allowed but must be deliberate and is logged loudly:
--set-json agentgateway:allowedSourceRanges='["0.0.0.0/0"]'.
- Validate CIDRs so a bundle that generates does not later fail Kubernetes 1.36+ strict
loadBalancerSourceRanges validation at Service apply time: require canonical form (no host bits, e.g. reject 1.2.3.4/24; no leading-zero prefix, e.g. reject 1.2.3.0/024) and reject IPv4-mapped IPv6 prefixes (e.g. ::ffff:192.12.2.0/120). Continue to reject the bare-string --set footgun.
Why private-by-default (RFC1918) rather than fail-closed-error or deny-all
Kubernetes has no native deny-all for a LoadBalancer — an empty loadBalancerSourceRanges means allow-all. So "deny by default" must be expressed as a real list. Three options were considered:
| Option |
No-flag behavior |
Trade-off |
| Fail-closed (error) |
bundle refuses to generate |
breaks existing inference pipelines; nothing deployable |
Deny-all sentinel (255.255.255.255/32) |
deployed but reachable by nobody |
gateway inert until scoped; "too limited" |
| RFC1918 private (chosen) |
deployed, private-only |
denies public internet but not in-VPC; corp VPN still needs the flag |
RFC1918 is "secure but not too limited": no public exposure by default, gateway immediately usable inside the cluster/VPC, and validate --phase deployment/conformance pass with no flags. It's generic (not a customer-specific network), so it doesn't firewall every deployment to one site. An internal-LB-by-default scheme (per-cloud annotations) is a stronger but larger cross-cloud change — proposed as a follow-up --internal option, not this issue.
Note: the chosen default is a named constant, so flipping to a deny-all sentinel later is a one-line change.
Compatibility
Default change (open → private). No-flag inference bundles still generate and deploy (now private instead of public), so existing pipelines are not broken. Gate behind a release note.
Implementation
Implemented in PR #1450. The default is applied at bundle time by resolveAgentgatewayExposure (a named constant agentgatewayDefaultSourceRanges), with canonical + 4-in-6 CIDR validation in netutil.IsValidCIDR. Verified end-to-end on a live EKS cluster: both the no-flag (private RFC1918) and --set-json (scoped) deployments pass validate --phase deployment and --phase conformance (including the inference-gateway conformance check).
Type of Change
- New feature / security hardening
Summary
The
agentgatewayinference-gateway currently defaults open to the internet: whenallowedSourceRangesis unset/empty, noloadBalancerSourceRangesis rendered and the LoadBalancer accepts0.0.0.0/0.aicr bundleonly prints a non-blocking warning. Apply least-privilege: make the gateway private by default — never emit a publicly-open gateway unless the operator explicitly opts in.Motivation
An inference endpoint open to the whole internet by default is a real exposure (flagged on multiple clusters). The endpoint is plaintext HTTP and unauthenticated. Defaulting to private aligns with the project's existing "fail closed, don't
Warn; continue" principle, while keeping bundles deployable.Proposed behavior
agentgatewayis enabled andallowedSourceRangesis empty/unset,aicr bundleinjects the private RFC1918 ranges (10.0.0.0/8,172.16.0.0/12,192.168.0.0/16) into the generated Service'sloadBalancerSourceRangesand records a bundle note. The deployed gateway is reachable from inside the cluster/VPC but denied to the public internet — it is never emitted open to0.0.0.0/0.--set-json agentgateway:allowedSourceRanges='["<cidr>"]'(or a recipecomponentRefoverride).--set-json agentgateway:allowedSourceRanges='["0.0.0.0/0"]'.loadBalancerSourceRangesvalidation at Service apply time: require canonical form (no host bits, e.g. reject1.2.3.4/24; no leading-zero prefix, e.g. reject1.2.3.0/024) and reject IPv4-mapped IPv6 prefixes (e.g.::ffff:192.12.2.0/120). Continue to reject the bare-string--setfootgun.Why private-by-default (RFC1918) rather than fail-closed-error or deny-all
Kubernetes has no native deny-all for a LoadBalancer — an empty
loadBalancerSourceRangesmeans allow-all. So "deny by default" must be expressed as a real list. Three options were considered:255.255.255.255/32)RFC1918 is "secure but not too limited": no public exposure by default, gateway immediately usable inside the cluster/VPC, and
validate --phase deployment/conformance pass with no flags. It's generic (not a customer-specific network), so it doesn't firewall every deployment to one site. An internal-LB-by-default scheme (per-cloud annotations) is a stronger but larger cross-cloud change — proposed as a follow-up--internaloption, not this issue.Compatibility
Default change (open → private). No-flag inference bundles still generate and deploy (now private instead of public), so existing pipelines are not broken. Gate behind a release note.
Implementation
Implemented in PR #1450. The default is applied at bundle time by
resolveAgentgatewayExposure(a named constantagentgatewayDefaultSourceRanges), with canonical + 4-in-6 CIDR validation innetutil.IsValidCIDR. Verified end-to-end on a live EKS cluster: both the no-flag (private RFC1918) and--set-json(scoped) deployments passvalidate --phase deploymentand--phase conformance(including theinference-gatewayconformance check).Type of Change