- adds a link ConfigHandler (router/link FactoryRegistry) that applies router.link.v1 config: Apply rebuilds the listener/dialer set wholesale, and established Xlinks survive because Listener.Close() only closes the accept loop
- translates local link: YAML into router.link.v1 JSON and pushes it through the managed-config registry at startup
- adds the UpdateLinkListeners ctrl message so the router republishes its listener set to the controller on change; the controller re-fans via the existing PeerStateChange path
- re-evaluates dialers on link group and listener changes via RescanForDialOpportunities
For #3743.
- removes the costTags option from router link listeners, which was parsed
from config, advertised to the controller, and stored on the router model
but never used for path selection or any other behavior
- drops GetLinkCostTags from the xlink.Listener interface and its transport
implementation
- reserves the corresponding ctrl_pb.Listener.costTags and
RouterLinks.RouterLink.linkCostTags protobuf fields and regenerates ctrl.pb.go
- documents the removal in the changelog
- reports edge router policy denials with an access-denied error naming the missing policy,
replacing the session error reused on the sessionless ER/T and create-circuit-v3 paths
- adds EdgeRouterManager.GetEdgeRouterAccess, which reports which of the two required policy
links (identity-to-edge-router, service-to-edge-router) is absent, and removes the boolean
IsAccessToEdgeRouterAllowed it replaces
- logs the controller's rejection on the router at warn level, since it is recoverable and
retried by the periodic scan; the router previously discarded the error code and message
- delays a new terminator's first create attempt by a fixed 2s so config applied in quick
succession settles before the router asks, avoiding a 2-3 minute wait for the retry scan; the
delay is a deliberate stopgap until edge router policy visibility lands in the router data model
- propagates the controller's error code and retry hint to SDK clients on the dial paths, which
dropped the code and left every refusal classified as unknown
- adds the retry hint header to controller error replies, grouped with the other error-reply
headers rather than the create-circuit-v3 request headers
- notes that the sync strategy headers alias the edge namespace's 1013-1015 ids and stay
disjoint only by message content type
- tests the per-policy denial reporting, the error code carried with and without a retry hint,
the controller-to-SDK error code mapping at both dial relay sites, and the terminator settle
gate
- waits for terminator establishment in the tunneler dataflow tests instead of a fixed sleep, so
they no longer race the settle delay
- restores the tproxy multiple-lanIf and multiple-resolver changelog entries with keep markers,
which regeneration drops because their commits reference pull requests rather than issues
- releases the per-identity lock before acquiring the cmap shard lock when the scan
loop reaps an entry, establishing a single shard-lock-then-value-lock order
- extracts the entry removal into removeIfEmpty, which decides based on the value
currently in the map, so an identity that reconnects after the scan decided to
remove it is left alone
- takes the per-identity lock in the removal check, fixing an unsynchronized read of
the router map
- documents the lock ordering invariant on identityConnections
- adds tests covering concurrent scanning and connect/disconnect handling, entry
reaping, and reconnection between the scan's decision and the removal
Forward ports the GHSA-cc5m-7mhm-xh9f fix, released in 2.0.2, to main.
- runs router certificate and fingerprint validation for router control-channel
underlay types, which was previously skipped for any connection carrying a
channel type header; only types dispatched to a separate self-validating
acceptor (the raft mesh) are skipped now
- binds the enrolled-fingerprint check to the verified leaf, so a peer cannot
pass by presenting its own leaf followed by a target router's public
certificate
- applies the already-connected / churn guard only when establishing a new
channel, so additional underlays of a grouped control channel are not rejected
while the router is already connected
- extracts the first-underlay header construction so the grouped-connection
scoping is unit-testable
- adds negative-path tests for untrusted and self-signed leaves, and for
separately-validated channel types being skipped
Forward ports the GHSA-mrpr-756c-xm47 fix, released in 2.0.2, to main.
- adds a shared cert.VerifyLeafCertChain helper that verifies the presented leaf
(certs[0], whose private key the TLS handshake proved) against the node's full
trusted-CA pool, treating certs[1:] only as candidate intermediates
- uses it for controller cluster mesh peer connections and router link
connections, which previously accepted a connection when any presented
certificate chained to the trusted CA while taking peer identity from the leaf
- matches the pinned metrics scrape certificate against the presented leaf only,
comparing full DER rather than just the signature, and rejects a leaf outside
its validity window
- adds negative-path tests covering rogue leaves paired with CA-chained filler
certificates
* fixesopenziti/ziti#3990 push service and posture state to subscribed SDKs
- pushes indexed atomic ServiceChangeSet envelopes to subscribed SDK connections: a full snapshot on subscribe, incremental service changes per RDM scan pass, posture check definition changes as their own entries, and identity-resolved config bodies, all serialized so envelopes hit the wire in index order
- pushes per-connection PostureStateChange state (monotonic seq, resync on request) for posture pass/fail, including flips caused by definition edits that mutate no posture data
- registers pending RDM identity subscriptions for identities not yet synced to the router and sends an authoritative full sync plus full posture state when the identity arrives; an active push subscription pins the connection's RDM listener
- advertises service subscriptions and router data model support on the control-channel capability bitmask; the controller persists each router's capabilities mask and version on the EdgeRouter entity via raft and renders them on the edge APIs, so SDKs can select capable routers before connecting
- submits posture per router and corrects MFA posture semantics: pushed expiry is the earliest of timeout and pending wake/unlock grace deadlines, wake/unlock re-pass satisfies the re-prompt, api session tokens whose amr attests TOTP seed the MFA baseline from auth_time only (never iat), and token exchange carries the subject token's auth_time
- sends structured denials on dial and bind refusals: posture failures carry the failing check ids, no-policy denials are access denied, unknown services are invalid service, and session token failures are invalid session; the denial's cause no longer rides the wire as an unserializable error
- hard-closes accepted SDK connections on edge listener shutdown so clients observe a router going away immediately
- adds integration coverage: subscription snapshots and change delivery, poll and push reconciliation as capable routers come and go, posture state and definition-change push, router views over the public SDK API, typed dial errors, MFA baseline seeding, and OIDC token-exchange auth_time preservation
- removed RDM capability from SDK, router/controller only
- 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
- 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
KeySet() formatted a peer controller's SHA1 cert fingerprint with %s
instead of %x, producing an undecodable raw-byte kid in the /oidc/keys
JWKS response for every entry sourced from GetPeerSigners(). The
error-path logging four lines below this already used %x correctly.
In a multi-controller raft HA deployment, each controller signs
OIDC/OTT JWTs with its own root ctrl-plane-identity key. A JWKS lookup
landing on a controller other than the signer sees the true signer's
key advertised under this corrupt kid and cannot match it against the
token's kid header, failing verification.
Fixes#4067
Co-authored-by: Björn Mage <4495532+bjornmage@users.noreply.github.com>
- keys router capability bits off the sdk-golang RouterCapability enum
- adds a generic capabilities.Mask[T ~int] bitmask, centralizing capability
set/check behind one value-to-bit translation
- adds capabilities.RouterCapability and ControllerCapability types, with
RouterCapabilityMask/ControllerCapabilityMask aliases, so masks and checks are
typed per namespace
- supports control-plane-only router capabilities as negative values that index
down from the top of the mask, collision-free with the SDK's upward-numbered
bits and invisible to the SDK and edge-api
- references the sdk-golang RouterCapability enum as the source of truth for
shared router capability bits
- holds the router's advertised capability mask as an instance on the router env
rather than a global, so in-process test routers do not share state
- advertises PostureChecks and BindSuccess as capability bits on both channels
while still sending the legacy boolean edge headers for backwards compatibility
- routes GetCapabilities/IsCapable and the controller's Router.Capabilities field
through the typed mask
- adds a provenance test that verifies, via go/packages, that every positive
router capability is SDK-sourced, every negative is control-plane-only, and no
two resolve to the same bit
- update sdk to v2.0.0-pre2
- reorders DisconnectRouter so Router.MarkDisconnected runs before the
RerouteLink cascade, so shortestPath and connected-map reads see the
disconnecting router as gone and never compute a replacement path
through the router being removed
- snapshots the router's links before MarkDisconnected, which clears the
router's link set, so the link-removal/reroute loop still runs
- 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
- adds a common/servermetrics package that owns the metrics MetricsMessage wire
format and the reporting/usage subsystem (message builder, usage registry,
interval and usage counters), wrapping the openziti/metrics Registry for
metric collection
- moves the controllers metrics reporter into the router package and removes it
from the shared metrics package, breaking a common -> router/env import cycle
- repoints controller and router consumers to common/servermetrics; base metric
collection stays on openziti/metrics
- keeps the proto field numbers and the metrics content-type identical so the
encoding is byte-compatible across the move, and uses a distinct proto package
name so ziti's and the library's messages coexist without a global proto
registry clash
- adds a round-trip test asserting wire compatibility with the library's
MetricsMessage
- leaves openziti/metrics unchanged, so sdk-golang and the shared xgress data
plane are unaffected
- adds mesh regression tests asserting GetPeerInfo, WaitForPeer, and
GetOrConnectPeer reuse an existing inbound peer connection instead of
dialing the (possibly unreachable) peer address
- improves the leader-side add-member error to guide operators to initiate
the join from a firewalled node rather than returning an opaque dial timeout
* fix#3933 add controller to enrollment response
- adds the cluster's controllers to ott, ottca, updb, and token enrollment
responses with client and OIDC API addresses only
- synthesizes the running controller with its API addresses in non-HA mode
so the list is never empty
- adds --not-before to ziti pki create for backdated test CAs
- replaces the test PKI with a SPIFFE-capable, ziti pki generated and managed
one and rewires the config sets
- tests the controller list across ott/ottca/updb/token, non-HA, and raft
* fix missing wildcard cert from new PKI
* go mod tidy
- adds a striped id locker in common/concurrency for sharded per-entity locking
- switches the link manager to striped per-link locking to reduce contention
- adds a cache-before-transaction read path to the router manager
- adds a RouterReportedLink regression test covering same-link serialization and stale/same-iteration no-op behavior
- adds a source_entity_id label to prometheus metric output when the metrics event carries a source entity id
- distinguishes per-router ctrl channel metrics and per-link metrics that share a single source_id, preventing Prometheus from collapsing them into identical time series and dropping samples
- adds a test verifying the label is emitted per entity and that distinct entities produce distinct series
- records the fix in the changelog
Implements the router-side Connect-V2 sessionless dial path. Dials are
authorized locally via the RouterDataModel instead of a controller-issued
service session token; circuit creation flows through the existing
`CreateCircuitV3` controller endpoint (#3721). Builds on the sdk-golang
v2 migration.
- Adds `processConnectV2` on `edgeClientConn`: resolves the service by id
or name via the RouterDataModel, checks dial access, and dispatches to
the controller via `sendCreateCircuitV3Msg`. Supports both
`xgEdgeForwarder` (SDK xgress) and `nonXgConnectHandler` flow-control
modes, selected by the SDK's `UseXgressToSdkHeader`.
- Makes `CircuitId` optional in `DecodeCreateCircuitV3Request`. The V2
router path does not pre-assign a circuit ID; the controller generates
it as V1/V2 already do. Without this the decoder rejected the empty
header and every V2 dial hung until timeout. Adds a regression test.
- Splits `checkAccess` to close a posture-check bypass on the V2 path.
The old single `checkAccess` short-circuited to nil for non-OIDC
sessions (V1 ran posture at the controller during `CreateSession`); V2
has no such step, so posture would have been skipped. `checkAccess` now
always runs the RDM `HasAccess` (policy + posture) check;
`checkAccessIfOidc` keeps the OIDC-only gate for the V1 and bind paths.
- Sends the V2 `state_connected` on the default (data) sender rather than
the control sender. On multi-underlay channels the two senders are
independently ordered, so an early terminator payload on the data
sender could beat `state_connected` to the SDK and be dropped (channel/v5
has no message-priority API).
- Updates `xgEdgeForwarder.lastRx` on every forward path, including the
fast `timeout == 0` `TrySend` branch used for normal payload dispatch.
The old code only updated it on the `timeout > 0` path, so active V2
circuits looked idle and could be unrouted prematurely.
- Adds `state.ConnState.ServiceId`, populated by the connect handlers from
the service session token (V1) or the request header (V2). The
non-xgress V2 path previously left this empty, so `handleDialAccessLost`
could not identify and close V2 non-xgress circuits when dial access was
revoked.
- Skips conns with no `ServiceSessionToken` in `RemoveLegacyServiceSession`;
a sessionless V2 conn's token is nil and the cleanup loop previously
dereferenced it unconditionally, which would panic the router.
- Advertises Connect-V2 via the `RouterCapabilityConnectV2` bit in the
listener hello so SDKs can detect V2 support.
- Wires `ContentTypeConnectV2` and `ContentTypeXgControl` handlers in
`Acceptor.BindChannel`, and adds `handleXgControl` for SDK-side xgress
control messages, preserving `ControlUserVal` so trace-route responses
correlate back to the initiator's `SendForReply` waiter.
- Adds `RouterDataModel.serviceNameIndex` for O(1) name->id lookup in the
V2 dial path, maintained with rename safety at the `HandleServiceEvent`
mutation points.
- Adds `tests/connect_v2_test.go` covering end-to-end V2 dataflow and the
V1 fallback (`ForceConnectV1`), asserting the dial path via the SDK
`DialEvent`.
- Propagates a V2 initiator's graceful half-close to legacy hosts via
`edgeXgressConn.FlowFromFabricToXgressClosed`, which emits an edge FIN
when the fabric->app half of the circuit closes. The SDK signals
half-close to its router xgress peer with the native xgress EOF flag;
without translating that to an edge FIN, a legacy host reading to EOF
stalled until teardown.
- Records the dialing identity id as the circuit `ClientId` for
sessionless V2 dials, since there is no dial session to key on; updates
`Test_OidcEvents` to match.
- Adds `tests/connect_v2_teardown_test.go` covering client- and
host-initiated close propagation on both the V2 and forced-V1 paths.
- Polls for the asynchronous conn close in the SDK posture-check tests
(`awaitClientConnClosed`): revocation tears the circuit down out of
band, so checking `IsClosed` immediately after the first read error was
racy.
- Temporarily pins sdk-golang/v2 to the openziti/sdk-golang#959 commit,
which carries the matching xgress conn-close-on-teardown fix the V2
posture tests depend on; to be repointed at the next sdk-golang
pre-release before merge.
For openziti/sdk-golang#936.
Bumps the sdk-golang dependency from v1 to the v2 module
(`github.com/openziti/sdk-golang/v2` at v2.0.0-pre1) and updates all
import paths. This is a no-behavior-change precursor that isolates the
dependency migration from the Connect-V2 feature work in #3884.
- Rewrites `github.com/openziti/sdk-golang/...` imports to
`github.com/openziti/sdk-golang/v2/...` across the main and zititest
modules.
- Pins both modules to `github.com/openziti/sdk-golang/v2 v2.0.0-pre1`.
- Adapts `edgeXgressConn.AcceptMessage` to the v2 `MsgSink` signature,
which now takes an `edge.SdkChannel` argument.
- Replaces the removed `edge.Conn.GetRouterId()` with
`RemoteAddr().String()` in the loop4 traffic-test logging.
For openziti/sdk-golang#936.
- gives ListenerCtrlChannel Min: 0 constraints per underlay type plus
MinTotalUnderlays: 1 in its configs, so the controller accepts the
high/low-priority grouped underlays the router dials while still closing the
channel only when its last underlay is lost
- restores the multi-underlay behavior the v4 listener-side SetMinTotal(1)
provided, which the channel/v5 migration dropped
- works around channel/v5 not yet treating MinTotalUnderlays alone as a
multi-underlay signal
- blocks non-admins from creating an enrollment that targets an admin identity
- blocks non-admins from reading, refreshing, or deleting an admin identity's enrollment
- filters admin identity enrollments out of enrollment lists for non-admin callers
- adds a permissions test covering the escalation paths and admin-allowed behavior
- switches the xlink transport and router ctrl listeners to NewClassicListenerWithAcceptor, passing the MultiListener as a HelloAcceptor
- replaces the controller ctrl channel's NewClassicListener/UnderlayDispatcher wiring with NewClassicListenerWithAcceptor and a TypeRoutingAcceptor, adapting the mesh acceptor via AsHelloAcceptor
- removes the multiListenerAcceptor wrapper now that MultiListener implements HelloAcceptor directly
- removes the xgress_edge Acceptor.Run Create-loop, handing underlays to the MultiListener through the acceptor-based listener
- moves the controller ctrl connect handler into ListenerConfig.ConnectionHandlers
- updates ctrlchan channel tests to the new constructor
- decomposes the ctrlchan, xlink and edge-listener channels onto the v5 Senders, MessageSourceProvider and UnderlayEventListener interfaces, replacing the v4 UnderlayHandler god-interface
- replaces the hand-rolled dial/grouping/backoff machinery with channel.BackoffDialPolicy and declarative Constraints; ctrl keeps survive-to-zero (Min: 0) with MinStableDuration: 0 for prompt reconnect, while xlink and edge default underlays keep Min: 1 so loss closes the channel
- builds grouped channels via channel.NewChannel(*Config) and moves handler retrieval to GetSenders()
- records the channel via InitChannel from each bind handler, before underlay events fire, so handlers registered during bind do not dereference a nil channel
- generates a group secret for ungrouped inbound ctrl underlays on the router accept path, matching the controller, since NewChannel requires one
- preserves link-id-as-channel-id (the link dial policy wraps the cloned link-id identity dialer) and adds a test asserting dialed underlays present the link id
- registers the latency handler explicitly, as it is no longer a self-describing receiver in v5
- channel.MultiChannel -> channel.Channel
- channel.MultiChannelConfig -> channel.Config
- channel.NewMultiChannel(...) -> channel.NewChannel(...)
channel/v5 unifies Channel and MultiChannel into a single Channel abstraction. This is the
mechanical token rename; the Config field changes and handler retrieval that the unification
requires land in the following commit. The two channel.go files that are fully rewritten for
v5 (common/ctrlchan, router/xlink_transport) are excluded here and rewritten in that commit.
Does not build on its own.
- channel.TypedReceiveHandler -> channel.ContentTypeReceiver
- binding.AddTypedReceiveHandler(h) -> channel.AddReceiveHandlers(binding, h)
channel/v5 repurposes TypedReceiveHandler for the senders-typed handler and replaces the
self-describing pattern with ContentTypeReceiver plus the AddReceiveHandlers free function
(openziti/channel#262). Mechanical conversion; does not build on its own.
- channel.NewChannel(name, factory, bindHandler, opts) -> channel.NewSingleChannel(...)
- channel.NewChannelWithUnderlay(...) -> channel.NewSingleChannelWithUnderlay(...)
channel/v5 repurposes NewChannel for the unified multi-underlay constructor, so the
single-underlay call sites move to the renamed helpers. Mechanical rename only; like the
preceding import commit it does not build on its own.
- moves the channel dependency to channel/v5 v5.0.10 and sdk-golang to v1.9.0 in the root and zititest modules
- mechanically rewrites every channel/v4 import path to channel/v5
This is the import-path-only step; the API-level changes the switch requires land in the following commit. This commit does not build on its own.
- adds a `DataState.Router` event variant (id, name, fingerprint,
configs) to the RDM protobuf and the matching ConfigType.Target field
flows
- loads routers into `RouterDataModelSender` at startup and registers
entity-change listeners so router create/update/delete and config
reassignment emit RDM events
- filters `Config` events per-router at `RouterSender`: each router
sees the full `Router` set but only its own router-target Configs
- receiver-side: parses and stores `Router` entities; GCs router-target
Configs locally when they drop off the router's `Configs` list, so
no synthetic remove events are needed on the wire
- extends the `router-data-model-test` fablab model with router-config
distribution scenarios (assignment, reassignment, controller
restart, RDM cache miss)
- updates `validate-router-data-model` to recognize the new event
shape
- refreshes the design doc to describe per-router filtering and the
change-notification flow
* Add router.link.v1 config type. Fixes#3974
- adds the built-in `router.link.v1` ConfigType with a JSON schema
covering listeners, dialers, heartbeats, payload/ack sender queue
sizes, and `gcMode` for auto-GC of stale links
- targets routers (`Target=router`) via the field added in #3743
- registers it for new databases via `createConfigType` in
`initialize`, and for existing databases via a migration step that
bumps the schema version 46 -> 47
- adds config-type-store tests covering registration, the router
target, and migration-driven creation on existing databases
- documents the Phase 1c step in the controller-managed router
configuration design doc
* Address review feedback on router.link.v1 config type. For #3974
- broadens the duration schema pattern to accept compound and fractional values (e.g. 1h30m, 1.5h) matching time.ParseDuration
- adds the gcMode schema property (enum preserve/orphaned/changed) to the built-in config type so its definition is complete where the type is created
- adds duration-format and gcMode validation test cases
- removes the now-unnecessary per-iteration loop variable copy in the reject-cases test
- collapses the edge service boltz child store into the unified service store:
EdgeService becomes a type alias for Service, and RoleAttributes, Configs, and
EncryptionRequired become top-level Service fields
- adds an IsFabricOnly discriminator separating pure-fabric ("management")
services from edge services
- adds a forward-only, irreversible migration that moves edge service data out of
the edge child bucket, classifies pre-existing services (fabric -> IsFabricOnly
with encryptionRequired=false; edge -> IsFabricOnly=false), deletes the old edge
bucket and its index, and rebuilds the role-attributes index via CheckIntegrity
- guards every edge-facing path on the unified store against fabric-only services
so they stay invisible to the edge API: by-id Read/ReadByName/Delete, list/query
and the detail lister, ReadForIdentity, identity service-config overrides,
service policy / SERP @id and #all/role denormalization, the association-list
routes (terminators, policies, configs), and the router data-model sync
- rejects edge-surface updates of fabric-only services, and preserves edge fields
on fabric-surface updates of edge services
- marks the fabric-only guards TEMPORARY(fabric-edge-collapse): they exist only to
keep seldom-used management services hidden, and are removed when the fabric/edge
distinction is erased
- adds in-CI migration tests (forward plus a round-trip asserting FK sub-bucket
relocation and forward/reverse refcount preservation), durable fabric-only
behavior tests, and store-level policy-exclusion tests
- adds one-time authentic-migration gate tooling under zititest/migration-test
(create-model.sh, query, verify) and its operator runbook
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)
- adds HandleChannelConnection (server-side conn-to-channel upgrade) and the
client-side NewChannel/ConnToChannel/MakeChannelRequest helpers to common/agent;
these import channel/v4 + identity, which are already root-module deps, so there
is no go.mod or Go-version change
- replaces the duplicated agent channel-upgrade handlers in controller, router,
tunnel, and demo with one-line delegations to common/agent.HandleChannelConnection
- moves the CLI-side channel dialer (was NewAgentChannel/connToChannelMapper/
MakeAgentChannelRequest in agentcli) into common/agent and repoints the agentcli
and demo callers
- normalizes the demo echo-server to accept the AppIdAny wildcard, matching the
other agent servers; previously it rejected it
- preserves the wire protocol (leading app-id byte, "agent" channel, 1s connect
timeout), so existing channel-based agent commands are unchanged
* fixes#3952 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
* address pr review
- treats an externalIdClaim with no location as no claim in GetExternalId so an
empty stored claim (e.g. the empty bucket older CLIs leave behind) falls back to
fingerprint enrollment instead of erroring
- validates the merged result on CA update rather than the raw request: a partial
patch overlays only its supplied subfields onto the stored claim, an empty {}
object preserves the stored claim, and a full replace validates as-is
- skips externalIdClaim validation on update when no claim is supplied, fixing a
spurious rejection of updates to CAs that have no claim
- reports the offending location/matcher/parser in the externalIdClaim validation
error instead of the raw struct
- adds a --clear-external-id-claim flag to update ca and only sends the claim object
when a claim flag changed, so existing updates preserve the claim and clearing is
explicit
- tests externalIdClaim patch merge, preserve, and clear behavior, and that an
unconfigured claim reads as no claim
- adds management API endpoints listing role-attribute usage for identities,
edge routers, services, and posture checks, reporting per-source counts
(and optionally ids via withIds) across home-entity collections and the
policies that reference each attribute
- adds RoleAttributeUsage model with QueryRoleAttributeUsage, mapping each
RoleAttributeKind to its contributing sources, with all reads in a single
transaction so counts stay consistent with the attribute list; returns an
empty result (not a panic) for kinds with no attributes and an error for
unknown kinds
- adds boltz.SetIndexValueTransform and AddSetIndexWithTransform, letting a
SetIndex filter and rewrite symbol values without persisting a derived field
- adds derived role-attribute set indexes to the service policy, edge router
policy, and service edge router policy stores, built from existing role
fields via a role-attribute-only transform that excludes the #all wildcard
- adds a db migration that backfills the new indexes via each index's own
CheckIntegrity(fix=true), scoped to the role-attribute indexes only, with a
per-index summary log, stopping at the first error
- tests the new endpoints via the typed edge-api management client