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.