Application Access
Mezite can publish internal applications — a Grafana dashboard, an
internal wiki, a private API, a Redis instance — to your users without a
VPN and without opening any inbound port into the network where the app
runs. The mezd agent next to the application keeps an
outbound reverse tunnel to the proxy; the proxy authenticates the user
in the browser via your SSO connector, checks role-based access, and
forwards the request down the tunnel.
Each enrolled application gets:
-
A public hostname (
https://grafana.apps.example.com) gated by your existing SSO login and, when a role demands it, per-session MFA. - Label-based RBAC — users only see and reach the apps their roles allow.
- A signed identity assertion on every forwarded request, so the backend can trust who the caller is without running its own SSO.
- An audit trail of every request and session.
Prerequisites
Self-hosted clusters need three things before enrolling apps:
- An apps domain. Pick a parent domain for app
hostnames (e.g.
apps.example.com) and publish a wildcard DNS record*.apps.example.compointing at the proxy. - A wildcard TLS certificate covering
*.apps.example.com— or a multi-SAN certificate listing each app hostname, if your policy forbids wildcards. - Configuration in
mezhub.yaml:
proxy:
apps_domain: apps.example.com
# Optional: a dedicated keypair for the apps wildcard. If omitted, the
# proxy's main HTTPS certificate must cover *.apps.example.com in its SANs.
apps_keypair:
cert_file: /etc/mezite/apps-wildcard.crt
key_file: /etc/mezite/apps-wildcard.key
# Optional: which SSO connector gates browser app access.
apps_oidc_connector: sso
If apps_domain is unset, application access is inactive —
nothing else about the cluster changes.
On managed clusters all of this
is pre-provisioned: the apps domain is your cluster's own hostname, the
wildcard DNS record and certificate are issued automatically, and apps
are reachable at <app>.<cluster>.hub.mezite.com.
Enroll an application
Applications are registered centrally — with mezctl or in
the web dashboard under Applications — and served by
one or more agents you bind to them. The agent needs network reach to
the app's internal address; nothing app-specific is installed on the
agent host.
mezctl apps register \
--name grafana \
--uri http://10.0.3.12:3000 \
--public-addr grafana.apps.example.com \
--labels env=prod,team=platform \
--agent-hostname node-1 \
--agent-hostname node-2 -
--uriis the internal address the agent dials — it never appears in DNS and needs no public route. -
--public-addrmust be a subdomain of the configuredapps_domain. The apex itself, hosts outside the domain, andhost:portvalues are rejected. -
--agent-hostnameis repeatable: bind two or more agents and the proxy load-balances across whichever are connected, falling through to the next live agent if a dial fails. An app with zero bound agents stays registered but returns502. -
--labelsdrive RBAC (below). Use--protocol=tcpfor non-HTTP services.
Manage the serving set without re-registering via
mezctl apps add-agent /
mezctl apps rm-agent, and inspect state with
mezctl apps ls and mezctl apps get <name>.
Take an app offline temporarily with
mezctl apps disable <name> (re-enable with
enable), or remove it with
mezctl apps rm <name>.
Browser access
Users browse straight to the app —
https://grafana.apps.example.com — or click
Launch on the Apps page of the
dashboard. An unauthenticated visit redirects through your SSO
connector, then back to the app with a short-lived session scoped to
that app's hostname only. Roles that set
require_session_mfa are prompted for a WebAuthn assertion
before the session is created.
Sessions are revocable server-side: locking a user or revoking the session cuts off app access immediately.
Trusting the caller's identity in your app
Every forwarded request carries a short-lived, asymmetrically signed
JWT in the Mezite-Jwt-Assertion header, plus convenience
headers X-Mezite-User and X-Mezite-Roles.
These headers are always overwritten by the proxy, so a client can
never spoof them. The JWT's aud claim is the app's
internal URI; sub is the username, and the user's roles
ride along as a claim.
Backends verify the JWT against the cluster's public key set:
# Online — from the proxy:
curl https://mezite.example.com/workload-identity/app-jwt-jwks.json
# Offline — for backends with no outbound internet:
mezctl auth export --type=app-jwt > app-jwt-jwks.json Verification is optional — apps that don't check the JWT still get the SSO gate, RBAC, and audit. Checking it protects against traffic that reaches the backend from elsewhere inside the network.
TCP applications
Non-HTTP services tunnel through a local listener. Register the app
with --protocol=tcp, then:
msh app login redis-internal --port 6380
# Tunnel open: 127.0.0.1:6380 -> redis-internal
redis-cli -p 6380 ping
The tunnel authenticates with the certificate from your
msh login session and is authorized per-app at the proxy.
Roles that require per-session MFA or a trusted device are refused on
the TCP path rather than silently downgraded. List the apps you can
reach with msh app ls.
Restricting access with labels
Roles match application labels the same way they match node labels —
with app_labels under allow or
deny:
spec:
allow:
app_labels:
env: ["prod", "staging"]
team: ["platform"]
Users without a matching role don't see the app in the launcher or
msh app ls, and direct requests are denied with an audit
record.
Audit
Application activity lands in the cluster audit log:
app.request— every forwarded HTTP request.-
app.session.start/app.session.end— bracket TCP sessions with bytes and duration. -
access.denied— RBAC rejections, including TCP attempts against MFA- or device-trust-protected apps. -
app.enabled/app.disabled— operator toggles.
mezctl audit ls --type=app.request Next steps
- RBAC — Role syntax, label matchers, and per-session MFA options.
- SSO (OIDC) — Configure the connector that gates browser app access.
- Audit Logging — Query and export application audit events.