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.com pointing 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 mezite.yaml:
mezite.yamlyaml
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.

Register an internal web appbash
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
  • --uri is the internal address the agent dials — it never appears in DNS and needs no public route.
  • --public-addr must be a subdomain of the configuredapps_domain. The apex itself, hosts outside the domain, and host:port values are rejected.
  • --agent-hostname is 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 returns 502.
  • --labels drive RBAC (below). Use--protocol=tcp for non-HTTP services.

Manage the serving set without re-registering viamezctl apps add-agent /mezctl apps rm-agent, and inspect state withmezctl apps ls and mezctl apps get <name>. Take an app offline temporarily withmezctl apps disable <name> (re-enable withenable), or remove it withmezctl apps rm <name>.


Browser access

Users browse straight to the app —https://grafana.apps.example.com — or clickLaunch 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 setrequire_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:

Fetch the verification keysbash
# 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:

Open a TCP tunnelbash
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 yourmsh 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 ordeny:

platform-engineer.jsonjson
{
  "metadata": { "name": "platform-engineer" },
  "spec": {
    "allow": {
      "app_labels": {
        "env": ["prod", "staging"],
        "team": ["platform"]
      }
    }
  }
}

Users without a matching role don't see the app in the launcher ormsh 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.