-
Notifications
You must be signed in to change notification settings - Fork 31
feat: centralized ambient egress #2194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file came out of a refactor to avoid circular import dependency between egress.ts and istio-resources.ts due to ingress and egress orchestration having been intertwined. Previously resulted in unit-test failures due to the dependency issues.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR centralizes Ambient egress control by creating shared, per-host resources in the istio-egress-ambient namespace. Previously, each package created its own egress resources; now a single ServiceEntry and AuthorizationPolicy per external host is created across all Ambient packages. Identity resolution uses a SA-first principals approach with namespace fallback, combining host owners and "Anywhere" participants. Safety checks prevent transient allow windows by skipping resource creation when no identities are resolved, and generation-based purging removes stale resources.
Key Changes:
- Centralized per-host
ServiceEntryandAuthorizationPolicyinistio-egress-ambientnamespace with unified identity resolution (owners + Anywhere participants) - New
egress-orchestrator.tsseparates egress reconciliation logic;remapAmbientEgressResources()merges package maps into per-host resources sanitizeWithLimit()utility ensures deterministic resource naming within Kubernetes limits
Reviewed changes
Copilot reviewed 19 out of 19 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
| test/vitest/network.spec.ts | Adds tests for per-host egress isolation in Ambient mode |
| src/pepr/operator/reconcilers/package-reconciler.ts | Calls istioEgressResources() separately; counts unique hosts for AP status |
| src/pepr/operator/crd/validators/package-validator.ts | Allows serviceAccount with remoteGenerated: Anywhere on Egress in Ambient |
| src/pepr/operator/controllers/utils.ts | Adds sanitizeWithLimit() for deterministic name truncation with hash suffix |
| src/pepr/operator/controllers/network/generators/egress.ts | Updates import for ambientEgressNamespace from centralized location |
| src/pepr/operator/controllers/istio/service-entry.ts | Implements generateSharedAmbientServiceEntry() for centralized per-host SE |
| src/pepr/operator/controllers/istio/service-entry.spec.ts | Tests shared Ambient SE generation with waypoint binding and annotations |
| src/pepr/operator/controllers/istio/istio-resources.ts | Centralizes Ambient egress constants; removes legacy istioEgressResources() |
| src/pepr/operator/controllers/istio/istio-resources.spec.ts | Updates mocks and expectations for orchestrator-based egress flow |
| src/pepr/operator/controllers/istio/egress.ts | Adds remapAmbientEgressResources() to merge package maps; defensive null checks |
| src/pepr/operator/controllers/istio/egress.spec.ts | Tests remapping logic for merging ports/protocols and collecting packages |
| src/pepr/operator/controllers/istio/egress-orchestrator.ts | New orchestrator for egress reconciliation; replaces removed function |
| src/pepr/operator/controllers/istio/egress-ambient.ts | Rewrites to create centralized SE/AP with pre-indexed identity resolution |
| src/pepr/operator/controllers/istio/egress-ambient.spec.ts | Comprehensive tests for identity resolution, merging, and fallback scenarios |
| src/pepr/operator/controllers/istio/defaultTestMocks.ts | Adds getPkgListMock for UDSPackage.Get() in tests |
| src/pepr/operator/controllers/istio/auth-policy.ts | Implements generateCentralAmbientEgressAuthorizationPolicy() with targetRef |
| src/pepr/operator/controllers/istio/auth-policy.spec.ts | Tests central AP generation with from-only rules targeting ServiceEntry |
| src/pepr/operator/controllers/istio/ambient-waypoint.ts | Updates import path for centralized ambient constants |
| docs/reference/configuration/service-mesh/egress.md | Updates documentation to reflect centralized per-host SE/AP architecture |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
joelmccoy
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally LGTM! Tested some of the functionality locally with no issues. A couple comments/questions for consideration
| const uniqueHosts = new Set( | ||
| egressRequestedFromNetwork(pkg.spec.network.allow) | ||
| .map(a => a.remoteHost) | ||
| .filter((h): h is string => typeof h === "string" && h.length > 0), | ||
| ); | ||
| numEgressAuthPols = uniqueHosts.size; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: I don't have a strong opinion, but wanted to ask the question in case somebody does.... Do we want to count the central auth policy as a count for this package?
I almost lean towards not counting it as it's extra logic and doesn't reflect the namespaced scope # of auth policies we would see in the package namespace
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Chatted with @mjnagel about this and i think we're in agreement that this count doesn't really provide that much so we also lean towards ont counting it towards the policy count.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to sanity check, we are in agreement that the remote host shared authpolicy entries should NOT be counted towards the package auth policy count? If so, I think we can still remove some of this logic here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this should be addressed in the lates commit 👍🏼
mjnagel
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Initial comments - need to go through some of the shared logic a bit more to comprehend the behavior and do testing.
| log.debug(serviceEntry, `Applying Service Entry ${serviceEntry.metadata?.name}`); | ||
| for (const allow of egressRules) { | ||
| // Process Anywhere participants | ||
| if (allow.remoteGenerated === RemoteGenerated.Anywhere) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we also need to check the port here to make sure that something allowed to "anywhere" on port 80 isn't allowed to hit a remote host on 443.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In testing I think this will mean we need to split the authpols by port (to.port on the rules) in order to handle remoteGenerated allowed to one port but not another, where a remoteHost is set for multiple ports.
| namespace, | ||
| name, | ||
| namespace: ambientEgressNamespace, | ||
| labels: { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Functionality it doesn't seem like we need it, but might be good to add the uds.dev/user- annotations to link this back to the "owners" (the packages explicitly requesting it)?
mjnagel
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did more e2e testing today and encountered some timing problems I believe as well as the port issue.
| await purgeOrphans(String(generation), ns, name, IstioServiceEntry, log, { | ||
| "istio.io/use-waypoint": "egress-waypoint", | ||
| }); | ||
| await purgeOrphans(String(generation), ns, name, IstioAuthorizationPolicy, log, { | ||
| "uds/for": "egress", | ||
| }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think there's a slim edge case here where this fails to delete things? Since generation is the new "shared" generation, if the Package CR happened to have been on that generation (for its "local" generation) then nothing would get purged. I think we could probably just directly get, then delete all of them (probably separate from purgeOrphans since that requires a generation for filtering).
| sharedEgressPkgId, | ||
| IstioAuthorizationPolicy, | ||
| log, | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm having trouble tracing down the "why" but this purge seems to have a race condition or other bug where it can result in most resources getting purged. I've been testing with a large set of packages and when applying them all together I end up with no egress service entries or authpols in my cluster. Restarting the watcher typically works to create them, but there's also some strange behavior on delete at times where deleting all the packages together sometimes creates/leaves them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to do more testing and debug, but definitely seems like the applies are ending up running out of order so I end up with an outdated generation "winning" for the resulting serviceentry/authpol, and then the purge functions remove them.
| log.debug(serviceEntry, `Applying Service Entry ${serviceEntry.metadata?.name}`); | ||
| for (const allow of egressRules) { | ||
| // Process Anywhere participants | ||
| if (allow.remoteGenerated === RemoteGenerated.Anywhere) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In testing I think this will mean we need to split the authpols by port (to.port on the rules) in order to handle remoteGenerated allowed to one port but not another, where a remoteHost is set for multiple ports.
Description
ServiceEntryinistio-egress-ambient.AuthorizationPolicyinistio-egress-ambienttargeting that ServiceEntry with from-only rules.remoteGenerated: Anywhere) with SA-first principals and namespace fallback.sanitizeWithLimit(). Ambient constants centralized inistio-resources.ts.New AP flow (Ambient)
ServiceEntry(bound to egress waypoint).AuthorizationPolicywith from-only rules andtargetRefto the host’sServiceEntry.Related Issue
Fixes #2064
Type of change
Steps to Validate
uds run test:uds-core-e2enpm run test:unitnpm run test-uds-core-upgradeManual steps
kubectl -n istio-egress-ambient get se,ap | grep httpbin# expect ambient-se-httpbin-org, ambient-ap-httpbin-orgR=$(kubectl -n ambient-egress-restricted get pod -l app=restricted-curl -o name)A=$(kubectl -n ambient-egress-anywhere get pod -l app=anywhere-curl -o name)kubectl -n ambient-egress-restricted exec -it $R -- sh -c 'curl -s -w " HTTP_CODE:%{http_code}\n" https://httpbin.org'# ALLOW (retry if 503)kubectl -n ambient-egress-restricted exec -it $R -- sh -c "curl -s -w ' HTTP_CODE:%{http_code}\n' http://httpbin.org"# DENYkubectl -n ambient-egress-restricted exec -it $R -- sh -c 'curl -s -w " HTTP_CODE:%{http_code}\n" https://api.github.com'# DENYkubectl -n ambient-egress-anywhere exec -it $A -- sh -c 'curl -s -w " HTTP_CODE:%{http_code}\n" https://httpbin.org'# ALLOWkubectl -n ambient-egress-anywhere exec -it $A -- sh -c "curl -s -w ' HTTP_CODE:%{http_code}\n' http://httpbin.org"# DENYkubectl -n ambient-egress-anywhere exec -it $A -- sh -c 'curl -s -w " HTTP_CODE:%{http_code}\n" https://api.github.com'# ALLOW (L4 Anywhere)Checklist before merging