Skip to content

Kubernetes

A Sentinel can read certificate material from a Kubernetes cluster through the API server. You give it a read-only ServiceAccount, and the credentials stay on the Sentinel’s host. They are never sent to NoCert.

One Sentinel can read up to 16 clusters. It does not have to run inside them.

Two passes per cluster, both read-only, no watch:

  • Secrets of type kubernetes.io/tls, listed with a server-side field selector. The API server applies the filter, so the list never returns another type.
  • Targeted reads of four service-mesh CA objects. Mesh CAs use their own types, so the filter above cannot see them. The list is exact: istio-ca-secret and cacerts in istio-system, linkerd-identity-issuer and the linkerd-identity-trust-roots ConfigMap in linkerd. A cluster without a mesh returns nothing.

Kubernetes control-plane PKI does not come through here. A Sentinel running on a node picks it up with its filesystem scan, from /etc/kubernetes/pki/ and the k3s and RKE2 directories.

The certificates, and enough to say where each came from: your cluster name, the namespace, the object name and type, the data key, and a flag when cert-manager manages the Secret.

Private keys do not leave. Kubernetes offers no field projection on Secret data, so a Secret arrives whole in the Sentinel’s memory, tls.key included. Only the allowlisted keys are read back out of it: tls.crt, ca.crt, and the mesh CA certificates. The wire format has no field that could carry a private key, and the agent is open source, so you can check that rather than take our word for it.

Kubernetes scanning is off by default, because it also needs credentials on the host. Open Sentinels, open the host that will read the cluster, and enable it there.

On the same page, select Add cluster. The wizard builds the manifest and gives you a kubectl apply command to run.

It grants get and list on Secrets, and get on ConfigMaps, to a ServiceAccount in a dedicated nocert-sentinel namespace.

Cluster-wide is the default. Namespace-scoped access is also offered, and it comes with two conditions:

  • You have to apply the namespace include list in NoCert. Without it the Sentinel keeps sweeping every namespace and RBAC denies the first sweep. The list applies to the whole Sentinel, not to one cluster, so applying it drops the out-of-scope findings of any other cluster that Sentinel reads.
  • The mesh CA objects live in istio-system and linkerd. Bind those namespaces too, or that second pass returns nothing.

Run this as root on the Sentinel’s host:

Terminal window
install -d -o nocert-sentinel -g nocert-sentinel -m 0700 /etc/nocert-sentinel/k8s
kubectl -n nocert-sentinel get secret nocert-sentinel-reader-token \
-o jsonpath='{.data.token}' | base64 -d > /etc/nocert-sentinel/k8s/prod-token
kubectl -n nocert-sentinel get secret nocert-sentinel-reader-token \
-o jsonpath='{.data.ca\.crt}' | base64 -d > /etc/nocert-sentinel/k8s/prod-ca.crt
chown nocert-sentinel:nocert-sentinel /etc/nocert-sentinel/k8s/prod-*
chmod 0600 /etc/nocert-sentinel/k8s/prod-*

Add it to /etc/nocert-sentinel/config.yaml:

allow_kubernetes_scan: true
kubernetes_clusters:
- name: prod
server: https://10.0.0.10:6443
ca_file: /etc/nocert-sentinel/k8s/prod-ca.crt
token_file: /etc/nocert-sentinel/k8s/prod-token

The name tags every finding from this cluster. Use lowercase letters, digits, dots, dashes and underscores, up to 64 characters.

An existing kubeconfig works if its user carries an inline token or a client certificate:

kubernetes_clusters:
- name: staging
kubeconfig: /etc/nocert-sentinel/k8s/staging.kubeconfig
context: staging

Two things a kubeconfig may not do. Exec plugins are rejected, so one that shells out to aws eks get-token will not load: the Sentinel runs no external binary to obtain a credential. tokenFile is rejected too, because a kubeconfig chooses its own server and CA, and an arbitrary file path would let it send the contents of any readable file as a bearer token. Use token_file in config.yaml instead, as above.

Restart when you’re done:

Terminal window
sudo systemctl restart nocert-sentinel

The cluster appears on the Sentinel’s page with its state. Expect No sweep reported yet until the Sentinel next polls.

A cluster whose last complete sweep is older than twice the cadence shows as stale rather than green, so roughly two hours by default. NoCert can’t tell a slow Sentinel from a dead one, so it doesn’t guess.

State What it means
Access forbidden (RBAC) The ServiceAccount lacks RBAC. Re-apply the RoleBinding or ClusterRoleBinding for this cluster.
Authentication failed The token was rejected. It may have expired, so extract it again from the reader Secret.
Unreachable The Sentinel’s host can’t reach the API server. Check the server URL and the network path.
Local config error The credentials wouldn’t load. Check server, ca_file and token_file, or the kubeconfig.
Timed out The API server didn’t answer in time. It may be overloaded, or the endpoint may be wrong.
Incomplete The sweep hit its size limit. Narrow the namespaces, or split the clusters across Sentinels.
Not swept An earlier cluster used up the sweep budget before this one was reached.
No longer reported Renamed or removed from this Sentinel. Its findings stay until a complete sweep reconciles them.

Set allow_kubernetes_scan: false on the host and restart. The Sentinel then refuses the scan even when NoCert asks for it. Removing the entry from kubernetes_clusters has the same practical effect, and the cluster moves to No longer reported.