Skip to content

feat(policy): support host wildcards and multi-port endpoints#366

Merged
johntmyers merged 2 commits intomainfrom
feat/359-host-wildcards-multi-port/jomyers
Mar 16, 2026
Merged

feat(policy): support host wildcards and multi-port endpoints#366
johntmyers merged 2 commits intomainfrom
feat/359-host-wildcards-multi-port/jomyers

Conversation

@johntmyers
Copy link
Collaborator

@johntmyers johntmyers commented Mar 16, 2026

🏗️ build-from-issue-agent

Summary

Add glob-style host wildcards (*.example.com) to endpoints[].host and multi-port support via a ports array field on NetworkEndpoint. Both features follow existing patterns in the policy engine and are backwards compatible with existing policies.

Related Issue

Closes #359

UX Changes

Host Wildcards

You can now use glob patterns in endpoints[].host instead of listing every subdomain individually:

# Before: had to list every subdomain
network_policies:
  anthropic:
    endpoints:
      - { host: api.anthropic.com, port: 443 }
      - { host: statsig.anthropic.com, port: 443 }
      - { host: sentry.anthropic.com, port: 443 }

# After: single wildcard covers all subdomains
network_policies:
  anthropic:
    endpoints:
      - { host: "*.anthropic.com", port: 443 }

Pattern semantics use . as the delimiter:

Pattern Matches Does not match
*.example.com api.example.com, cdn.example.com example.com, deep.sub.example.com
**.example.com api.example.com, deep.sub.example.com example.com
example.com example.com (exact, unchanged) api.example.com

Validation rejects overly broad patterns:

  • Bare * or ** → error (matches all hosts)
  • *com → error (must start with *. or **.)
  • *.com → warning (very broad, covers all subdomains of a TLD)

Multi-Port Endpoints

You can now specify multiple ports on a single endpoint instead of duplicating the block:

# Before: had to duplicate the entire endpoint for each port
network_policies:
  api:
    endpoints:
      - host: api.example.com
        port: 443
        protocol: rest
        tls: terminate
        access: read-only
      - host: api.example.com
        port: 8443
        protocol: rest
        tls: terminate
        access: read-only

# After: single endpoint with multiple ports
network_policies:
  api:
    endpoints:
      - host: api.example.com
        ports: [443, 8443]
        protocol: rest
        tls: terminate
        access: read-only

Backwards compatible — existing port: 443 syntax still works unchanged. Internally it normalizes to ports: [443].

Combined

Both features compose naturally:

network_policies:
  cloud_apis:
    endpoints:
      - host: "*.googleapis.com"
        ports: [443, 8443]
    binaries:
      - { path: "/**" }

Changes

  • proto/sandbox.proto: Added repeated uint32 ports = 9 to NetworkEndpoint, documented host wildcard support on host field
  • crates/openshell-policy/src/lib.rs: Added ports field to YAML serde type, portports normalization in to_proto()/from_proto(), compact serialization (single port uses scalar form)
  • crates/openshell-sandbox/data/sandbox-policy.rego: Split endpoint_allowed into exact/glob host clauses, changed endpoint.portendpoint.ports[_] in all rules, added glob host matching to endpoint_matches_request
  • crates/openshell-sandbox/src/opa.rs: Updated proto_to_opa_data_json() to emit ports array, added normalize_endpoint_ports() to preprocess_yaml_data(), fixed reload() to route through full preprocessing pipeline instead of bypassing validation
  • crates/openshell-sandbox/src/l7/mod.rs: Updated validate_l7_policies() for ports array and wildcard host validation (rejects bare *, requires *. prefix, warns on broad patterns)
  • crates/openshell-sandbox/src/mechanistic_mapper.rs: Emit ports field in generated proposals
  • architecture/security-policy.md: Documented host wildcard syntax, delimiter semantics, multi-port syntax, backwards compat, validation rules
  • e2e/python/test_sandbox_policy.py: Added 6 e2e tests (MP-1 through MP-3 for multi-port, HW-1 through HW-3 for host wildcards)

Deviations from Plan

None — implemented as planned.

Testing

  • mise run pre-commit passes
  • Unit tests added/updated
  • E2E tests added (requires cluster for execution)

Tests added:

  • Unit (opa.rs): 18 tests — multi-port matching (first/second/unlisted port, backwards compat, hostless, proto round-trip, L7 evaluation), host wildcards (subdomain match, deep subdomain reject, bare domain reject, case insensitive, port enforcement, multi-port combo, L7 rules, endpoint config query)
  • Unit (lib.rs): 7 tests — ports array parsing, single port normalization, round-trip preservation, compact serialization, wildcard host parsing and round-trip
  • Unit (l7/mod.rs): 7 tests — wildcard validation (bare star, double star, malformed prefix, broad pattern warning, valid pattern), ports array with REST 443 warning
  • E2E (test_sandbox_policy.py): 6 tests — MP-1 multi-port allows all listed ports, MP-2 denies unlisted port, MP-3 single port backwards compat, HW-1 wildcard matches subdomain, HW-2 rejects bare domain, HW-3 rejects deep subdomain

Checklist

  • Follows Conventional Commits
  • Architecture docs updated

Documentation updated:

  • architecture/security-policy.md: Host wildcard and multi-port sections with syntax, validation rules, examples, and field mapping table updates

Add glob-style host wildcards to endpoints[].host using OPA's
glob.match with "." as delimiter — *.example.com matches a single
DNS label, **.example.com matches across labels. Validation rejects
bare * and requires *. prefix; warns on broad patterns like *.com.

Add repeated uint32 ports field to NetworkEndpoint for multi-port
support. Backwards compatible: existing port scalar is normalized to
ports array. Both the proto-to-JSON and YAML-to-JSON conversion paths
emit a ports array; Rego always references endpoint.ports[_].

Fix OpaEngine::reload() to route through the full preprocessing
pipeline instead of bypassing L7 validation and port normalization.

Closes #359
@johntmyers johntmyers self-assigned this Mar 16, 2026
@johntmyers johntmyers added the test:e2e Requires end-to-end coverage label Mar 16, 2026
@johntmyers johntmyers merged commit 241e95d into main Mar 16, 2026
9 checks passed
@johntmyers johntmyers deleted the feat/359-host-wildcards-multi-port/jomyers branch March 16, 2026 20:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test:e2e Requires end-to-end coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: support host wildcards and multi-port endpoints in network policies

2 participants