Cilium PR1: cf-ssh-bastion Teardown — Implementation Plan
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Cleanly retire the Cloudflared-tunneled SSH bastion (cf-ssh-bastion) and all its supporting Terraform, Kubernetes, Velero, and documentation references.
Architecture: Single PR with multiple commits. Removes the ArgoCD Application (whose finalizer cascades deletion of namespace + child resources), the Cloudflare DNS record + Access app + tunnel ingress route (via Terraform), the Vault k8s auth role + policy (via Terraform), the Velero backup-schedule namespace inclusion, and the one live documentation reference in docs/operations/cilium.md. Spec docs and migration history are preserved verbatim.
Tech Stack: ArgoCD (GitOps), Cilium (CNI/policy), Cloudflare (tunnel + Zero Trust Access via Terraform), Vault (Kubernetes auth + KV v2 via Terraform), HCP Terraform (TF execution), Velero (backup), bd (issue tracker).
Spec: docs/engineering/specs/2026-05-12-cilium-post-migration-hardening-design.md §“PR1: Bastion Teardown”.
File Structure
Section titled “File Structure”| Path | Action | Purpose |
|---|---|---|
argocd/app-configs/cf-ssh-bastion/ (entire directory, 8 files) | DELETE | ArgoCD-managed bastion manifests (Deployment, Service, RBAC, ExternalSecret, CNP, namespace, SSH client config, kustomization) |
argocd/cluster-app/templates/cf-ssh-bastion.yaml | DELETE | ArgoCD Application template (has finalizer → deletion cascades to all child resources) |
tf/cloudflare/ssh-bastion.tf | DELETE | cloudflare_zero_trust_access_application.ssh_bastion (browser-rendered SSH terminal at ssh.fzymgc.net) |
tf/cloudflare/variables.tf | MODIFY | Empty ssh_services default map (cascades removal of cloudflare_dns_record.ssh_services["ssh"]) |
tf/vault/k8s-cf-ssh-bastion.tf | DELETE | vault_kubernetes_auth_backend_role.cf_ssh_bastion |
tf/vault/policy-cf-ssh-bastion.tf | DELETE | vault_policy.cf_ssh_bastion (read access to bastion secret) |
argocd/app-configs/velero/backup-schedule.yaml | MODIFY | Remove cf-ssh-bastion from includedNamespaces in both backup schedules (lines 43, 97) |
docs/operations/cilium.md | MODIFY | Remove the single live reference (line 86) under “NetworkPolicy enforcement” |
docs/operations/cilium-migration.md | LEAVE AS-IS | Historical record of the migration; references are part of the narrative |
docs/engineering/specs/* + docs/engineering/plans/* | LEAVE AS-IS | Historical design/plan docs |
images/cf-ssh-bastion/Dockerfile | LEAVE AS-IS | Source for the published GHCR image; deletion deferred to a follow-up bead |
Vault secret secret/data/fzymgc-house/cluster/cf-ssh-bastion | LEAVE AS-IS | Documented as orphaned; deletion deferred to a follow-up bead |
Pre-flight
Section titled “Pre-flight”Task 0: Verify Pre-State and Capture Baseline
Section titled “Task 0: Verify Pre-State and Capture Baseline”Files: none (verification only).
- Step 0.1: Confirm you are in the correct workspace
Run:
jj --no-pager workspace listpwdExpected: cilium-hardening workspace exists, pwd ends with .worktrees/cilium-hardening.
- Step 0.2: Confirm bastion currently exists in-cluster
Run:
kubectl get ns cf-ssh-bastion -o jsonpath='{.metadata.name}{"\n"}'kubectl get application cf-ssh-bastion -n argocd -o jsonpath='{.metadata.finalizers}{"\n"}'kubectl get pod -n cf-ssh-bastion -o jsonpath='{.items[*].metadata.name}{"\n"}'kubectl get cnp -n cf-ssh-bastion -o jsonpath='{.items[*].metadata.name}{"\n"}'Expected output (literal):
- Line 1:
cf-ssh-bastion - Line 2:
["resources-finalizer.argocd.argoproj.io"] - Line 3: one or more
cf-ssh-bastion-*pod names - Line 4: six CNP names:
bastion-ingress-cloudflared bastion-allow-kube-dns bastion-allow-k8s-api bastion-allow-node-ssh bastion-allow-icmp bastion-allow-external-https
If finalizer is missing on line 2, STOP — Application deletion will not cascade. File an issue and abort PR1.
- Step 0.3: Confirm
hl-7h0is still in_progress
Run:
bd show hl-7h0 | head -5Expected: shows IN_PROGRESS status. We will close this at the end of PR1.
- Step 0.4: Confirm Cloudflare resources are live
Run:
dig +short ssh.fzymgc.net CNAMEExpected: returns a cfargotunnel.com CNAME (tunnel is live). Record the exact value so we can verify it is gone post-merge.
- Step 0.5: Commit the baseline state to your memory (do NOT commit to git)
No commit. This task is read-only.
Kubernetes Manifests (commit 1)
Section titled “Kubernetes Manifests (commit 1)”Task 1: Delete ArgoCD-Managed Bastion Manifests
Section titled “Task 1: Delete ArgoCD-Managed Bastion Manifests”Files:
- Delete:
argocd/cluster-app/templates/cf-ssh-bastion.yaml - Delete:
argocd/app-configs/cf-ssh-bastion/cilium-network-policy.yaml - Delete:
argocd/app-configs/cf-ssh-bastion/deployment.yaml - Delete:
argocd/app-configs/cf-ssh-bastion/external-secret.yaml - Delete:
argocd/app-configs/cf-ssh-bastion/kustomization.yaml - Delete:
argocd/app-configs/cf-ssh-bastion/namespace.yaml - Delete:
argocd/app-configs/cf-ssh-bastion/rbac.yaml - Delete:
argocd/app-configs/cf-ssh-bastion/service.yaml - Delete:
argocd/app-configs/cf-ssh-bastion/ssh-client-config.yaml - Step 1.1: Delete the ArgoCD Application template
Run:
rm argocd/cluster-app/templates/cf-ssh-bastion.yaml- Step 1.2: Delete the entire bastion app-configs directory
Run:
rm -r argocd/app-configs/cf-ssh-bastion/- Step 1.3: Verify deletions
Run:
ls argocd/cluster-app/templates/cf-ssh-bastion.yaml argocd/app-configs/cf-ssh-bastion/ 2>&1Expected: both paths report No such file or directory.
- Step 1.4: Render
cluster-appHelm template locally to catch regressions
Run (per memory cluster-app-helm-template-trap):
helm template argocd/cluster-app --name-template cluster-app --namespace argocd >/tmp/cluster-app-render.yaml 2>&1echo "exit: $?"grep -c "^kind:" /tmp/cluster-app-render.yamlgrep "cf-ssh-bastion" /tmp/cluster-app-render.yaml | head -5Expected: exit 0; positive number of kind: matches; no cf-ssh-bastion matches.
- Step 1.5: Run yamllint on changed paths
Run:
yamllint argocd/cluster-app/ argocd/app-configs/ 2>&1 | tail -20echo "exit: $?"Expected: exit 0 (deletions do not introduce lint errors).
- Step 1.6: Stage and describe the change in jj
Run:
jj --no-pager stjj --no-pager describe -m "feat(cf-ssh-bastion): remove ArgoCD manifests and cluster-app template
Cloudflared-tunneled SSH bastion is retired. The ArgoCD Applicationtemplate carries resources-finalizer.argocd.argoproj.io so deletioncascades to the namespace and all child resources (Deployment, Service,ExternalSecret, CiliumNetworkPolicy, ServiceAccount, RBAC).
Vault secret secret/data/fzymgc-house/cluster/cf-ssh-bastion is leftin place; documented as orphaned, removal deferred to a follow-up bead.
Refs spec: docs/engineering/specs/2026-05-12-cilium-post-migration-hardening-design.md
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"jj --no-pager log -r '@-..@'Expected: jj st shows 9 deleted files; jj describe updates the working-copy commit’s message; jj log confirms description on @.
Terraform — Cloudflare (commit 2)
Section titled “Terraform — Cloudflare (commit 2)”Task 2: Remove Cloudflare DNS, Access App, and Tunnel Ingress Route
Section titled “Task 2: Remove Cloudflare DNS, Access App, and Tunnel Ingress Route”Files:
- Delete:
tf/cloudflare/ssh-bastion.tf - Modify:
tf/cloudflare/variables.tf:144-160(thessh_servicesvariable block) - Step 2.1: Delete the Access app file
Run:
rm tf/cloudflare/ssh-bastion.tf- Step 2.2: Empty the
ssh_servicesdefault map
Edit tf/cloudflare/variables.tf. Find the ssh_services variable (currently around line 144). Replace its default block.
Before:
variable "ssh_services" { description = "SSH services exposed via tunnel on fzymgc.net domain with Cloudflare Access browser-rendered terminal" type = map(object({ service_url = string }))
default = { ssh = { service_url = "ssh://bastion-ssh.cf-ssh-bastion.svc.cluster.local:2222" } }
validation { condition = alltrue([for k, v in var.ssh_services : can(regex("^ssh://", v.service_url))]) error_message = "All SSH service URLs must start with ssh://." }}After:
variable "ssh_services" { description = "SSH services exposed via tunnel on fzymgc.net domain with Cloudflare Access browser-rendered terminal" type = map(object({ service_url = string }))
default = {}
validation { condition = alltrue([for k, v in var.ssh_services : can(regex("^ssh://", v.service_url))]) error_message = "All SSH service URLs must start with ssh://." }}Rationale: cloudflare_dns_record.ssh_services in tunnel.tf uses for_each = var.ssh_services. Emptying the map removes the ssh.fzymgc.net CNAME and the tunnel ingress rule it backs, without removing the variable definition (so future SSH services can be re-added by setting the variable).
- Step 2.3: Validate Terraform formatting and syntax
Run:
cd tf/cloudflare && terraform fmt -check -diff && terraform validate 2>&1; cd -Expected: exit 0, no fmt diff, validate succeeds.
If terraform validate errors with provider-init failure, run terraform init -backend=false first.
- Step 2.4: Render a local plan-style summary (no HCP run needed)
Run:
cd tf/cloudflare && terraform fmt 2>&1 && grep -n "ssh_bastion\|ssh_services" *.tf | head -20; cd -Expected: no remaining ssh_bastion resource references; ssh_services variable still present with empty default; for_each in tunnel.tf still references var.ssh_services (which is fine — empty map => zero resources).
- Step 2.5: Describe a new commit
Run:
jj --no-pager new -m "feat(tf/cloudflare): remove SSH bastion DNS, Access app, and tunnel route
Empties var.ssh_services default map (removes the cloudflare_dns_record.ssh_services\[\"ssh\"\]entry via for_each) and removes the cloudflare_zero_trust_access_application.ssh_bastionthat fronted ssh.fzymgc.net with Cloudflare's browser-rendered xterm.
The ssh_services variable itself is retained so future SSH services can bere-added without re-introducing the variable block.
HCP Terraform will plan + apply on PR merge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"jj --no-pager stExpected: working copy moves to a new change stl ... with the message above. (jj new -m creates a new commit on top of @ with the given description, then advances @ to it. Files for this commit are added in the next step.)
- Step 2.6: Verify staged file changes are the intended ones
Run:
jj --no-pager diff --summaryExpected: shows D tf/cloudflare/ssh-bastion.tf and M tf/cloudflare/variables.tf and nothing else.
If anything else appears, run jj --no-pager restore <path> for each unintended path to remove it from this commit.
Terraform — Vault (commit 3)
Section titled “Terraform — Vault (commit 3)”Task 3: Remove Vault k8s Auth Role and Policy
Section titled “Task 3: Remove Vault k8s Auth Role and Policy”Files:
- Delete:
tf/vault/k8s-cf-ssh-bastion.tf - Delete:
tf/vault/policy-cf-ssh-bastion.tf - Step 3.1: Start a new jj change for the Vault commit
Run:
jj --no-pager new -m "feat(tf/vault): remove cf-ssh-bastion auth role and policy
Drops vault_kubernetes_auth_backend_role.cf_ssh_bastion andvault_policy.cf_ssh_bastion. The KV-v2 secret atsecret/data/fzymgc-house/cluster/cf-ssh-bastion is left orphaned(not managed by Terraform); cleanup deferred to a follow-up bead.
HCP Terraform will plan + apply on PR merge.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"- Step 3.2: Delete the two TF files
Run:
rm tf/vault/k8s-cf-ssh-bastion.tf tf/vault/policy-cf-ssh-bastion.tf- Step 3.3: Verify no remaining references
Run:
grep -rn "cf_ssh_bastion\|cf-ssh-bastion" tf/vault/ 2>&1 | head -10Expected: no matches (or only matches in markdown comments — none expected).
- Step 3.4: Validate Terraform
Run:
cd tf/vault && terraform fmt -check -diff && terraform validate 2>&1; cd -Expected: exit 0.
- Step 3.5: Verify the commit has only the intended changes
Run:
jj --no-pager diff --summaryExpected: only D tf/vault/k8s-cf-ssh-bastion.tf and D tf/vault/policy-cf-ssh-bastion.tf.
Velero Backup (commit 4)
Section titled “Velero Backup (commit 4)”Task 4: Remove cf-ssh-bastion from Velero Backup Schedules
Section titled “Task 4: Remove cf-ssh-bastion from Velero Backup Schedules”Files:
-
Modify:
argocd/app-configs/velero/backup-schedule.yaml(two occurrences at lines 43 and 97) -
Step 4.1: Start a new jj change
Run:
jj --no-pager new -m "chore(velero): remove cf-ssh-bastion from backup schedules
Drops the retired bastion namespace from both backup-scheduleincludedNamespaces lists.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"- Step 4.2: Read the affected lines and surrounding comment
Run:
grep -n -B1 "cf-ssh-bastion" argocd/app-configs/velero/backup-schedule.yamlExpected: two occurrences, each preceded by a # Bastion - stateless, secrets from Vault comment line.
- Step 4.3: Remove both occurrences (the namespace line AND its
# Bastion -comment)
Use Edit on argocd/app-configs/velero/backup-schedule.yaml. There are two locations to update. For each, find this block:
Before:
- cloudflared # Bastion - stateless, secrets from Vault - cf-ssh-bastion includedResources:After:
- cloudflared includedResources:Note: the YAML appears twice in the file (two separate Schedule resources), with slightly different includedResources lists. Apply the deletion at both locations. Do NOT use a single replace_all if the surrounding context differs — verify each replacement individually.
- Step 4.4: Verify both occurrences are gone
Run:
grep -c "cf-ssh-bastion" argocd/app-configs/velero/backup-schedule.yamlExpected: 0.
- Step 4.5: yamllint the file
Run:
yamllint argocd/app-configs/velero/backup-schedule.yamlecho "exit: $?"Expected: exit 0.
- Step 4.6: Confirm the commit’s diff is minimal
Run:
jj --no-pager diffExpected: shows two two-line deletions (each: the # Bastion - comment line and the - cf-ssh-bastion line) and nothing else.
Documentation (commit 5)
Section titled “Documentation (commit 5)”Task 5: Remove Live Bastion Reference from cilium.md
Section titled “Task 5: Remove Live Bastion Reference from cilium.md”Files:
-
Modify:
docs/operations/cilium.md:80-95(the “NetworkPolicy enforcement” section) -
Step 5.1: Start a new jj change
Run:
jj --no-pager new -m "docs(cilium): remove retired cf-ssh-bastion NP reference
Updates the NetworkPolicy enforcement section to reflect thatcf-ssh-bastion has been retired. Migration history docs are keptverbatim.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>"- Step 5.2: Read the current section
Run:
sed -n '80,95p' docs/operations/cilium.mdConfirm content matches what’s described in Step 5.3 “Before”.
- Step 5.3: Update the section
Use Edit on docs/operations/cilium.md.
Before:
## NetworkPolicy enforcement
Cluster-wide `policyEnforcementMode: default` — pods without matching CiliumNetworkPolicy are allowed by default; pods with matching policies are restricted to what the policies permit.
Currently enforced policies live in:
- `argocd/app-configs/cf-ssh-bastion/cilium-network-policy.yaml`
## Known follow-upsAfter:
## NetworkPolicy enforcement
Cluster-wide `policyEnforcementMode: default` — pods without matching CiliumNetworkPolicy are allowed by default; pods with matching policies are restricted to what the policies permit.
No CiliumNetworkPolicies are currently in force. Crown-jewel namespace coverage is tracked under the post-migration hardening initiative (see `docs/engineering/specs/2026-05-12-cilium-post-migration-hardening-design.md`).
## Known follow-ups- Step 5.4: Run rumdl on the doc
Run:
rumdl check docs/operations/cilium.mdecho "exit: $?"Expected: exit 0. If failures, fix them inline before continuing.
- Step 5.5: Verify mkdocs renders the page
Per memory mkdocs-nav-curated, this file is already in the nav (we are not adding new docs in PR1). Run:
uvx --with mkdocs-material mkdocs build --strict 2>&1 | tail -20echo "exit: $?"Expected: exit 0, no broken-link warnings about cf-ssh-bastion paths.
- Step 5.6: Confirm commit diff
Run:
jj --no-pager diff --summaryExpected: only M docs/operations/cilium.md.
Verification, Push, and PR (commit 6 if any cleanup needed)
Section titled “Verification, Push, and PR (commit 6 if any cleanup needed)”Task 6: Cluster-Side Pre-Push Sanity Checks
Section titled “Task 6: Cluster-Side Pre-Push Sanity Checks”Files: none (live cluster checks).
- Step 6.1: Review the full PR contents
Run:
jj --no-pager log -r 'trunk()..@'jj --no-pager diff -r 'trunk()..@' --summaryExpected:
- 5 commits (Task 1 through Task 5).
- File summary:
D argocd/app-configs/cf-ssh-bastion/{cilium-network-policy,deployment,external-secret,kustomization,namespace,rbac,service,ssh-client-config}.yaml(8 files)D argocd/cluster-app/templates/cf-ssh-bastion.yamlD tf/cloudflare/ssh-bastion.tfM tf/cloudflare/variables.tfD tf/vault/k8s-cf-ssh-bastion.tfD tf/vault/policy-cf-ssh-bastion.tfM argocd/app-configs/velero/backup-schedule.yamlM docs/operations/cilium.md
If anything else appears in the summary, investigate before proceeding.
- Step 6.2: Confirm cluster bastion is still healthy (we have not deployed yet)
Run:
kubectl get pod -n cf-ssh-bastion -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.phase}{"\n"}{end}'Expected: cf-ssh-bastion-* Running (single pod, Running). This is the “no premature damage” check before push.
- Step 6.3: Run global lint pre-flight
Run:
lefthook run pre-commit 2>&1 | tail -30echo "exit: $?"Expected: exit 0. lefthook chains yamllint, rumdl, cocogitto.
Task 7: Push, Open PR, and Watch the Rollout
Section titled “Task 7: Push, Open PR, and Watch the Rollout”Files: none (push + PR + verification).
- Step 7.1: Create the bookmark and push
Run:
jj --no-pager bookmark create feat/cilium-pr1-bastion-teardown -r @jj --no-pager git push -b feat/cilium-pr1-bastion-teardownExpected: bookmark created at @; remote accepts the push; output ends with a Create a pull request for ... URL.
If push is rejected with branch-protection error, STOP; you are pushing to main — re-check the bookmark and jj log.
- Step 7.2: Open the PR
Run:
gh pr create --head feat/cilium-pr1-bastion-teardown --title "feat(cf-ssh-bastion): retire bastion and supporting infra (PR1/5)" --body "$(cat <<'EOF'## Summary
First PR of the cilium-post-migration-hardening initiative. Retires the Cloudflared-tunneled SSH bastion now that the tunnel path is no longer used.
Removes, in five small commits:
1. **ArgoCD manifests** — Application template (carries resources-finalizer so deletion cascades) + `app-configs/cf-ssh-bastion/`.2. **Cloudflare TF** — `cloudflare_zero_trust_access_application.ssh_bastion`, empties `var.ssh_services` (removes DNS + tunnel ingress route via `for_each`).3. **Vault TF** — `vault_kubernetes_auth_backend_role.cf_ssh_bastion` + `vault_policy.cf_ssh_bastion`.4. **Velero** — Removes `cf-ssh-bastion` from both backup-schedule `includedNamespaces` lists.5. **Docs** — Removes the one live reference in `docs/operations/cilium.md`. Migration history docs left verbatim.
## Out of scope (follow-up beads)
- Delete `images/cf-ssh-bastion/Dockerfile` source.- Delete the orphaned Vault secret `secret/data/fzymgc-house/cluster/cf-ssh-bastion`.
## Test plan
- [ ] HCP Terraform Cloudflare workspace plan shows: delete `cloudflare_zero_trust_access_application.ssh_bastion`, delete `cloudflare_dns_record.ssh_services["ssh"]`, delete tunnel ingress route to `bastion-ssh.cf-ssh-bastion.svc.cluster.local:2222`.- [ ] HCP Terraform Vault workspace plan shows: delete role + policy, no other changes.- [ ] Post-merge: `kubectl get application cf-ssh-bastion -n argocd` returns NotFound.- [ ] Post-merge: `kubectl get ns cf-ssh-bastion` returns NotFound.- [ ] Post-merge: `kubectl get pod,cnp -A | grep -i bastion` returns nothing.- [ ] Post-merge: `dig +short ssh.fzymgc.net` no longer returns a cfargotunnel.com record.- [ ] Close `hl-7h0` (stale; PR4/PR5 of the Calico→Cilium migration are merged).
Refs spec: `docs/engineering/specs/2026-05-12-cilium-post-migration-hardening-design.md`.
🤖 Generated with [Claude Code](https://claude.com/claude-code)EOF)"Expected: PR URL printed. Open the URL in a browser and verify the diff matches Step 6.1’s summary.
- Step 7.3: Inspect HCP Terraform plans on the PR
Wait for HCP Terraform to comment on the PR (typically <2 min). Inspect:
gh pr view --json comments --jq '.comments[] | select(.body | contains("Terraform")) | .body' | head -100Expected: two plans — one for cloudflare workspace, one for vault workspace.
Verify the Cloudflare plan shows:
- Destroy
cloudflare_zero_trust_access_application.ssh_bastion - Destroy
cloudflare_dns_record.ssh_services["ssh"] - An update on the tunnel ingress config that removes the SSH-services route (exact resource name depends on how
tunnel.tfcomposes ingress rules; verify onlycf-ssh-bastion/2222/ssh.fzymgc.netreferences are removed)
Verify the Vault plan shows:
- Destroy
vault_kubernetes_auth_backend_role.cf_ssh_bastion - Destroy
vault_policy.cf_ssh_bastion - NO other destroys (especially no
vault_kv_secret_v2— the bastion secret must remain).
If either plan shows additional unexpected destroys, STOP; ask before merging.
- Step 7.4: Merge
Once review + plans are clean:
gh pr merge --squash- Step 7.5: Watch ArgoCD reconcile + cascade
Run in a loop until the namespace is gone (use Monitor for state-change emission per memory cluster-reconciliation-monitoring):
while true; do APP=$(kubectl get application cf-ssh-bastion -n argocd -o jsonpath='{.status.sync.status}' 2>&1) NS=$(kubectl get ns cf-ssh-bastion -o jsonpath='{.status.phase}' 2>&1) PODS=$(kubectl get pod -n cf-ssh-bastion --no-headers 2>&1 | wc -l) echo "$(date +%H:%M:%S) app=$APP ns=$NS pods=$PODS" [[ "$APP" == *NotFound* && "$NS" == *NotFound* ]] && break sleep 10doneExpected: within ~2 min, both app and ns report NotFound.
- Step 7.6: Verify the post-state checklist
Run all of these and capture exit codes:
kubectl get application cf-ssh-bastion -n argocd 2>&1 | grep -i notfoundkubectl get ns cf-ssh-bastion 2>&1 | grep -i notfoundkubectl get pod,cnp -A 2>&1 | grep -i bastion | wc -l # expected 0dig +short ssh.fzymgc.net # expected emptyExpected: all four match (three notfound lines, fourth empty). Any miss = investigate before closing the PR conversation.
- Step 7.7: Close stale bead
hl-7h0
Run:
bd close hl-7h0 --message "Stale. PR4 of the Calico→Cilium migration merged as #1018 and PR5 as #1019. Closing as part of PR1 of the post-migration hardening initiative (#$(gh pr view --json number --jq .number))."bd show hl-7h0 | head -5Expected: bead transitions to closed.
- Step 7.8: File follow-up beads
Run:
bd create --title "Remove orphaned Vault secret secret/data/fzymgc-house/cluster/cf-ssh-bastion" --description "Bastion was retired in PR #<this-pr>. The KV-v2 secret remains in Vault. Delete via 'vault kv metadata delete secret/fzymgc-house/cluster/cf-ssh-bastion' once verified unused for 30 days." --priority 3 --type chore
bd create --title "Remove cf-ssh-bastion Dockerfile source from images/" --description "The bastion was retired in PR #<this-pr>. Source for the GHCR image at images/cf-ssh-bastion/ is no longer needed. Delete after confirming no consumers reference the published image tag." --priority 3 --type choreReplace <this-pr> with the merged PR number. Capture the new bead IDs and add them to the PR description as a comment.
Self-Review
Section titled “Self-Review”This implementation plan was checked against the spec on 2026-05-12:
-
Spec coverage — All “Repo changes” items in spec §“PR1: Bastion Teardown” map to tasks above:
argocd/app-configs/cf-ssh-bastion/deletion → Task 1argocd/cluster-app/templates/cf-ssh-bastion.yamldeletion → Task 1- Cloudflared tunnel route audit + removal → Task 2 (resolved to
tf/cloudflare/notargocd/app-configs/cloudflared-main/; Cloudflared k8s deployment does NOT reference the bastion) - Vault path (KV-v2) left intact → Task 7.8 (follow-up bead)
hl-7h0closure → Step 7.7- All “Verification” items in spec §“PR1: Bastion Teardown” map to Step 7.6.
-
Surface area beyond the spec — three additions surfaced during file-structure audit:
tf/vault/k8s-cf-ssh-bastion.tf+tf/vault/policy-cf-ssh-bastion.tf(Task 3) — Vault auth role + policy were not enumerated in the spec but exist and must be removed.argocd/app-configs/velero/backup-schedule.yaml(Task 4) — Velero backup schedule references the bastion namespace; removed.docs/operations/cilium.md(Task 5) — single live doc reference (not migration history) updated. These are noted as “Surface area beyond the spec” rather than spec gaps; they are direct consequences of removing the workload and were missed in spec because they are TF / non-manifest references.
-
Placeholder scan — no TBDs, no “implement later”, no “similar to Task N” without inline code, no vague verification (“ensure it works”). All steps have exact commands + expected output.
-
Type/name consistency — Confirmed:
- Bookmark name
feat/cilium-pr1-bastion-teardownused consistently in Task 7.1 and 7.2. - PR title prefix
feat(cf-ssh-bastion):matches conventional-commits scope. - HCP Terraform workspace names are not hardcoded in this plan; the reviewer reads them from the PR comment Step 7.3.
- Bookmark name