Implement reproducible S1 handoff contracts
Assistant: codex Assistant-Model: gpt-5.6-sol Assistant-Session: 01a02994-7685-7940-bf34-3555b8256018
This commit is contained in:
parent
c8cb1c8edf
commit
b93af8cc78
44 changed files with 2035 additions and 342 deletions
48
Makefile
48
Makefile
|
|
@ -61,35 +61,49 @@ sops-decrypt: ## Print decrypted file to stdout (for inspection) FILE=secrets/fo
|
|||
@[ -n "$(FILE)" ] || (echo "Usage: make sops-decrypt FILE=secrets/xxx.sops.yaml" && exit 1)
|
||||
sops -d $(FILE)
|
||||
|
||||
sops-rotate: ## Rotate recipients on a SOPS file (after updating .sops.yaml)
|
||||
@[ -n "$(FILE)" ] || (echo "Usage: make sops-rotate FILE=secrets/xxx.sops.yaml" && exit 1)
|
||||
sops --rotate --in-place $(FILE)
|
||||
sops-rotate: ## Check SOPS recipient drift; use the bounded tool for approved changes
|
||||
python3 scripts/sops_rotation.py --check
|
||||
|
||||
check-secrets: ## Fail if any file in secrets/ is not SOPS-encrypted
|
||||
@! (git ls-files secrets | xargs -r grep -L -E '(^sops:$$|\"sops\"[[:space:]]*:)' | tee /dev/stderr | read) \
|
||||
|| (echo "❌ Unencrypted secrets detected above. Encrypt with: sops --encrypt --in-place <file>"; exit 1)
|
||||
@echo "✔ All files in secrets/ appear SOPS-encrypted"
|
||||
check-secrets: ## Fail if any declared secret-bearing path is not encrypted
|
||||
python3 scripts/check_secret_paths.py --tracked
|
||||
|
||||
# ---- Terraform (Hetzner) ----
|
||||
validate-inventory: ## Validate adopted/provider-managed host declarations without provider access
|
||||
python3 scripts/inventory_contract.py inventory/servers.yaml
|
||||
|
||||
validate-baseline: ## Validate the executable baseline and its Ansible/Goss consumers
|
||||
python3 scripts/baseline_contract.py --check-repo
|
||||
|
||||
validate-receipts: ## Validate committed metadata-only S1 receipt examples
|
||||
python3 scripts/s1_receipt.py docs/evidence/s1-receipts/*.json
|
||||
|
||||
s1-handoff: ## Run the live fail-closed S1 verification gate and emit a receipt
|
||||
python3 scripts/s1_handoff.py
|
||||
|
||||
s1-handoff-dry-run: ## Validate handoff inputs without host access; receipt is not-run
|
||||
python3 scripts/s1_handoff.py --dry-run
|
||||
|
||||
tf-fmt: ## Terraform fmt
|
||||
@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
|
||||
@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); @terraform -chdir=terraform/hetzner fmt -recursive || true
|
||||
terraform -chdir=terraform/hetzner fmt -recursive
|
||||
|
||||
tf-init: ## Terraform init
|
||||
@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
|
||||
@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); terraform -chdir=terraform/hetzner init
|
||||
terraform -chdir=terraform/hetzner init
|
||||
|
||||
tf-plan: tf-init ## Terraform plan (requires decrypted HCLOUD_TOKEN)
|
||||
@echo "🔍 Running terraform plan..."
|
||||
@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
|
||||
@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; unlock secrets/hetzner-token.yaml with SOPS" && exit 1)
|
||||
@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); terraform -chdir=terraform/hetzner plan -var="hcloud_token=$(HCLOUD_TOKEN)"
|
||||
|
||||
tf-apply: tf-init ## Terraform apply (provision)
|
||||
@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
|
||||
tf-apply: ## Terraform apply (provision; exact approval required before init)
|
||||
@test "$(APPROVE_TF_APPLY)" = "YES" || (echo "Refusing apply: review make tf-plan, then set APPROVE_TF_APPLY=YES" && exit 1)
|
||||
@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; unlock secrets/hetzner-token.yaml with SOPS" && exit 1)
|
||||
@$(MAKE) tf-init
|
||||
@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); terraform -chdir=terraform/hetzner apply -auto-approve -var="hcloud_token=$(HCLOUD_TOKEN)"
|
||||
|
||||
tf-destroy: tf-init ## Terraform destroy (tear down)
|
||||
@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; export SOPS_AGE_KEY or set keys.txt & fill secrets.sops.yaml" && exit 1)
|
||||
tf-destroy: ## Terraform destroy (exact approval required before init)
|
||||
@test "$(APPROVE_TF_DESTROY)" = "DESTROY-MANAGED-HETZNER" || (echo "Refusing destroy: set APPROVE_TF_DESTROY=DESTROY-MANAGED-HETZNER after exact plan review" && exit 1)
|
||||
@[ -n "$(HCLOUD_TOKEN)" ] || (echo "HCLOUD_TOKEN empty; unlock secrets/hetzner-token.yaml with SOPS" && exit 1)
|
||||
@$(MAKE) tf-init
|
||||
@export HCLOUD_TOKEN=$(HCLOUD_TOKEN); terraform -chdir=terraform/hetzner destroy -auto-approve -var="hcloud_token=$(HCLOUD_TOKEN)"
|
||||
|
||||
# --- Terraform provider/lockfile helpers ---
|
||||
|
|
@ -199,7 +213,7 @@ doctor: ## Check tools and basic repo setup
|
|||
command -v age >/dev/null && ok "age: $$(age --version)"; \
|
||||
command -v terraform >/dev/null && ok "terraform: $$(terraform -version | head -1)"; \
|
||||
test -f keys/admin_ssh.pub && ok "keys/admin_ssh.pub present" || echo "ℹ add your SSH pubkey to keys/admin_ssh.pub"; \
|
||||
test -f inventory/group_vars/secrets.sops.yaml && ok "secrets.sops.yaml present" || echo "ℹ create inventory/group_vars/secrets.sops.yaml"; \
|
||||
python3 scripts/check_secret_paths.py --tracked >/dev/null && ok "declared secret paths encrypted" || fail "secret path check failed"; \
|
||||
grep -q "age1" .sops.yaml && ok ".sops.yaml has an age recipient" || echo "ℹ add your age public key to .sops.yaml"; \
|
||||
git config --get core.hooksPath >/dev/null && ok "git hooksPath: $$(git config --get core.hooksPath)" || echo "ℹ run: make hooks"; \
|
||||
'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue