Infra side of ADR-0014: an mTLS gRPC consumer of Calico Goldmane's Flows API that records the namespace-pair edge-set in CNPG and posts a daily new-edge digest to #security. Adds the goldmane-edge-aggregator stack, the pg-goldmane-edges Vault rotation role (Tier-0 vault state updated here), and the namespace in the ghcr-credentials allowlist. Cert: REUSES the operator-minted, Tigera-CA-signed whisker-backend client cert (Goldmane verifies only the CA chain, not identity) instead of minting from the Tigera CA private key. This avoids putting the CA key in TF state AND the hashicorp/tls provider, which is incompatible with this repo's global generate-providers/lockfile pattern (it broke every stack's lockfile). Verified live: aggregator streaming flows, 174 edges in Postgres across 50x54 namespaces, db+slack ExternalSecrets synced, digest dry-run formats correctly, private image pulls via the Kyverno-synced ghcr-credentials. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
108 lines
3.8 KiB
HCL
108 lines
3.8 KiB
HCL
# =============================================================================
|
|
# 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",
|
|
# tuya-bridge runs a PUBLIC-decision image, but new ghcr packages default
|
|
# PRIVATE until their visibility is flipped (UI) — safety net so pulls
|
|
# work from the first deploy; prune once the package is public.
|
|
"tuya-bridge",
|
|
"f1-stream",
|
|
"job-hunter",
|
|
"instagram-poster",
|
|
"payslip-ingest",
|
|
"wealthfolio",
|
|
"fire-planner",
|
|
"recruiter-responder",
|
|
# openclaw's install-recruiter-plugin init container pulls the PRIVATE
|
|
# ghcr.io/viktorbarzin/recruiter-responder:latest image (infra#27).
|
|
"openclaw",
|
|
# k8s-portal: last in-cluster image build, migrated to GHA→ghcr (ADR-0002,
|
|
# "no local builds"). ghcr.io/viktorbarzin/k8s-portal:latest is PRIVATE
|
|
# (infra repo default); the deployment references the cloned secret.
|
|
"k8s-portal",
|
|
# goldmane-edge-aggregator: PRIVATE ghcr image pulled by the aggregate
|
|
# Deployment + digest CronJob (ADR-0014, infra#58).
|
|
"goldmane-edge-aggregator",
|
|
]
|
|
}
|
|
|
|
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" {
|
|
# Kyverno's validate-policy webhook DENIES in-place changes to a generate
|
|
# rule's spec ("changes of immutable fields ... is disallowed"), so any
|
|
# allowlist edit must delete+recreate the policy. Generated secrets survive
|
|
# policy deletion; generateExisting re-adopts them on recreate.
|
|
force_new = true
|
|
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,
|
|
]
|
|
}
|