Skip to content

Public Repository Security

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

Credentials and State

Material Storage Rule
Proxmox API token Controller environment or password manager Dedicated, scoped token; never tfvars
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 token and Grafana bootstrap password Encrypted .local/vault.yml Vault password stored separately
Optional desktop password hash Same encrypted vault Hashes are sensitive offline-cracking targets
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. Provider credentials come from environment variables. 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 Proxmox datacenter firewall must be active. All four VM NICs enable firewall processing and each VM defaults to dropping inbound traffic:

Source Destination Allowed
Explicit admin_cidrs All guests TCP 22
Explicit admin_cidrs Server only TCP 6443
Four exact node addresses All nodes UDP 8472 for Flannel, TCP 10250 for kubelet, ICMP
Four exact node addresses Server only TCP 6443
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 NodePort range, load-balancer controller, ingress controller, host metrics port, RDP, or VNC network service is opened. Grafana and Prometheus are ClusterIP-only; use loopback port forwarding. Public ingress and an authentication proxy need a separate explicit design.

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. The network boundary is outside the guests.

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. An optional console password does not re-enable password SSH.

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.

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 existing application Secret example has empty values; populate real application secrets through a separate secret-management workflow.

How to Rotate Secrets

Rotate the Proxmox token in Proxmox, update the controller secret store, test a plan, then revoke the old token. 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.