Skip to content

Flow graph and route matrix

The flow graph and the route matrix are two views over the same idea: every reachable path from a SCIM group to an application segment.

The graph

Five columns, left to right:

SCIM groups -> access policies -> connector groups -> segment groups -> segments

Each node is one resource. Each edge represents a real ZPA reference:

  • SCIM group -> policy: the policy’s condition mentions the SCIM group’s ID on the RHS of a SCIM_GROUP operand.
  • Policy -> connector group: the policy targets a server group, and that server group is bound to the connector group.
  • Policy -> segment group: the policy’s segment scope (direct segments and segment groups).
  • Segment group -> segment: ZPA’s one-to-one constraint. A segment belongs to exactly one segment group.

Click a node and every path it participates in is highlighted. Layout is ELK-powered so it stays sane on large tenants.

POST /api/v1/graph returns the filtered graph for the current selection. The body lets you scope to one starting node or one segment.

The route matrix

GET /api/v1/routes returns the full enumeration:

type RouteMatrix struct {
Routes []Route
}
type Route struct {
ScimGroupID string
ScimGroupName string
PolicyID string
PolicyName string
Action string // ALLOW | BLOCK_ACCESS
SegmentID string
SegmentName string
SegmentGroupID string
ConnectorGroupIDs []string
}

The UI renders this as a filterable table. Click any cell value and every other route through that node gets filtered in.

What this answers

  • “Who can hit application X?” - filter by SegmentID, read the ScimGroupName column.
  • “What can user group Y reach?” - filter by ScimGroupID, read the SegmentName column.
  • “Which policies are even doing anything?” - sort by PolicyID, look for policies with zero or one route (likely orphaned or scoped too tight).

Implementation

internal/analysis/flow.go builds the route set by traversing the inverted indexes from the index layer. It does not call ZPA. The cost is one map walk per dimension - fast enough that we just rebuild on demand instead of caching the matrix.