What runs in your cluster
You are being asked to give an admission webhook a say over every pod. This page is the full inventory of what that costs: the workloads, the permissions, the network paths — and the honest answer to what our own infrastructure could do to you if it were compromised.
Workloads
| Object | Default | What it does |
|---|---|---|
Deployment attestkeep | 2 replicas, anti-affinity preferred across nodes, PDB minAvailable: 1 | The operator: admission webhook (8443), console and API (8080), metrics (9090). Controller work is serialised through a PostgreSQL advisory lock, so replicas scale without a leader-election lease. |
Deployment attestkeep-scanner | 1 replica | Runs Trivy scans and cosign verification off the admission path. Never receives admission traffic. |
StatefulSet attestkeep-postgresql | 1 replica, 20 GiB, postgres:16.4-alpine | The bundled database. Renders only when you have not set externalDsn — point that at your own PostgreSQL and this object does not exist. |
All product containers run as non-root (uid 65532), with a read-only root filesystem, all capabilities dropped, and RuntimeDefault seccomp. Requests default to 100m CPU / 256Mi per product workload. Everything lives in the release namespace; nothing is installed outside it except the two cluster-scoped objects below.
Cluster-scoped objects
- Four CRDs in the
attestkeep.comgroup:ImageSecurityPolicy,ImageSecurityReport,AttestationRenewal,ImageSecurityUsage. - One
ValidatingWebhookConfiguration: pod CREATE and UPDATE only,failurePolicy: Ignoreby default (see Admission for the trade), 15-second timeout, and three escape hatches — the bypass namespace list (kube-system,cert-manager, its own namespace), aattestkeep.com/webhook=bypassnamespace label, and an object selector that keeps the operator's own pods out of their own admission loop. There is no mutating webhook: this product never rewrites your workloads.
RBAC — every permission and its reason
One ServiceAccount, one ClusterRole, one namespaced Role. This is the complete list; there is nothing else.
| Permission | Scope | Why |
|---|---|---|
attestkeep.com CRDs: full CRUD + status | Cluster | Policies, reports and usage records are CRDs; the operator manages its own resources. |
pods: get, list, watch; nodes, namespaces: get, list | Cluster | The reconciliation sweep has to see what is actually running to compare it with what was admitted. |
secrets: get only — deliberately not list | Cluster | Registry credentials. The operator can read a pull secret whose name it learned from a pod specification; it cannot enumerate what a namespace holds. Credentials are used at scan time and never stored. |
pods/eviction: create | Cluster, only if rbac.allowEnforce: true (default false) | Runtime enforcement is armed with two keys: the policy chooses enforce mode and the chart grants the permission. A compromised operator on an audit-only install has nothing to evict with. Eviction, not delete, so PodDisruptionBudgets are honoured. |
validatingwebhookconfigurations: update | Cluster, restricted by resourceNames to its own configuration | Publishing and rotating its own CA bundle. It cannot touch any other webhook in the cluster. |
secrets: write | Namespace, restricted by resourceNames to attestkeep-webhook-tls and attestkeep-evidence-key | The serving certificate and the evidence signing key. The operator creates and rotates exactly these two Secrets in its own namespace. There is no delete verb on Secrets anywhere in the role set. |
Every outbound connection
This is the table for your firewall ticket. Nothing else leaves the cluster — there is no analytics, no crash reporting, no telemetry SDK. The one version string that leaves is the operator version on the licence check.
| Destination | Port | Required? | Knob | If blocked |
|---|---|---|---|---|
lic.attestkeep.com | 443 | Yes | license.serverUrl | Activation is impossible; an activated install keeps running on its held certificate and degrades to Community when that certificate expires. See Licensing. |
ghcr.io (Trivy vulnerability DB) | 443 | For scanning | trivy.dbRepository — mirror it into your own registry | The existing database keeps working and ages; attestkeep_vulnerability_db_age_seconds tells you by how much. |
| Your image registries | 443 | For scanning | Pull secrets resolved from pod specs; scan.insecureRegistries for HTTP | Scans fail for images there; admission behaviour follows your policy's cold-image setting. |
ghcr.io/attestkeep/attestkeep-k8s (kubelet image pull) | 443 | At install | image.repository — mirror it | Pods cannot start. |
| Sigstore TUF + public Rekor (via cosign) | 443 | No | scan.cosignOffline: true | With keyed signing and offline mode on, verification stays local. See Air-gapped. |
| Release feed | 443 | No — off by default, no default host | updates.endpoint | An installation that was never told where to look never looks. Report-only even when on; nothing is downloaded. |
| Your OTLP collector | 4317/4318 | No — off by default | OTEL_EXPORTER_OTLP_ENDPOINT | Exporters are never started. |
| Your Slack/Teams/webhook URLs, your SMTP server | 443 / 587 / 465 | No | Configured per channel in the console | Sends fail and are logged; admission is never blocked by a notification. Webhook posts refuse loopback and link-local targets at dial time, so a channel URL cannot be pointed at cloud metadata. |
| Your SSO identity provider | 443 | No — Enterprise, config-gated | sso.* | That sign-in method fails; local accounts are unaffected. |
The chart ships no NetworkPolicy: your perimeter is yours to define, and the table above is exactly what an egress policy for this namespace needs to allow.
If our licence server were compromised
The question a security team should ask about any vendor with a phone-home path. The honest answer, from how the client is built:
- It cannot instruct your cluster. The licence protocol has two response shapes: a certificate and a renewal status. There is no code path in the operator where a licence-server response selects a policy, a namespace, an image rule, a URL, or an executable. Nothing is ever downloaded from it.
- It cannot grant itself anything. Certificates are Ed25519-signed and verified against a public key compiled into the binary at build time — EdDSA only, so
algdowngrade tricks are out by construction. Certificates are also bound to your cluster's fingerprint: a certificate issued for another cluster does not verify on yours. - What it could do is turn your entitlement off. A hostile server can answer
revoked, and the operator honours that answer. The result is a downgrade, not a takeover — and you should know its exact shape: a deactivated installation stops enforcing at admission and admits with a warning, on the reasoning that a licensing failure must never take down your deployments. If someone can speak for our licence server to your cluster, the blast radius is your enforcement switching off — visibly, in the console and in every admission response warning — never a workload it placed or a policy it changed. - Silence does nothing. Network errors and server failures are deliberately read as outages, not as answers: a bad deploy of the licence server cannot revoke customers. Your install runs on its held certificate until that certificate's own expiry.
The evidence signing key is generated inside your cluster and never issued by the licence server; the server's certificate merely names your key so third parties can tie the two together. See the evidence trust model.