Skip to main content
Version: 1.19.0 (latest)

kziti

kziti is a CLI tool for deploying and operating an OpenZiti environment for Kasm Workspaces. This page explains the architecture that kziti deploys on your behalf, the deployment shape, and what stays in raw OpenZiti's domain. For setup, see the quickstart.

Administrators grant access to specific named services rather than to network ranges, and connectivity is brokered through identity-bound routers. The OpenZiti primitives (controllers, routers, identities, policies) are the building blocks. The grouping (public versus private routers, networks, service sets) is what kziti layers on top.

For large or complex networks you may want to consider NetFoundry's official OpenZiti SAAS offering.

kziti Deployment Shape

kziti is an opinionated deployment:

  • Public routers are pure transit. They accept client connections at the edge and route fabric traffic. They do not host services.
  • Private routers run alongside the resources they expose. Outbound-only to the controller and public routers — no inbound firewall rules required at the resource site.
  • Networks are the unit of grouping. All resources for one team, customer, or environment live under one logical network. Access is granted at network, service-set, or individual-service granularity.
  • Identity types are explicit. Kasm users, Kasm workspaces, and external users are distinct types.

The shape of a deployment

A deployment has three kinds of OpenZiti component:

  • Controller(s): the control plane. Holds identities, services, and policies. Run with Raft consensus when you need high availability.
  • Public routers: the data plane at the edge. Public-facing listeners on a known port. Clients (Kasm sessions, external devices running the OpenZiti edge tunnel) connect here.
  • Private routers: the data plane next to your resources. Each private router is bound to a single network and makes only outbound connections.

Two kinds of host connect on the Kasm side:

  • Kasm sessions: dial services from inside their session container, using a Ziti tunnel that the egress sidecar manages.
  • External clients: (optional) run the OpenZiti edge tunnel on a laptop or other device. They dial the same services from anywhere, with the same access controls.

Tagging: how networks, services, and access are joined

OpenZiti uses role attributes as the join key between identities, services, and policies. kziti assigns role attributes consistently so that policies stay declarative. Adding or revoking access changes attributes, not policy bodies.

Router attributes

In OpenZiti a private router is two distinct objects: the edge router record (the control-plane entry) and the tunneler identity (the SDK identity the router uses to connect outbound as a connector). kziti assigns different attributes to each, because SERPs reference router record attributes and ERPs reference identity attributes.

Edge router record roleAttributes:

  • #role-dmz: public router (pure transit, no tunneler identity).
  • #role-private: private router.
  • #net-corp-a: marks this router as belonging to the corp-a network. Referenced by the per-network SERP to restrict which routers can carry corp-a service traffic.

Tunneler identity roleAttributes (private routers only):

  • #role-private: mirrors the router record.
  • #connector-corp-a: the hosting (Bind) role for the corp-a network. Referenced by two per-network policies: the ERP erp-corp-a-connectors (grants the connector identity access to #net-corp-a private routers, which it needs in order to host) and the Bind service policy sp-bind-corp-a (authorizes it to host #net-corp-a services). Scoped per-network so a router enrolled in corp-b cannot host corp-a services.

Service attributes

  • #net-corp-a: every service in the corp-a network.
  • #svcset-devops: a service set (a named, globally-scoped group of services that can span multiple networks). Service sets are not a first-class OpenZiti object; they are a shared attribute applied to the services in the group.
  • #svc-corp-a-gitlab: the individual service attribute.

Client identity attributes

Client identities (Kasm users, workspace images, external tunnel users) carry access attributes. The attribute name is the resource ID:

  • #net-corp-a: grants access to every service in the corp-a network.
  • #svcset-devops: grants access to every service carrying that service-set attribute.
  • #svc-corp-a-gitlab: grants access to a single service.

To grant or revoke access, kziti adds or removes the corresponding resource attribute on the relevant identity.

Identity types

kziti distinguishes three identity types by metadata. You will see all three in the controller after kziti has reconciled an entitlement:

  • Kasm user identity: created when an entitled Kasm user is mapped to an OpenZiti egress provider. Used when that user accesses services through a Kasm session.
  • Kasm workspace identity: created when a workspace, rather than a user, is the unit of entitlement. Useful when a workspace image needs access regardless of which user launches it.
  • External user identity: for users running the OpenZiti edge tunnel on their own device, outside Kasm. The same access controls apply; only the launch context differs.

All three types use the same role attribute scheme (#net-*, #svcset-*, #svc-*). They differ in how they are issued and where they are presented to the OpenZiti controller.

The three policy axes

OpenZiti has three independent policy types. All three must permit traffic before it flows. kziti generates and maintains these for you, but understanding the three axes helps you reason about why a connection succeeded or failed.

AxisQuestion it answers
Edge router policy (ERP)Which identities are allowed to use which routers to connect?
Service edge router policy (SERP)Which routers are allowed to carry traffic for which services?
Service policy (Dial / Bind)Which identities can connect to (Dial) or host (Bind) which services?

Three rules to keep in mind:

  1. All three must allow. A Dial permission with no matching ERP gives an identity nothing — the client cannot even reach a router to attempt the connection.
  2. Policies are additive, not subtractive. Granting matches add up; you cannot revoke with a deny rule. To take access away, remove the granting attribute or policy.
  3. Granularity lives in the Service Policy axis. kziti maintains a universal client ERP (#all#role-dmz) so any client can reach the DMZ routers, a per-network connector ERP (#connector-corp-a#net-corp-a) so the private router's tunneler identity can reach its own private router to host services, and a per-network Bind service policy (#connector-corp-a#net-corp-a services) to authorize hosting. SERPs are per-network (#net-corp-a services → #role-dmz + #net-corp-a routers). Every Dial entitlement you make is reflected in the Dial service policy: it grants access to any identity holding the matching #net-*, #svcset-*, or #svc-* attribute. Adding or revoking access is a single attribute change on the identity.

Multi-tenant isolation

The combination of policies provides hard tenant separation:

  • An identity without #net-corp-a, #svcset-*, or a #svc-corp-a-* attribute cannot reach any corp-a service. No Dial policy matches, so the controller refuses the connection before any router is involved.
  • Even with a matching Dial policy, traffic only flows if the SERP allows the chosen routers to carry that service's traffic. Misconfiguring a per-network SERP can limit routing options, but it cannot create cross-tenant access.
  • A private router for corp-a can only host (Bind) corp-a services. Even if compromised, it cannot be used as a pivot to host another tenant's services.
  • Public routers are pure transit. The universal client ERP that lets every identity reach any public router does not weaken isolation, because all three policy axes still gate traffic.

kziti's scope, and what stays in raw OpenZiti

kziti handles routers, role attributes, identities, and policies. Raw OpenZiti tooling remains available for auditing (ZAC, ziti edge read operations), custom topologies outside kziti's model, and recovery scenarios not covered by kziti's commands. If an operation feels like it should be a kziti command but isn't, report it.