Repository navigation
Implement admission, credential expiry and access revocation #977
Description
Parent: #970
Deliverable
A transport-independent admission component that binds each accepted request to one authorized connector and gVPC and terminates forwarding when authority expires.
Dependencies
#972, #976, foundation #994; coordinate pre-admission limits and negative cases with #991.
Galactic Edge integration (#992)
Implement this milestone as a consumer of the Galactic Edge provider contract. #992 owns provider authorization, host attachment lifecycle, shared-state fencing and maintenance, independently enforced forwarding expiry, prefix conflict arbitration/publication, and gVPC traffic policy. MASQUE owns client authentication, connector /96 assignment and retention, session packet checks, transport behavior and its provider adapter. Use the foundation's versioned contract and existing provider helpers; do not introduce a second host-networking controller, lease store or BGP publisher. Any required foundation change must be tracked and qualified under #992 before this milestone claims integrated acceptance.
Work
- Implement the selected credential profile and trust configuration, resolving an authenticated principal to explicit connector/gVPC grants.
- Bind immutable authorization context to each CONNECT-IP request; reject missing, invalid, expired, ambiguous or unauthorized credentials and references.
- Consume revocation and authorization updates through the accepted internal interface and cap forwarding validity by credential, grant and ownership deadlines.
- Provide an internal test/configuration path to exercise enrollment-independent credentials and grants. Do not add a public API, client login workflow or plugin/portal integration.
Acceptance criteria
- Unit and integration tests cover valid admission, wrong issuer/audience where applicable, expiry, revoked access and attempts to select another gVPC.
- Existing forwarding stops within the agreed bound after expiry or revocation, including the documented control-plane outage case.
- Authorization cannot be extended merely by reconnecting or replaying stale grant state.
- The component exposes the same decision and immutable context to HTTP/3 and HTTP/2 adapters; credentials are absent from logs.
Additional integration acceptance
- Authenticate external clients separately from pod-bound provider authentication. Resolve trusted connector grants to a foundation-authorized EdgeService/VPC/prefix; client input cannot create or broaden provider grants.
- Bind credential/grant version and deadline to immutable VPC/service/attachment/route/session identities. Fail closed on deleted/recreated grants, stale versions and ambiguous mappings.
- Implement the per-connector authority model agreed in Define connector admission, addressing and ownership contracts #972/Integrate connector ownership with Edge claims and independent expiry #976 and test single-connector revocation while other connectors renew the same instance, including stale reconnect attempts and authority-source outage.
- Complete authentication and authorization before any MTU probe or tenant packet I/O; test CONNECT-IP server lacks overload limits and tenant isolation checks #991's unauthenticated probe case on both transports.
Scope follows #970: CONNECT-IP with HTTP/2 fallback; no public API development, plugin/portal integration, product-client development or production rollout.
Metadata
Metadata
Assignees
Labels
Type
Fields
Priority
Activity
[-]MASQUE: implement admission, credential expiry and access revocation[/-][+]Implement admission, credential expiry and access revocation[/+]privateip commentedon Oct 28, 2026
Verdict: valid. The capability is missing on
mainat 69640a5.How this was confirmed. This is a missing component rather than a misbehaving one, so the check was a source search, not a failing test.
internal/edge/masque/grant.godefines theAuthorizerinterface (Authorize,Refresh,Retire) that the adapter calls on admission, on every refresh tick and on replacement. Its doc comment states that authentication, /96 allocation and EdgeService issuance are not the adapter's job.tests/edgeconformance/masqueprovider/config.go. It compares plaintext credentials from a config file, issues a one-hour deadline with no credential expiry, refers to EdgeServices the deployer created by hand, and forgets retirements when it restarts.validUntil = min(credential expiry, read start + 30 s − 2 s)renewal, pod-bound provider identity on an authenticated TLS channel, revocation and rotation, and the operator tool for test connectors.What is already in place.
EdgeService.spec.validUntilminus MaxSkew through admission, the attachment service controller and the route checks.Refreshreturns unauthenticated or forbidden, or when its deadline passes, without touching other connectors. Integrate connector ownership with Edge claims and independent expiry #976 added this.internal/edge/admission.IdentityFromUserInfoturns a TokenReview result into a pod-bound identity, so the authority can reuse it.Findings beyond the issue text.
masque.Granthas no authorization ID or credential revision. The contract'sRefreshtakes both, so the grant type needs them, andsameAuthorityhas to compare them.connectip.Admission, so this component serves both by construction. Running the HTTP/2 rows themselves waits on Implement HTTP/2 CONNECT-IP fallback over TLS #981.privateip commentedon Oct 28, 2026
Approved plan.
Root cause.
masque.Authorizerhas no production implementation. The only one is the conformance fixture's static config.Fix, on branch
fix/issue-977:internal/edge/masque/authority, the core.Authorizecompares digests in constant time. It reads the record and the VPC uncached and requires the VPC UID to match. It then creates one EdgeService per authorization incarnation, withPacketAccessandPrefixReachability, the /96 as its only prefix, the /120 pool asclientPool, andvalidUntil = min(credential expiry, read start + 28 s).Refreshrereads the record and renewsvalidUntilwith UID and resourceVersion preconditions. It refuses a disabled or rotated revision, a retired incarnation and the wrong pod. When the authority can't read the record, it returns unavailable and never renews from a cache.Retireand a revocation sweep setvalidUntilin the past. A retired incarnation is never renewed.masque.Grantgains an authorization ID and a credential revision, andsameAuthoritycompares both.masque-authority. The authority checks it with a TokenReview andadmission.IdentityFromUserInfo.authority.Clientimplementsmasque.Authorizerfor the adapter.cmd/masque-authorityhas aservecommand andgrant create|rotate|disable|deletecommands. Eachgrantcommand validates the VPC UID and the reserved pools, prints the bearer once and stores only its digest.Tests.
Risk. Almost all of the code is new. The only change to an existing API is the two new
masque.Grantfields, which touches the fixture and the adapter tests. The foundation is unchanged.Out of scope.
config/manifests ship with Implement the HTTP/3 CONNECT-IP edge service #980.privateip commentedon Oct 28, 2026
Three changes to the approved plan. Each came from a test that failed while I implemented it.
Authorizecarries the time the call was first sent, and the authority keeps a retired incarnation for 10 s before deleting it. Without this, a late retry could create a second incarnation. The sequence that showed it:Now a retry either finds its retired incarnation and is refused, or arrives too late and is refused as stale. The cost is up to 10 s more on a cross-pod takeover, on top of waiting for core to release the old attachments.
Incarnations are named
masque-<connector>-r<revision>, not after the authorization ID. A second test raced three pods for one connector. One check after each create could not settle the race: an issuer that checked before its rival existed could lose on name order, and both incarnations stayed live. The authority already issues only after every other incarnation of the connector is gone, so racing issuers now contend for one name. The API server accepts exactly one create. The UID tells successive incarnations apart, and the authorization ID still includes the nonce.Revocation push notifications are not built. The provider learns of a revocation from its next
Refresh, at most 5 s later. The issuedvalidUntilstays the backstop.Renewing in place works against core. A new adapter check renews a live service's
validUntilpast its first deadline, through several new generations. The attachment, route and claim epoch stay unchanged.feat(edge): Add the MASQUE grant authority
feat(edge): Add the MASQUE grant authority