- verifies the control-channel peer leaf against the controller's full trusted-CA pool
(identity.CA()) instead of only self-signed roots, honoring intermediate trust anchors
and multi-root bundles
- drops the client-auth extended-key-usage requirement so an externally managed PKI with
arbitrary or absent EKUs is not rejected
- adds tests asserting router control channel connections with an untrusted or
self-signed leaf (including one backed by a scraped CA-chained filler cert) are
rejected, and that separately-validated channel types are skipped
- extracts a small header helper so the grouped-connection first-underlay scoping
is unit-testable, and tests that the flag is not inherited by additional underlays
Runs the router certificate fingerprint validation for router control-channel
underlay types, which was previously skipped for any connection carrying a
channel type header. Connections of other types (e.g. the raft mesh) continue to
be deferred to their own acceptor.
The already-connected / churn guard is applied only when establishing a new
channel, so additional underlays of a grouped control channel are not rejected
while the router is already connected.
- verifies the presented leaf against the node's full trusted-CA pool (identity.CA())
instead of only self-signed roots, so intermediates distributed as trust anchors and
multi-root bundles are honored
- drops the client/server-auth extended-key-usage requirement so an externally managed
PKI with arbitrary or absent EKUs is not rejected
- removes the now-unnecessary direction-aware mesh validation, which only existed to work
around the EKU requirement
- verifies an outbound dial peer's leaf (its TLS server certificate) against
server-authentication key usage, and an inbound peer's leaf against
client-authentication key usage, so a split client-auth/server-auth external
PKI does not reject legitimate controller mesh connections
- adds a server-auth verification helper alongside the client-auth one and
covers both directions with tests, including the outbound server-certificate
path in the live handshake test
- adds tests asserting incoming router links reject a dialer presenting an
untrusted or self-signed leaf, including one backed by a scraped CA-chained
filler cert, and accept a leaf that chains to the CA
- adds tests asserting the metrics scrape-cert gate matches the pinned cert
against the presented leaf only, honors the validity window, and rejects
requests with no client certificate
- copies the eventual event Data out of the bbolt-managed buffer when loading, instead of
retaining the raw slice
- the data is handed to event listeners asynchronously after the read transaction closes,
so the retained slice could dangle into reused or remapped mmap memory
(cherry picked from commit 95d6210e22)
- copies the terminator PeerData values and InstanceSecret out of the bbolt-managed
buffer when loading a terminator, instead of retaining the raw slices
- prevents a dangling reference into the mmap: bbolt values are only valid for the life
of the transaction, and are reused or unmapped once the tx closes or the database file
is remapped on growth
- fixes a controller SIGSEGV in the create-circuit response encoding, which reads the
cached terminator peer data and can memmove from a stale address under terminator churn
(cherry picked from commit 0cefe735db)
Backport of the BuildAll revocation-load fix (main PR #4103) so the
router-data-model validation reflects revocation presence correctly on this
branch. The test accompanying the main-line change is omitted here because its
test helper does not exist on release-v2.0.x.
- adds BuildRevocations and wires it into InstantStrategy.BuildAll so a rebuilt
data model includes existing revocations instead of dropping them
- skips already-expired revocations at load time
- moves MarkInitialized from the end of initWeb to after config.Configure runs in Run
- prevents GetApiAddresses from caching an empty address set when an early raft mesh hello arrives before the xweb config has been populated
- ensures a controller no longer permanently advertises empty apiAddresses to its peers after losing this startup race
(cherry picked from commit 5c16993ef984f2ee89b6fed47d60e71258dc0680)
- denies non-admins the ability to create, read, refresh, or delete an
enrollment belonging to an admin identity, since an enrollment carries
the one-time-token/JWT used to enroll as its target identity
- filters admin-identity enrollments out of enrollment lists for non-admin
callers
- denies access when the target identity or enrollment cannot be loaded
rather than relying on a downstream handler
- adds a permissions test covering each escalation path plus the
admin-allowed behavior
Backport to release-v2.0.x of #4013.
Backport to release-v2.0.x of #3927 (main-line fix in PR #3930).
The router's CheckConnections reaper enforced only JWT expiry, so a revoked OIDC
api-session, or a disabled/deleted identity, kept its live circuits and hosted
terminators until the access token expired. The router now enforces the
RouterDataModel revocations directly, tightening access-loss propagation to the
reaper interval.
- adds a Type field to DataState_Revocation and the raft Revocation command
proto, mirroring rest_model.RevocationTypeEnum (API_SESSION/IDENTITY/JTI) so the
management API, OIDC producers, sync, and router enforcement share one
vocabulary; the common.RevocationType* constants are compile-time bound to the
enum to prevent drift
- adds an IssuedBefore cutoff so an identity revocation invalidates only sessions
issued before it; a session re-authenticated after the cutoff survives the
still-lingering revocation. Persists IssuedBefore on the db and model Revocation
and carries it (plus the Type) through the single and batched raft marshalling
- adds RouterDataModel.IsApiSessionRevoked and IsIdentityRevoked and enforces both
in CheckConnections, closing a revoked session's connections
- revokes a deleted or disabled identity's live OIDC sessions via an
IdentityRevocationConstraint in the db package, run as a store pre-commit
constraint so the revocation is written in the same transaction as the identity
change and cannot be skipped (self-contained OIDC JWTs aren't otherwise
reachable). NewIdentityManager installs it with the revocation type and lifetime
- has the OIDC end-session (TerminateSessionFromRequest) revoke the specific
api-session named by the z_asid claim, with an identity-scoped fallback; sets
IssuedBefore on the identity fallback and the management revocation API; adds
RevocationManager.CreateOrReplace, routed through by both the OIDC paths and the
management revocation API, so a repeat logout/termination/revocation refreshes
the cutoff rather than colliding on the reused id. Expiry derives from the
longest configured token duration via a shared common.MaxTokenDuration helper
- adds tests/revocation_enforcement_oidc_test.go covering api-session revocation
(and a fresh session staying unaffected), identity disable and delete, and the
identity cutoff (a post-cutoff session surviving the lingering revocation)
- regenerates edge_cmd.pb.go (protoc-gen-go v1.31.0) and edge_ctrl.pb.go
(v1.36.11) against the release-v2.0.x base, matching each file's existing
generator version
* backport openziti/ziti#3952 to v2.0 reject invalid externalIdClaim and stop enrollment panic
- moves the error check before the locator assignment in Ca.GetExternalId
so an unsupported matcher/parser no longer nil-derefs and returns HTTP 500
- guards the claim index against negative and out-of-range values and errors
when a matched claim resolves to an empty string, so an empty externalId is
never silently accepted nor mapped to an identity
- adds validateExternalIdClaim, rejecting unsupported locations, unsupported
matcher/parser combos, missing matcher/parser criteria, and negative indexes
with HTTP 400 at CA create and update time
- applies the validation to both CA create and update, which previously had
no externalIdClaim validation on the update path
- tests GetExternalId across the matcher/parser matrix for no-panic and
correct error/value behavior
- tests CA create and update reject invalid externalIdClaim configurations
- tests that an externalIdClaim resolving to empty fails enrollment cleanly
* backport openziti/ziti#3952 to v2.0 address pr review
- adds the missing self.startIndex assignment in BoltDbFsm.Init so
GetStartRaftIndex() returns the persisted raft index instead of 0,
which was leaving the RDM's RaftIndexProvider seeded at 0 on every
restart and reporting a stale index until the next command applied
- adds a regression test that opens an FSM, persists a raft index,
reopens it, and asserts GetStartIndex reflects the persisted value
- notes the fix in CHANGELOG.md under the ziti/v2 issue list
* fixes#3809 support CSR submission during OIDC authentication
- accepts an optional CSR during OIDC login (all auth methods) and
signs it into a session-bound certificate with a SPIFFE ID derived
from the identity and API session
- returns the signed certificate PEM as a top-level "session_cert"
field in the token endpoint JSON response (CodeExchange, RefreshToken,
TokenExchange)
- adds cert-binding verification on RefreshToken and TokenExchange:
if z_cfs is present the peer cert fingerprint must match (strict),
otherwise falls back to SPIFFE ID verification
- supports cert rotation via csr_pem form parameter on refresh and
token exchange; replaces the session cert fingerprint while
preserving the authenticating cert fingerprint
- adds AuthCertFingerprints (z_acfs) claim to track permanent auth
cert fingerprints separately from rotatable session cert fingerprints
- only the leaf certificate fingerprint is added to z_cfs and z_acfs,
intermediates are never included
- invalid CSR returns 400 Bad Request in OIDC error format
- propagates updated CustomClaims (including CertFingerprints) from
access token to renewed refresh token so rotated fingerprints are
enforced on subsequent refreshes
- adds CertGenerated field to ApiSessionEvent
- advertises OIDC_AUTH_WITH_CSR controller capability when OIDC is
enabled
- adds unit tests for verifyCertBinding (fingerprint, SPIFFE ID,
edge cases) and CsrPem field parsing
- adds integration tests for initial CSR auth (updb, cert, ext-jwt),
cert-binding on refresh/exchange, CSR rotation with cert auth,
SPIFFE fallback and z_cfs transition, and CSR property forging
resistance
* address pr concerns
* add z_cfs len tests on junk chain certs
* strip csr subject info, replace w/ santized values
* fix csr rotation rejection/paths during token exchange/refresh
- gates OIDC `z_cfs` claim on cert-based primary auth so non-cert sessions don't bind TLS-presented certs
- reorders router api session validation: fingerprint match first, then SPIFFE-on-first-party as fallback
- accepts first-party SPIFFE-Identity match as an acceptance signal (previously fell through to fingerprint check)
- adds `ziti agent inspect <value>...` CLI that sends an InspectRequest directly to a ziti process over its agent IPC channel and pretty-prints JSON values in the response
- accepts app id 0 on controller and router agent channels so a single inspect command works against any process type
- adds Controller.agentOpInspect, backed by a new InspectionsManager.InspectLocal that runs inspect processing on the local controller only, without fanning out to routers or peer controllers
- extracts the router inspect handler into a new router/inspect package and stores a single shared instance on Router, reused by both the control channel and the agent IPC channel
- adds RouterEnv.GetInspectHandler and RouterEnv.GetXgressListeners so the control channel bind pulls the shared handler from env
- removes the now-redundant InspectRouterEnv interface from handler_ctrl/bind.go
- adds tunnel HandleAgentAsyncOp with support for stackdump and sdk inspect keys
* fixes#3734 enforce client certificate proof-of-possession for OIDC sessions
- adds verifyCertProofOfPossession() in resolveOidcSession() to require
TLS client cert matching a z_cfs fingerprint or SPIFFE ID when the
OIDC token was issued with cert bindings
- adds z_cae (CertAllowExpired) claim from auth policy, propagated
through token issuance and refresh
- adds TrustCache.VerifyClientCert() with tiered pool matching
(first-party roots, trust anchors, third-party) and TTL cache
- adds WrapIdentityWithCertValidation() on the router to verify client
certs at the TLS level against RDM PublicKeys
- adds IsFirstPartyCert() on the router to gate SPIFFE ID matching by
checking whether the cert chains to the controller root CA
- adds VerifySpiffeId() in common/spiffehlp with SpiffeMatchApiSession,
SpiffeMatchIdentity, and SpiffeMatchNone return types
- adds SPIFFE IDs to OTT and token enrollment certs (/identity/<id>)
- enforces cert expiry by match type: API session certs must be valid,
fingerprint-matched certs respect z_cae, legacy sessions skip checks
- shallow-copies leaf certs before overriding time fields in all cert
verification paths to avoid races on shared x509.Certificate pointers
- fixes controllerRootCache setting inited=true before the ctrl channel
is available, which permanently cached the failure
* fixes#3806 expose OpenZiti endpoints in OIDC discovery document
- adds vendor-specific "openziti_endpoints" field to the
/.well-known/openid-configuration response
- overrides Discovery() on the OIDC server to wrap the standard config
with OpenZiti login and MFA endpoint URLs
- advertises password, cert, ext-jwt, totp, totp enrollment, and
auth query endpoints as absolute URLs derived from the issuer
- adds integration test verifying all openziti_endpoints fields
- adds dual-server integration test confirming endpoint URLs reflect
the correct issuer when edge-oidc is hosted on multiple bind points
* changelog
- registers the service policy type symbol as a string, so queries match the
API's "Dial" and "Bind" names rather than the internal int32 ids
- adds a symbol mapper that converts the stored int32 to its PolicyType name
at query eval time
- updates the posture-checks lookup in EdgeServiceManager, the only direct
caller that went through GetSymbol, to read the mapped string form
- adds a store test covering type = "Dial" and type = "Bind" filtering
- notes the breaking removal of the undocumented type = 1/type = 2 form in
the 2.0 deprecation cleanup list
- migrates from op.NewProvider() to LegacyServer/RegisterLegacyServer,
routing all OIDC endpoint errors through op.WriteError which maps
server_error to HTTP 500 and supports custom status codes via
op.StatusError
- returns oidc.ErrInvalidClient() from AuthorizeClientIDSecret for
unknown clients and bad secrets (HTTP 400 with invalid_client)
- returns oidc.ErrInvalidGrant() from parseRefreshToken,
parseAccessToken, createAccessToken, and renewRefreshToken for
client-supplied token errors (HTTP 400 with invalid_grant)
- fixes copy-paste bug in parseAccessToken that reported "invalid
refresh_token" for access token errors
- plain Go errors from server-side failures (identity read, JSON
marshal, token signing, Raft dispatch) now correctly surface as
HTTP 500 via WriteError's DefaultToServerError handling
- adds integration tests covering error codes for token endpoint,
login endpoint, userinfo endpoint, and end_session endpoint
* fixes#3680 add revocation management API, CLI, and enforcement
- adds Management API endpoints for revocations (POST, GET, LIST) with
type-aware validation (JTI/API_SESSION require UUID, IDENTITY requires
existing identity)
- adds CLI commands: ziti edge create revocation identity|api-session|jti
- adds revocation checks to resolveOidcSession in security_ctx.go so the
REST API returns 401 for revoked OIDC tokens. Previously only
ValidateAccessToken (router ctrl channel path) checked revocations,
so revoked tokens still received 200 OK from the management and client
HTTP APIs
- adds api-session revocation check to ValidateAccessToken, which only
checked JTI and identity revocations
- adds Type field to Revocation model, store, and protobuf message
- adds integration tests covering CRUD, input validation, and token
enforcement for all three revocation types
- use release edge-api@v0.28.1
- adds ErrorType constants for rejected-by-application, DNS resolution failed,
port not allowed, invalid link destination, and resources not available
- adds corresponding CircuitFailureCause strings reported in circuit events
- extracts classifyDialError() in route handler to map dial errors to specific
error codes using typed errors, syscall constants, and string matching
- detects DNS errors via *net.DNSError and string fallback for ER/T hosted
services where errors are serialized through the SDK message protocol
- detects resource exhaustion via EMFILE, ENFILE, ENOBUFS syscall errors
- introduces InvalidLinkDestinationError typed error in forwarder package
- adds unit tests covering all 16 classification cases
- adds integration tests for rejected-by-application (SDK host),
DNS resolution failed, connection refused, and port not allowed
(ER/T host mode) with circuit event verification
- adds CreateEnrollAndStartTunnelerEdgeRouterWithCfgTweaks to test context
- changes lastResponse from plain int64 to atomic.Int64 in both router
and peer heartbeat callbacks, fixing a data race between the heartbeat
response handler and the heartbeat check ticker
- fixes peer heartbeat logger channelType from "router" to "peer"
- uses already-parsed AdvertiseAddress for cert validation instead of
re-reading from the raw map, which panicked when the key was absent
- replaces errors.Wrapf(err, ...) with fmt.Errorf where err is nil, so
routerDataModel.listenerBufferSize validation errors are returned
instead of silently swallowed
- fixes pfxlog.Logger().Warn() call using %w verb, which is not
interpolated outside fmt.Errorf
- prefers the signing cert from the hello header when available, falling
back to the TLS underlay cert
- applies consistently on both dialer and acceptor sides
- removes the dead append on the acceptor side that was immediately
overwritten
- checks for the timeout key in the rateLimiter submap instead of the
parent tls cfgmap, so a user-specified timeout is no longer silently
overwritten by the default
- swaps arguments to strings.HasPrefix so it checks whether the trust
domain starts with "spiffe://" rather than whether "spiffe://" starts
with the trust domain
- fixes both the trustDomain and additionalTrustDomains code paths
- replaces the per-handler goroutine in connectEventsHandler with a shared,
bounded goroutine pool on AppEnv
- the old design spawned a processEvents goroutine per router connection that
only exited on application shutdown, leaking a goroutine on every reconnect
- adds ConnectEventsConfig to the controller config with pool tuning options
(queueSize, minWorkers, maxWorkers, idleTime)
- defaults: queue 16, 0-16 workers, 30s idle timeout