Skip to main content
When a cluster’s API server is in a network Lumovi can’t reach (a private network, behind NAT, in a data center of your own), run Lumovi’s agent in it. The agent dials Lumovi over HTTPS, and Lumovi reaches the API server through that connection. Nothing in the cluster listens for Lumovi, and no firewall has to let anything in.

How it works

  • The agent connects to Lumovi’s api/agent with a WebSocket, naming its cluster and sending its token. Lumovi checks them against the agents it knows.
  • It tells Lumovi how to reach its cluster: the API server’s certificate authority, and its service account’s token. That service account may only impersonate users and groups.
  • Lumovi’s connections to the API server run through it. The agent connects each one to the API server, at the address Kubernetes gives its pod. TLS runs inside, from Lumovi to the API server: Lumovi checks the API server’s certificate against the cluster’s certificate authority, as kubernetes.default.svc, and the agent only passes on bytes it can’t read.
  • Lumovi acts as whoever signed in, impersonating them with the service account’s token, so the cluster applies their RBAC, as with any member.
  • Kubernetes rotates the token, and the agent reads it again every minute. When it changed, the agent sends Lumovi the new one.

Set it up

1

Make a token for the agent

The agent proves which cluster it is with a token. Lumovi only needs its SHA-256:
Hash the token itself, without a line break after it, as printf %s does.
2

Tell Lumovi about it

List the agents that may connect in LUMOVI_FLEET_AGENTS, as YAML, or that encoded in base64 on one line:
agents.yaml
With the Helm chart, put the list in a Secret, under agents.yaml, in Lumovi’s namespace, and name it in fleet.agentsSecret:
values.yaml
Lumovi reads the list when it starts: restart it after changing the list (kubectl rollout restart deployment/lumovi --namespace lumovi). Its pod doesn’t start until the Secret is there. Until the agent connects, the cluster’s card says Unreachable: Its agent isn't connected.
3

Install the agent in the cluster

With your kubeconfig pointing at the private cluster, store the token, and install the chart as an agent, with the name Lumovi knows it by:
agent.hubUrl is the address people open Lumovi at, with its base path if it has one. clusterName must be the agent’s name in Lumovi’s list: unless set, it’s in-cluster.The chart’s notes, after installing, print a command that gives the SHA-256 of the token in agent.tokenSecret, and the entry for Lumovi’s list, if you’d rather make the token here first.
4

See it connect

The agent’s log says when it’s connected:
Lumovi’s log says The agent of private-eu connected (Lumovi 1.1.0), and the cluster is on the fleet page.

The agents Lumovi knows

LUMOVI_FLEET_AGENTS (or the chart’s fleet.agentsSecret) is a list, each with:
string
required
The cluster’s name in the fleet, and the agent’s LUMOVI_AGENT_NAME (the chart’s clusterName). Two agents can’t have the same name.
string
The SHA-256 of the agent’s token, in hex: 64 characters. Lumovi’s settings then don’t hold the token itself.
string
The agent’s token itself, at least 32 characters, instead of tokenSha256.
object
The cluster’s labels, like { env: production }.
string[]
Only people in one of these groups see the cluster: groups as your provider or proxy names them, without prefixes. Everyone signed in does, without it, and an empty list hides it from everyone.
boolean
default:"false"
true: requests carry each person’s own token, for an API server that trusts your provider, instead of impersonating them. Lumovi must have their tokens: set LUMOVI_OIDC_FORWARD_TOKEN.
An agent’s cluster takes the server’s prefixes for the names it impersonates. A list that isn’t like this stops Lumovi, saying what’s wrong, like LUMOVI_FLEET_AGENTS[0] (private-eu) needs a token of at least 32 characters, or its tokenSha256.

What the chart makes

With mode: agent, the chart makes no Lumovi in the cluster, only its agent:
  • A Deployment, lumovi-agent for a release called lumovi, that runs the agent from Lumovi’s image. It has one replica, whatever replicaCount says, and is replaced with the Recreate strategy: a second agent of the same cluster would take over from the first.
  • A service account, lumovi, with a ClusterRole and a ClusterRoleBinding, lumovi-impersonate, that let it impersonate users and groups, and nothing else (unless rbac.create is false). Its token is mounted in the agent’s pod.
  • Health checks on port 8081. /healthz answers 200 connected while the agent is connected to Lumovi, and 503 not connected otherwise, so the pod is ready only while connected. It’s alive as long as the port answers.
There’s no Service: nothing connects to the agent. It requests 10m of CPU and 32Mi of memory, and is limited to 128Mi (agent.resources). The chart’s image, extraEnv, podSecurityContext, securityContext, nodeSelector, tolerations, affinity and priorityClassName apply to it too. See Helm values.

Run it another way

The agent is in Lumovi’s image, as /app/out/agent/agent.js. It runs in the cluster it relays to: it connects to the API server at KUBERNETES_SERVICE_HOST and KUBERNETES_SERVICE_PORT (443 unless set), which Kubernetes sets in every pod, and needs its pod’s service account token and CA certificate. Give its service account permission to impersonate users and groups, and nothing more, as a member’s has.
string
required
Lumovi’s address, with its base path if it has one: an http or https URL. The agent connects to api/agent below it, with a wss or ws WebSocket. Use https: the token travels in the connection’s headers.
string
required
The cluster’s name, as Lumovi’s list of agents has it.
string
required
The agent’s token. Spaces and line breaks around it are left out, so hash it without them.
number
default:"8081"
Where GET /healthz says whether the agent is connected. 0: nowhere.
number
default:"60"
How often the agent reads its service account’s token again, in seconds.
string
default:"/var/run/secrets/kubernetes.io/serviceaccount"
Where the pod’s service account token and CA certificate are.
Without one of the required settings (KUBERNETES_SERVICE_HOST among them), or without the service account’s token, the agent stops at once and says why:
When Kubernetes stops it, it closes its connection, and Lumovi’s log says The agent of private-eu disconnected.

Connecting, and staying connected

  • When it can’t reach Lumovi, or its connection closes, the agent tries again after 1 second, then 2, 4, 8 and 15, and every 30 seconds after that. Once connected, it starts again from 1.
  • When Lumovi restarts, for an upgrade say, it closes its agents’ connections, and they connect to the new one.
  • Lumovi pings its agents as often as it checks pages’ connections (LUMOVI_HEARTBEAT_SECONDS, every 30 seconds), and closes a connection that doesn’t answer.
  • The agent listens for those pings, as Lumovi tells it how often they come. A connection dropped somewhere on the way, without a word, goes quiet: after two heartbeats and a second without one (61 seconds), the agent says Nothing from the hub in 61 s, closes it, and connects again.
  • When Lumovi refuses it, the agent stops, as trying again wouldn’t help, and Kubernetes restarts it until it’s fixed:
While it’s away, Lumovi’s log says The agent of private-eu disconnected, and the cluster’s card says Its agent isn't connected. An agent that’s connected but can’t reach its own API server stays connected, and the cluster’s card says Unreachable, as for any cluster that doesn’t answer. Other problems reaching Lumovi are in the agent’s log, like Can't reach the hub at https://lumovi.example.com: …, then Connecting again in 4 s: it couldn't connect.

In front of Lumovi

Agents connect to api/agent below Lumovi’s base path, with a WebSocket, and keep it open. Whatever is in front of Lumovi (an ingress, a load balancer, a CDN) must pass WebSockets there, as it does for pages at api/socket, and keep idle connections open longer than the heartbeat. Run one replica of Lumovi: an agent connects to one of them.
  • Behind an authenticating proxy, let api/agent through to Lumovi without signing in (with oauth2-proxy, --skip-auth-route). Agents can’t sign in to a proxy, and Lumovi checks their names and tokens itself. A proxy that answers 401 makes an agent stop, saying Lumovi doesn’t know its name and token.
  • With the chart’s networkPolicy, its from must let in whatever agents come through, like your ingress controller or proxy.
  • A server of one cluster refuses agents, with 403: only a fleet takes them.

Security

  • Nothing in the cluster listens. The agent only dials out, to Lumovi, and has no Service.
  • The agent can’t read what it relays. Lumovi checks the API server’s certificate itself, end to end, against the certificate authority the agent sent.
  • Its service account may only impersonate, like a member’s, and Lumovi uses its token only while the agent is connected.
  • Whoever has an agent’s token can connect as its cluster. As the agent says which certificate authority to trust, they could stand in for the cluster with a server of their own, and see what people send it: their own tokens too, with forwardToken. Keep the token in a Secret in that cluster that only its administrators can read, and give Lumovi only its SHA-256 (tokenSha256), so Lumovi’s settings don’t hold it.
  • A takeover shows. When another agent connects with the same name and token, the first one stops, and its log says another agent connected as it. Unless two clusters were given the same name and token by mistake, that’s a sign the token is somewhere it shouldn’t be: make a new one.

A fleet of clusters

The fleet page, and the clusters Lumovi reaches itself.

Server configuration

Every fleet setting.