Cert-Manager Audit
In Expired Certificates: Incident Review, I listed a future action: “Audit the cluster to see if there are any other TLS secrets that aren’t using cert-manager.” Here’s how I did it using Elixir Livebook.
Cert-manager metadata
A secret that’s been issued by cert-manager has various annotations. Here’s my gitea-tls
secret (ellided, obviously):
% kubectl --namespace gitea get secret gitea-tls -o yaml
apiVersion: v1
data:
ca.crt: ...
tls.crt: ...
tls.key: ...
kind: Secret
metadata:
annotations:
cert-manager.io/alt-names: git.k3s.differentpla.net
cert-manager.io/certificate-name: git-k3s-differentpla-net
cert-manager.io/common-name: ""
cert-manager.io/ip-sans: ""
cert-manager.io/issuer-group: ""
cert-manager.io/issuer-kind: ClusterIssuer
cert-manager.io/issuer-name: k3s-ca-cluster-issuer
cert-manager.io/uri-sans: ""
creationTimestamp: "2023-03-18T20:06:04Z"
name: gitea-tls
namespace: gitea
resourceVersion: "25878364"
uid: f51be88d-503e-46ff-8133-48be6ba09f6b
type: kubernetes.io/tls
So that’s how I’ll do it:
- Get all the secrets with
type: kubernetes.io/tls
. - Ignore the ones with cert-manager annotations.
If all of the TLS secrets are managed by cert-manager, the resulting list should be empty, right?
Well, no, there are other system-issued TLS secrets, but we can also ignore those.
Install dependencies
Mix.install([{:k8s, "~> 1.2"}])
Connect
{:ok, conn} = K8s.Conn.from_service_account()
:ok
Note that my Livebook installation has cluster admin access.
List TLS secrets
op = K8s.Client.list("v1", "Secret")
{:ok, secrets} = K8s.Client.run(conn, op)
tls_secrets =
secrets["items"]
|> Enum.filter(fn
%{"type" => "kubernetes.io/tls"} -> true
_ -> false
end)
Filter functions
issued_by_cert_manager? = fn
%{"metadata" => %{"annotations" => %{"cert-manager.io/issuer-name" => _}}} -> true
_ -> false
end
is_in_namespace? = fn %{"metadata" => %{"namespace" => namespace}}, n -> namespace == n end
is_named? = fn %{"metadata" => %{"name" => name}}, n -> name == n end
Run the query
tls_secrets
|> Enum.reject(fn secret ->
is_in_namespace?.(secret, "kube-system") or is_in_namespace?.(secret, "cert-manager") or
is_in_namespace?.(secret, "longhorn-system")
end)
|> Enum.reject(&is_named?.(&1, "erlclu-ca-key-pair"))
|> Enum.reject(issued_by_cert_manager?)
We ignore system-issued TLS secrets, those in the cert-manager namespace itself, and also those managed by Longhorn. The CA keypair for my Erlang cluster is also manually-managed.
That first Enum.reject
could probably be shortened by using Enum.any?/2
, but I’m suffering from a cold at the
moment, so my brain’s not really working properly.
The query should return an empty list.