Skip to content

Public Repository Security

The public repository contains desired configuration, public examples, non-secret version/checksum pins, and tests. Your controller and Ubuntu KVM host remain the trust boundary; GitHub Actions never deploys infrastructure.

Credentials and State

Material Storage Rule
SSH private key SSH agent or private key store outside Git Never generated by Terraform
SSH public key Ignored local tfvars; passed to cloud-init Public, but keep personal key metadata out of examples
K3s, Grafana, and Authelia credentials Encrypted .local/vault.yml Preserve originals for recovery
Vault password Private .local/vault-password, mode 0600 Move recovery copy to a password manager; never commit
TLS CA and server private keys Private .local/pki, files mode 0600 No automatic Mac trust-store installation
Kubeconfig .local/kubeconfig, mode 0600 Cluster-administrator credential
Terraform state and saved plans Ignored local files or a private locked backend Treat all state and plans as sensitive
Idempotence reports .local/idempotence.json, mode 0600 May include operational metadata; never CI artifacts
etcd snapshots and VM backups Encrypted off-host storage Contain credentials and application data

sensitive = true is output redaction, not encryption. This repository avoids Terraform password/private-key resources and Kubernetes/Helm providers so those credentials never enter its state. Libvirt uses the existing SSH identity rather than a new API credential. State still reveals network topology and infrastructure metadata and must be protected.

For one operator, an encrypted workstation disk and encrypted off-host state backup are the default. For collaboration, use a private remote backend with encryption, versioning, and locking. Configure backend authentication outside Git and migrate existing state deliberately with terraform init -migrate-state. Never run concurrent operators against independent copies of local state.

Ansible secret tasks use no_log, suppress diffs, avoid cached token facts, and keep token/config files root-only. No plaintext vault file is created by the generation helper. Decrypting or editing a vault is a separate trusted operator action; your editor and workstation must also be protected.

Network Policy at the VM Boundary

The guests use a dedicated nftables input table with default drop. It does not flush Kubernetes/CNI tables or add a competing forwarding policy:

Source Destination Allowed
PN51 NAT gateway Guests TCP 22, 80, 443, 6443 for SSH/tunnels and ingress proxy
Private node subnet Nodes UDP 8472; TCP 80, 443, 6443, 10250
Pod subnet API and kubelet TCP 6443, 10250
Guest outbound Package/image registries, DNS, time, other destinations Allowed; replies are statefully tracked

Flannel VXLAN is not encrypted. Keep this LAN/VLAN trusted and do not expose UDP 8472 to the internet. VM firewalls do not isolate pods from one another: application namespaces still need appropriate Kubernetes NetworkPolicies and RBAC. This baseline intentionally does not claim a zero-trust pod network.

No broad NodePort range, host metrics port, RDP, or VNC network service is opened. K3s includes Traefik and ServiceLB inside the private VMs. The API is accessed through a loopback-only SSH tunnel; services remain ClusterIP behind ingress. After guarded cutover, the host proxy accepts only 192.168.1.0/24 on 80/443. Both proxy ACLs and UFW define this boundary.

Guest UFW is not configured by this workflow. Do not apply an old generic UFW or hardening script over the cluster: forwarding rules and strict reverse-path filtering can break the CNI. Platform namespace policies restrict ingress and egress; monitoring admits only its own workloads, Traefik, and authorized API health probes. Authelia defaults to deny and requires MFA on every dashboard.

Host and Cluster Privileges

The cloud-init ubuntu account has passwordless sudo, because Ansible must configure the guests without embedding sudo credentials. Treat its SSH key as a guest-root credential. Root SSH and password SSH are disabled. The PN51 host uses terminal-authenticated sudo, not passwordless host sudo. Membership in system libvirt is privileged and is granted only to the existing administrator.

K3s enables Secret encryption at rest and protects kubeconfig/token permissions. Workers join with the running server's CA-pinned secure token, not only a short bootstrap password. Existing token mismatch and stale worker identity checks fail closed. Token rotation and etcd restores are deliberate operations, not automatic convergence.

The monitoring namespace permits privileged pod security because node-exporter needs host inspection. Do not allow untrusted workloads or namespace-admin users there. Keep application namespaces restricted separately. Grafana Secret values are outside Helm release values, but remain Kubernetes Secrets: base64 is not encryption and cluster administrators can read them. Platform pods are restricted, non-root, and use read-only root filesystems. Headlamp has no cluster-admin binding or persistent bearer-token Secret; operators mint short-lived read-only tokens. TLS private keys and vault secrets are reconciled without logging them.

Homepage has no Kubernetes token or Docker socket and accepts only its declared ingress hostname. Argo CD is isolated in its own restricted namespace and is a trusted, privileged controller. Its external UI requires Authelia MFA plus Argo CD login; anonymous access is disabled. The locked default project and the repository/namespace-scoped homelab project prevent accidental infrastructure adoption. Do not grant untrusted users project administration or access to repository credentials. No Application or auto-sync is created by bootstrap.

How to Check Before Publishing

git status --short
git check-ignore pn51/terraform/terraform.tfvars .local/vault.yml .local/kubeconfig
make secrets-scan

Review staged files and diffs before committing. The working-tree secret scan uses Git's tracked and nonignored file list, so it includes new public source without scanning private runtime data. A direct gitleaks dir can also flag legitimate credentials in ignored private directories on an operator's machine. Never upload raw output or solve that by committing those directories or globally disabling rules. No path-based scanner exception is added to hide tracked secrets.

Git ignore rules do not protect a file that was already committed. Remove the file from tracking, rotate the exposed credential immediately, then coordinate history cleanup as needed. Deleting the latest file does not remove it from Git history or forks. The fitness workload declares no Secret data in Git; Ansible owns its private runtime and TLS Secrets, which the application project cannot take over.

Dependency Automation

Keep Dependabot alongside Renovate. The two tools do not overlap in the current setup: homelab Renovate promotes only the two published fitness images, not application packages, GitHub Actions, or container base images.

The application's Dependabot configuration declares weekly GitHub Actions, frontend npm, and backend/frontend Docker updates, plus monthly root uv updates. Preserve that coverage; reviewed application dependency changes pass publishing qualification and scans before Renovate promotes their resulting images. Homelab has no Dependabot version-update configuration, so scheduled controller, documentation, Terraform, and action updates are not provided by this image-only Renovate configuration.

Version-update configuration, advisory alerts, and automatic security-fix PRs are separate settings. Live checks on 2026-11-05 found homelab advisory alerts enabled, fitness-tracker advisory alerts disabled, and Dependabot security-update PRs disabled in both repositories. No setting was removed or silently changed. Enable application advisory alerts and security-fix PRs deliberately as a separate security improvement; a declared version-update file is not proof those security features are active.

Removing Dependabot would only be appropriate after another reviewed tool owns all those ecosystems and their lockfile/base-image checks, without duplicate PRs. This release deliberately does not expand Renovate beyond application image delivery or auto-merge unrelated dependency changes.

Controller Dependency Advisories

The release review separately found existing Dependabot advisories in the homelab controller/docs uv.lock: critical certifi and high filelock, pymdown-extensions, and ansible-core findings. These are not a clean security bill for the controller; review the current advisory inventory and validate a scoped dependency upgrade. Do not silently substitute a release candidate for a pinned controller version or bypass TLS/scanner gates.

The deployed fitness backend/frontend are separate images and passed their required CI vulnerability scans. Controller dependency remediation remains a separate follow-up; this rollout does not claim those alerts are fixed.

How to Rotate Secrets

Rotate SSH keys by adding and testing a new public key before removing the old one; cloud-init is first-boot configuration, so existing guests may need an explicit authorized-keys update or rebuild.

K3s server-token rotation changes datastore bootstrap protection: follow the K3s token procedure, update the encrypted vault and root token files deliberately, and take a new snapshot. Preserve old tokens with snapshots that require them. The role deliberately refuses an unexplained token change on an existing node.

Grafana reads its initial admin password into its persistent database. Rotating only the Kubernetes Secret does not change that database password. Use Grafana's documented account-administration flow, update the vault, and verify login. Never put a new password in a CLI argument that will be logged.

Troubleshooting

Trust failures require correct CAs or verified host fingerprints, not insecure flags. Unexpected secret-scan findings require a local review with redacted output. Restore failures involving tokens require the snapshot's original token, not an attempt to brute-force a fresh credential into the datastore.

See provisioning, operations, and testing.