infra/stacks/kyverno/modules/kyverno/registry-credentials.tf
Viktor Barzin 90e074a4a2 kyverno(wave1): swap kubernetes_manifest → kubectl_manifest + flip 3 security policies to Enforce
## Resolves code-e2dp (Kyverno TF apply blocked)
Root cause: terraform-provider-kubernetes v3.1.0 panics on plan/refresh of
kubernetes_manifest resources holding Kyverno ClusterPolicy CRDs (large
CEL/foreach schemas). Workaround: swap to gavinbunney/kubectl_manifest which
treats manifests as opaque YAML strings.

## Migration mechanics
- Root terragrunt.hcl: added gavinbunney/kubectl provider declaration so all
  stacks get it generated in providers.tf.
- stacks/kyverno/modules/kyverno/versions.tf (new): module-level provider source
  declaration (required for kubectl_manifest in a child module).
- Converted 17 kubernetes_manifest resources across 7 files to kubectl_manifest
  with yaml_body = yamlencode({...}). depends_on chains preserved.
- terraform state rm for all 17 old kubernetes_manifest entries.
- stacks/kyverno/imports.tf (new): TF 1.5+ import blocks mapping each
  kubectl_manifest to its live cluster resource by apiVersion//Kind//name ID.
- One resource (policy_inject_keel_annotations) needed kubectl delete + recreate
  because the kubectl provider couldn't patch it cleanly (resourceVersion=0
  invalid for update — gotcha when adopting a resource previously
  kubernetes_manifest-owned).

## W1.4 — security policies Audit → Enforce (LIVE)
Three policies flipped: deny-privileged-containers, deny-host-namespaces,
restrict-sys-admin. Verified live via kubectl. failurePolicy=Ignore preserved.

## Shared exclude list (35 namespaces)
local.security_policy_exclude_namespaces in security-policies.tf.
- 31 critical from memory id=1970 (Keel rollout list)
- + frigate (camera HW transcoding needs host access)
- + kured (privileged DaemonSet for node reboots)
- + default (etcd backup/defrag CronJobs use hostNetwork)
- + changedetection (uses SYS_ADMIN for chromium sandbox)

## W1.5 — require-trusted-registries stays Audit
Pattern */* allows anything-with-a-slash; Enforce would be a no-op for supply
chain. Tracked under beads code-8ywc as follow-up.

## TF import-blocks
The imports.tf file should be removed in a follow-up cleanup commit once
verified — TF doesn't auto-clean these.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

Closes: code-e2dp
2026-05-18 20:10:27 +00:00

135 lines
4 KiB
HCL

# =============================================================================
# Private Docker Registry Credentials — Auto-sync to all namespaces
# =============================================================================
# Source secret in kyverno namespace, cloned by ClusterPolicy into every NS.
# Pods use imagePullSecrets: [{name: registry-credentials}] to pull from
# registry.viktorbarzin.me (or 10.0.20.10:5050 internally).
data "vault_kv_secret_v2" "viktor" {
mount = "secret"
name = "viktor"
}
resource "kubernetes_secret" "registry_credentials" {
metadata {
name = "registry-credentials"
namespace = kubernetes_namespace.kyverno.metadata[0].name
}
type = "kubernetes.io/dockerconfigjson"
data = {
".dockerconfigjson" = jsonencode({
auths = {
# Phase 4 of forgejo-registry-consolidation 2026-05-07 — registry-
# private decommissioned. Old auths entries (registry.viktorbarzin.me,
# registry.viktorbarzin.me:5050, 10.0.20.10:5050) removed to prevent
# silent fallback. If a pod somehow references the old hostname now,
# it will visibly fail with auth missing rather than silently pulling
# potentially-stale blobs.
"forgejo.viktorbarzin.me" = {
auth = base64encode("cluster-puller:${try(data.vault_kv_secret_v2.viktor.data["forgejo_pull_token"], "")}")
}
}
})
}
}
# Grant Kyverno controllers permission to manage Secrets (needed for generate clone rules)
resource "kubernetes_cluster_role" "kyverno_secret_manager" {
metadata {
name = "kyverno:secret-manager"
labels = {
"app.kubernetes.io/instance" = "kyverno"
}
}
rule {
api_groups = [""]
resources = ["secrets"]
verbs = ["get", "list", "watch", "create", "update", "patch", "delete"]
}
}
resource "kubernetes_cluster_role_binding" "kyverno_admission_secret_manager" {
metadata {
name = "kyverno:admission-controller:secret-manager"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = kubernetes_cluster_role.kyverno_secret_manager.metadata[0].name
}
subject {
kind = "ServiceAccount"
name = "kyverno-admission-controller"
namespace = "kyverno"
}
}
resource "kubernetes_cluster_role_binding" "kyverno_background_secret_manager" {
metadata {
name = "kyverno:background-controller:secret-manager"
}
role_ref {
api_group = "rbac.authorization.k8s.io"
kind = "ClusterRole"
name = kubernetes_cluster_role.kyverno_secret_manager.metadata[0].name
}
subject {
kind = "ServiceAccount"
name = "kyverno-background-controller"
namespace = "kyverno"
}
}
resource "kubectl_manifest" "sync_registry_credentials" {
yaml_body = yamlencode({
apiVersion = "kyverno.io/v1"
kind = "ClusterPolicy"
metadata = {
name = "sync-registry-credentials"
}
spec = {
rules = [
{
name = "sync-registry-secret"
match = {
any = [
{
resources = {
kinds = ["Namespace"]
}
}
]
}
exclude = {
any = [
{
resources = {
namespaces = ["kube-system", "kube-public", "kube-node-lease"]
}
}
]
}
generate = {
apiVersion = "v1"
kind = "Secret"
name = "registry-credentials"
namespace = "{{request.object.metadata.name}}"
synchronize = true
clone = {
namespace = "kyverno"
name = "registry-credentials"
}
}
}
]
}
})
depends_on = [
helm_release.kyverno,
kubernetes_secret.registry_credentials,
kubernetes_cluster_role_binding.kyverno_admission_secret_manager,
kubernetes_cluster_role_binding.kyverno_background_secret_manager,
]
}