MacVz turns an Apple Silicon Mac into a Kubernetes virtual node that runs OCI
workloads as isolated Linux micro-VMs via apple/container. This document
states the security boundaries, conservative defaults, and the operator's
responsibilities for a beta deployment (issue #28).
Kubernetes API server ──TLS──▶ macvz-kubelet (host process on the Mac)
▲ │
│ mutual TLS (logs/exec/ │ local unix socket / CLI
│ portforward/stats) ▼
kubectl user apple/container service ──▶ micro-VMs
- macvz-kubelet → API server: outbound, authenticated by the kubeconfig / in-cluster ServiceAccount token over the cluster's normal TLS. No bespoke auth path; standard client-go credential flows only.
- API server → kubelet endpoint: inbound to the Mac for
kubectl logs/exec/port-forwardand metrics scraping. This is the most sensitive surface and is hardened below. - macvz-kubelet → apple/container: local only (CLI over the host's unix socket). Not a network listener.
Access uses ordinary kubeconfig or in-cluster configuration
(config.RestConfig), so connections to the API server use client-go's standard
TLS (server CA verification + client credential). MacVz adds nothing here and
hardcodes no endpoints or tokens.
Operator responsibility: provide a kubeconfig that points at the
macvz-kubelet ServiceAccount (or an equivalently scoped identity), not a
cluster-admin credential, in production.
This HTTPS endpoint exposes powerful operations — exec is arbitrary in-guest
command execution. It is hardened in three ways:
- Mutual TLS. Set
node.servingClientCAFileto the cluster's API-server client CA. The server then requires and verifies a client certificate signed by that CA (RequireAndVerifyClientCert), so only the API server can call it. - Bind address. When
node.internalIPis set (or auto-detected), the server binds to that address rather than all interfaces, shrinking exposure. - Restart tolerance. If a fast restart races the old process releasing
kubeletPort, startup retriesEADDRINUSEbriefly instead of failing immediately. - Loud default. With no client CA configured, macvz-kubelet logs a prominent warning at startup that the endpoint is unauthenticated.
Operator responsibility: in production, always set servingClientCAFile. If
you leave it unset (e.g. local dev), restrict the kubelet port by firewall to
the API server only. The endpoint is disabled entirely when no serving
certificate is configured (Pods still run; logs/exec are unavailable).
node:
internalIP: 10.0.0.42
kubeletPort: 10250
servingTLSCertFile: /etc/macvz/serving.crt
servingTLSKeyFile: /etc/macvz/serving.key
servingClientCAFile: /etc/macvz/apiserver-client-ca.crt # enables mutual TLSdeployments/rbac.yaml is scoped to exactly what the Virtual Kubelet node and pod controllers invoke, verified against the upstream controller source:
| Resource | Verbs | Why |
|---|---|---|
nodes |
get, list, watch, create | register and read the node; never deleted by macvz |
nodes/status |
get, patch, update | maintain conditions/addresses/capacity |
pods |
get, list, watch, delete | reconcile Pods; delete when the workload is gone |
pods/status |
get, update, patch | report Pod status |
configmaps, secrets, services |
get, list, watch | required by the upstream pod-controller informers |
events |
create, patch, update | Pod/node lifecycle events |
leases |
get, create, update | node heartbeat in kube-node-lease |
delete on nodes and leases is intentionally not granted (the controllers
never call it), nor is write access to the Pod spec.
Secrets note: the broad secrets read is the one rule wider than MacVz's own
needs — MacVz does not materialize Secret contents (env valueFrom and
configMap/secret volumes are rejected; see VOLUMES.md), but the
upstream pod controller constructs a Secret informer regardless. If your cluster
supports it, scope this via per-namespace Roles instead of a cluster-wide read,
or run on a Kubernetes build where the informer can be disabled. This is a known
beta limitation, documented rather than silently granted.
The apple/container runtime is driven through its CLI over a host-local unix
socket; macvz-kubelet opens no network listener for runtime control. The runtime
socket must not be proxied or bound to a network address. Each workload runs in
its own micro-VM (hardware-isolated), not a shared kernel namespace.
- No secret/credential logging. Startup and lifecycle logs record image refs, IDs, and config flags — never env values, tokens, or kubeconfig contents.
- Pod env injection is conservative. Env
valueFrom(Secret/ConfigMap/field refs) is rejected by translation, so MacVz never reads Secret material to inject into a guest. Only literal env values are passed. - Registry credentials.
imagePullSecretsare not consumed. Private registries must be authenticated at the host level via thecontainertooling's own credential store, keeping registry secrets off the Pod path.
- Run macvz-kubelet under the
macvz-kubeletServiceAccount, not cluster-admin. - Set
servingClientCAFile(mutual TLS) — or firewall the kubelet port to the API server. - Set
node.internalIPso the kubelet endpoint binds to one address. - Keep
node.volumes.hostPathAllowedPrefixesempty unless hostPath is needed; keep it tightly scoped when it is (see VOLUMES.md). - Store serving keys and kubeconfig with
0600perms, outside any image or git. - Authenticate private registries on the host, not via
imagePullSecrets.