Commit Graph

1678 Commits

Author SHA1 Message Date
Paul Lorenz e4f2d18479 Manage router link configuration via the controller. Fixes #4004
- 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.
2026-08-06 12:37:09 -04:00
Paul Lorenz a003f18792 Remove unused link costTags support
- 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
2026-08-03 18:03:26 -04:00
Paul Lorenz 4f7ff884f5 Merge pull request #4209 from openziti/fix-connection-tracker-deadlock
Fix lock order inversion in ConnectionTracker
2026-08-03 16:08:58 -04:00
Paul Lorenz eb669e6e10 Merge pull request #4194 from openziti/issue-4193-ert-terminator-error-reporting
Make ER/T terminator create failures diagnosable
2026-08-03 16:06:41 -04:00
Paul Lorenz d7076430c9 Make ER/T terminator create failures diagnosable. Fixes #4193
- 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
2026-08-03 15:43:30 -04:00
Paul Lorenz 0451ca7ebc Fix lock order inversion in ConnectionTracker. Fixes #4206
- 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
2026-07-31 14:08:08 -04:00
Paul Lorenz 538f0d31ec Merge pull request #4080 from openziti/issue-4079-disconnect-router-reroute-ordering
Mark router disconnected before rerouting its links
2026-07-30 11:46:53 -04:00
Paul Lorenz f3a27b32a8 Validate router certificates on typed control channel connections
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
2026-07-27 17:40:35 -04:00
Paul Lorenz 898c95b217 Verify peer certificate chains against the leaf on control-plane connections
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
2026-07-27 17:39:48 -04:00
Andrew Martinez e7d23ef0ae fixes openziti/ziti#3990 push service and posture changes to subscrib… (#4057)
* fixes openziti/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
2026-07-22 11:52:39 -04:00
Paul Lorenz 95d6210e22 Copy eventual event data out of bolt memory. For #4108
- 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
2026-07-15 19:12:28 -04:00
Paul Lorenz 0cefe735db Copy terminator peer data and instance secret out of bolt memory. Fixes #4108
- 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
2026-07-15 18:57:09 -04:00
Björn Mage 4a8b63d99d fix(oidc_auth): hex-encode peer signer kid in KeySet() (#4068)
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>
2026-07-07 20:23:06 -04:00
Andrew Martinez 45b5046f52 fixes openziti/ziti#4071 unify router capabilities into one shared namespace (#4073)
- 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
2026-07-02 17:02:34 -04:00
Paul Lorenz ba9060ae2b Mark router disconnected before rerouting its links. Fixes #4079
- 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
2026-07-02 15:53:23 -04:00
Paul Lorenz 4d3b303f65 Defer xweb initialized signal until config is loaded. Fixes #4069
- 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
2026-07-02 10:28:13 -04:00
Paul Lorenz 187aa11f24 Own the metrics wire format in ziti. Fixes #4036
- 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
2026-06-29 22:37:25 -04:00
Paul Lorenz 5bef2b8c3a Merge pull request #4038 from openziti/issue-4035-prometheus-ctrl-metric-labels
Emit source_entity_id label on prometheus metrics. Fixes #4035
2026-06-29 13:12:58 -04:00
Paul Lorenz 2799c00191 Merge pull request #4042 from openziti/link-manager-striped-locking
Add striped per-link locking and cache-before-txn router reads
2026-06-29 13:12:36 -04:00
Paul Lorenz cba63836f9 Merge pull request #4028 from openziti/connect-v2
Implement connect-v2
2026-06-29 13:07:11 -04:00
Paul Lorenz 863f6c90fe Merge pull request #4029 from openziti/sdk-golang-v2
Migrate to sdk-golang v2 module path
2026-06-29 13:06:36 -04:00
Paul Lorenz 474420d5c6 Merge pull request #4030 from openziti/issue-3929-revocation-max-token-duration
Use MaxTokenDuration for identity revocation lifetime
2026-06-29 13:05:56 -04:00
Paul Lorenz a9664eb0c5 Merge pull request #3991 from openziti/3841-firewalled-cluster-join
Harden and clarify firewalled controller cluster join
2026-06-29 11:50:05 -04:00
Paul Lorenz 18cf6ea5c1 Harden and clarify firewalled controller cluster join. Fixes #3841
- 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
2026-06-29 11:45:48 -04:00
Andrew Martinez 1b180d14e8 fix #3933 add controller to enrollment response (#3947)
* 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
2026-06-29 11:44:23 -04:00
Paul Lorenz 158e52b60e Merge pull request #4013 from openziti/issue-4010-enrollment-priv-escalation
Prevent enrollment-based privilege escalation to admin identities. Fixes #4010
2026-06-26 13:13:18 -04:00
Paul Lorenz 5fb197935d Deny enrollment access on identity read error. For #4010
- returns false (denies) instead of true when reading the target
  identity fails, so an error cannot be treated as a non-admin identity
2026-06-26 12:07:31 -04:00
Paul Lorenz 2ee646c1c4 Add striped per-link locking and cache-before-txn router reads. Fixes #4045
- 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
2026-06-26 11:51:00 -04:00
Paul Lorenz a41344c2ca Emit source_entity_id label on prometheus metrics. Fixes #4035
- 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
2026-06-25 15:18:03 -04:00
Paul Lorenz 67eba590e2 Implement connect-v2. Fixes #3884
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.
2026-06-24 16:40:40 -04:00
Paul Lorenz e168a50d2a Use MaxTokenDuration for identity revocation lifetime. For #3929
- uses Oidc.MaxTokenDuration() instead of RefreshTokenDuration so the
  revocation lifetime always covers the longest-lived token
2026-06-24 15:39:30 -04:00
Paul Lorenz 86092a8640 Migrate to the sdk-golang v2 module path. For #3884
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.
2026-06-23 15:43:39 -04:00
Paul Lorenz 2dc4075446 Make listener ctrl channels multi-underlay-capable. For #3983
- 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
2026-06-22 14:53:47 -04:00
Paul Lorenz 3027fdffd3 Prevent enrollment-based privilege escalation to admin identities. Fixes #4010
- 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
2026-06-19 12:27:53 -04:00
Paul Lorenz 1c877e2501 Migrate to channel/v5 deferred-ack accept API. For #3983
- 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
2026-06-18 12:51:02 -04:00
Paul Lorenz d481224c9b Decompose multi-underlay channels onto the channel/v5 API. Fixes #3983
- 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
2026-06-18 12:51:02 -04:00
Paul Lorenz ee8ad78e3b Rename MultiChannel to the unified Channel for channel/v5. For #3983
- 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.
2026-06-18 12:51:02 -04:00
Paul Lorenz ae806045b5 Convert self-describing receive handlers to channel/v5. For #3983
- 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.
2026-06-18 12:51:02 -04:00
Paul Lorenz 08aa26a051 Rename single-underlay channel constructors for channel/v5. For #3983
- 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.
2026-06-18 12:51:02 -04:00
Paul Lorenz 1c122af490 Rewrite channel/v4 imports to channel/v5. For #3983
- 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.
2026-06-18 12:51:02 -04:00
Paul Lorenz 0ee288ecbf Distribute routers and router-target configs through the RDM. Fixes #3976 (#3977)
- 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
2026-06-16 22:31:43 -04:00
Paul Lorenz 1d6333a6c0 Add router.link.v1 config type. Fixes #3974 (#3975)
* 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
2026-06-16 16:38:13 -04:00
Paul Lorenz f18ce1b580 Collapse fabric and edge services at the db level. Fixes #3934
- 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
2026-06-16 13:04:13 -04:00
Paul Lorenz ff619272ba Enforce api-session and identity revocations on the router. Fixes #3927
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)
2026-06-13 01:36:12 -04:00
Paul Lorenz 9483c39bbd Consolidate agent channel plumbing into common/agent. Fixes #3894
- 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
2026-06-12 15:47:03 -04:00
Andrew Martinez 9470c1f126 fixes #3952 reject invalid externalIdClaim and stop enrollment panic (#3953)
* 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
2026-06-12 13:52:54 -04:00
Paul Lorenz cdc5d89885 Add configs field to routers. Fixes #3780 2026-06-11 13:45:58 -04:00
Paul Lorenz 0ba057e1bf Merge pull request #3918 from openziti/extend-attribute-search
Add role-attribute usage queries. Fixes #1593
2026-06-10 13:00:40 -04:00
Paul Lorenz a582fe5d0f Add role-attribute usage queries. Fixes #1593
- 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
2026-06-10 12:39:26 -04:00
Paul Lorenz df2054e003 Merge pull request #3950 from openziti/fix-delete-trigger-err-return
Return event-firing error in DeleteById instead of swallowing it
2026-06-09 23:38:03 -04:00