Skip to content

feat(bundler): private-by-default agentgateway exposure (deny public, open via --set-json) #1373

Description

@yuanchen8911

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions