diff --git a/stacks/kyverno/modules/kyverno/ghcr-credentials.tf b/stacks/kyverno/modules/kyverno/ghcr-credentials.tf new file mode 100644 index 00000000..9586163c --- /dev/null +++ b/stacks/kyverno/modules/kyverno/ghcr-credentials.tf @@ -0,0 +1,89 @@ +# ============================================================================= +# ghcr.io pull credentials — synced ONLY to namespaces running PRIVATE ghcr +# images (ADR-0002 off-infra builds) +# ============================================================================= +# The credential is Viktor's admin PAT (Vault secret/viktor/ghcr_pull_token — +# an alias of github_pat: GitHub has no API to mint tokens, so a UI-minted +# read:packages token can replace the alias value later with no TF change). +# Because the PAT is broad, this is a positive allowlist, NOT cluster-wide +# like registry-credentials: any workload in a listed namespace can read the +# secret, so every entry widens the blast radius. Public-image namespaces +# need no credentials — keep this list to private-image consumers only. + +locals { + ghcr_private_namespaces = [ + "tripit", + "f1-stream", + "job-hunter", + "instagram-poster", + "payslip-ingest", + "wealthfolio", + "fire-planner", + "recruiter-responder", + ] +} + +resource "kubernetes_secret" "ghcr_credentials" { + metadata { + name = "ghcr-credentials" + namespace = kubernetes_namespace.kyverno.metadata[0].name + } + type = "kubernetes.io/dockerconfigjson" + data = { + ".dockerconfigjson" = jsonencode({ + auths = { + "ghcr.io" = { + username = "ViktorBarzin" + password = try(data.vault_kv_secret_v2.viktor.data["ghcr_pull_token"], "") + auth = base64encode("ViktorBarzin:${try(data.vault_kv_secret_v2.viktor.data["ghcr_pull_token"], "")}") + } + } + }) + } +} + +resource "kubectl_manifest" "sync_ghcr_credentials" { + yaml_body = yamlencode({ + apiVersion = "kyverno.io/v1" + kind = "ClusterPolicy" + metadata = { + name = "sync-ghcr-credentials" + } + spec = { + rules = [ + { + name = "sync-ghcr-secret" + match = { + any = [ + { + resources = { + kinds = ["Namespace"] + names = local.ghcr_private_namespaces + } + } + ] + } + generate = { + generateExisting = true + apiVersion = "v1" + kind = "Secret" + name = "ghcr-credentials" + namespace = "{{request.object.metadata.name}}" + synchronize = true + clone = { + namespace = "kyverno" + name = "ghcr-credentials" + } + } + } + ] + } + }) + + depends_on = [ + helm_release.kyverno, + kubernetes_secret.ghcr_credentials, + kubernetes_cluster_role_binding.kyverno_admission_secret_manager, + kubernetes_cluster_role_binding.kyverno_background_secret_manager, + ] +} diff --git a/stacks/tripit/main.tf b/stacks/tripit/main.tf index b91f3940..f1748bb6 100644 --- a/stacks/tripit/main.tf +++ b/stacks/tripit/main.tf @@ -142,34 +142,10 @@ resource "kubernetes_namespace" "tripit" { } } -# GHCR pull secret (tripit ns only) for the private ghcr.io/viktorbarzin/tripit image -# now built off-infra by GitHub Actions. Uses viktor's github_pat as the pull -# credential — admin-scoped, accepted as an interim (rotate to a fine-grained -# read:packages token later). Scoped to this namespace to limit the broad token's -# blast radius (deliberately NOT folded into the cluster-wide registry-credentials). -data "vault_kv_secret_v2" "viktor" { - mount = "secret" - name = "viktor" -} - -resource "kubernetes_secret" "ghcr_credentials" { - metadata { - name = "ghcr-credentials" - namespace = kubernetes_namespace.tripit.metadata[0].name - } - type = "kubernetes.io/dockerconfigjson" - data = { - ".dockerconfigjson" = jsonencode({ - auths = { - "ghcr.io" = { - username = "ViktorBarzin" - password = data.vault_kv_secret_v2.viktor.data["github_pat"] - auth = base64encode("ViktorBarzin:${data.vault_kv_secret_v2.viktor.data["github_pat"]}") - } - } - }) - } -} +# GHCR pull secret: the ghcr-credentials Secret in this namespace is cloned in +# by the kyverno stack's sync-ghcr-credentials ClusterPolicy (allowlisted +# private-ghcr namespaces only — ADR-0002). Source of truth: +# stacks/kyverno/modules/kyverno/ghcr-credentials.tf. # App secrets — seed these in Vault before applying: # secret/tripit @@ -370,7 +346,7 @@ resource "kubernetes_deployment" "tripit" { name = "registry-credentials" } image_pull_secrets { - name = kubernetes_secret.ghcr_credentials.metadata[0].name + name = "ghcr-credentials" } init_container { @@ -650,7 +626,7 @@ resource "kubernetes_cron_job_v1" "tripit_worker" { name = "registry-credentials" } image_pull_secrets { - name = kubernetes_secret.ghcr_credentials.metadata[0].name + name = "ghcr-credentials" } container { name = "worker"