# ================================================================================================= # PiHole WEB/API ADMIN PASSWORD — the one piece of PiHole state that was NOT declared anywhere. # ================================================================================================= # WHY THIS FILE EXISTS. Measured on live prod 2026-10-04, `pihole.toml` marks every non-default # setting with `### CHANGED`, and names the ones that came from the environment `### CHANGED (env)`. # The complete list was: # # dns.upstreams ### CHANGED (env) <- declared, catalog/pihole.yaml # dns.listeningMode ### CHANGED (env) <- declared, catalog/pihole.yaml # dns.rateLimit.count ### CHANGED (env) <- declared, catalog/pihole.yaml # misc.dnsmasq_lines ### CHANGED (env) <- declared, catalog/pihole.yaml # files.macvendor ### CHANGED <- set by the image itself, container path # webserver.api.pwhash ### CHANGED <- SET OUT OF BAND. Declared NOWHERE. This file. # # So exactly ONE operator-set value lived only on the Longhorn PVC: the admin password, as a # one-way BALLOON-SHA256 hash. Nobody could recover it (a hash does not reverse), nothing in git # produced it, and a rebuild from git would have come up with a DIFFERENT password — or none. # # THE REASON THIS BLOCKS HA, which is why it is fixed first. The next step is running more than one # PiHole replica, and a replica with a fresh PVC would come up with its own unrelated password. The # admin UI would then accept or reject the same credential depending on which pod answered. Declaring # it here makes every replica, and every rebuild, identical by construction. # # MECHANISM: `webserver.api.password` is a real, settable FTL config key — verified in the running # image, where `pihole setpassword` is itself just `setFTLConfigValue "webserver.api.password"` with # the comment "pihole-FTL will automatically hash the password". So FTL takes CLEARTEXT on this key # and writes the hash into `webserver.api.pwhash` itself. catalog/pihole.yaml therefore passes this # value as `FTLCONF_webserver_api_password`, and the hash on the PVC becomes a derived artefact # rather than the source of truth. # # DEFENCE IN DEPTH, NOT THE ONLY GATE. The admin UI is already behind the oauth2-proxy sidecar # (`--allowed-group=platform-admins`), so this password is a SECOND gate. It is kept rather than set # empty because the `pihole` Service still publishes port 80 (PiHole direct, bypassing the proxy on # :4180) and only the CiliumNetworkPolicy closes it — and the default-deny policy in this repo has a # documented drift history. An unauthenticated admin UI that can rewrite LAN DNS is not a good thing # to leave resting on one policy staying correct. Narrowing that Service port is tracked follow-up. # # ROTATION: `sops` this file in your own terminal, change the value, commit. Flux re-applies the # Secret, the pod restarts, FTL re-hashes. Do NOT `sops -d` it into a shared terminal or a log # (CLAUDE.md section 6); read it in an editor. The value below was generated randomly at authoring # time and has never been printed to a transcript. apiVersion: v1 kind: Secret metadata: name: pihole-bootstrap namespace: pihole labels: app.kubernetes.io/name: pihole type: Opaque stringData: #ENC[AES256_GCM,data:TMOuRyJeb5IVn3lZPXY7lgh1KGxu7GY9+WIOLHpMB50JqzmM2tHIaWUWKTt1JtZoX/E5/Y7WCUTnfkJLhRBN2zV2MwWRBSLYVy5CMg7xV8PTLRZJYAVUYS5Trg==,iv:q+DpfT6PQfY5ZuNsmP7Ddl+PDLjSm89fJICAhYoW64k=,tag:axijaO3DaxIZgZ3LeK2hXA==,type:comment] webui-password: ENC[AES256_GCM,data:7FcK/bUW79M46f/MlouAmATKbMnoUTNuMUjtqJ5vpU4=,iv:I8DBFB39uXZV42Ie2IPwSefmMgdKEtiDknfc9Tu7h5E=,tag:L423784UrDU3TvylpHc6KQ==,type:str] sops: kms: [] gcp_kms: [] azure_kv: [] hc_vault: [] age: - recipient: age175l8mdpdfu7hq0aml3exs3gykarwjmuvr23sq56s3wjejnyk44wsp4mfzg enc: | -----BEGIN AGE ENCRYPTED FILE----- YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0MkRSNjZLZlpXYkcvaVJL cjlqb2wrcWREZnZLdGpsTHNPWEpseHlRZnpjCnJVM1FDQ2M2dXlaS1JBZ2k2V1BP Yi9QUDZSMzZsL09pTi93RTdjdGxtY2MKLS0tIDIrQTZIVWdhMGIxSCtlUFN2Mlho SWVybld4VjJyaHRSKzZZU0xsUUV2SEEKbDYHV/qBZVspK7DpBW3vYjRMJ/HmAN+9 G9bOlNEp0jAtcNZEn6lpQ9ORgvmIrhJHoBQRLabdMWzNWSZsNn56tA== -----END AGE ENCRYPTED FILE----- lastmodified: "2026-10-04T06:01:27Z" mac: ENC[AES256_GCM,data:rhTMTOwLOtHXHgqI5M2ZW/6w+UpLpxBlTk7NoN0wmGSqOcE+OGrXA1DhkZrJweyQT0RSNSSBm/6QMztLP81eepOzdp2rEkwvbQPK/l6K9g16Ww9c1IatS3NQsKEB8AoMDx7jBQ6exXYgggPx3MJsNYbE+WyloQ96U9KPdrecAM8=,iv:n34v2zX9D5xsaR1PFCH94mNMiuRfhUyI2lseUCoqiTA=,tag:9YmN9nIVLXfsGYIb0bUl2w==,type:str] pgp: [] encrypted_regex: ^(data|stringData)$ version: 3.9.4