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 e2e 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

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