feat(baseline): deploy as yourself — auto-resolve admin user/key, add extra keys - #5
Conversation
… extra keys Make the baseline role zero-config by default instead of requiring a manually set ssh_admin_pubkey: - ssh_admin_user "" -> control-machine local $USER - ssh_admin_pubkey "" -> autodetected ~/.ssh key (id_ed25519 > ecdsa > rsa) - new ssh_admin_extra_pubkeys list authorizes additional team keys The lockout guard is now an unconditional assert that runs before account creation and hardening, aborting the play unless a NON-ROOT admin user and at least one key resolve. This closes two footguns: resolving to "root" (which ssh_hardening then locks out) and an unset $USER silently skipping hardening with a green exit. A debug task echoes the resolved user + key count for visibility into what is trusted. Docs (ansible/README.md, baseline README, group_vars/all.yml, defaults) updated to drop the "pubkey REQUIRED" framing and document the control-node $USER/$HOME resolution caveat. molecule.yml drops the now-inert ssh_admin_* skip vars (converge runs only anvil + caddy; baseline is never exercised). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Code Review
This pull request updates the Ansible baseline role to support deploying "as yourself" by default. It autodetects the control machine's local user and SSH keys when ssh_admin_user and ssh_admin_pubkey are left empty, and introduces an unconditional lockout guard to prevent keyless or root-only hardening. Feedback suggests improving the robustness of the key autodetection task by using tilde expansion, falling back to /dev/null to avoid empty lookup paths, and initializing ssh_admin_detected_pubkey in the role defaults to prevent undefined variable errors.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
There was a problem hiding this comment.
Pull request overview
This PR updates the baseline Ansible role to default to “deploy as yourself” by resolving the admin username and SSH key(s) from the control machine when inventory values are left empty, while adding support for authorizing additional teammate keys. It also strengthens the SSH lockout guard by making it an unconditional pre-check that aborts before hardening unless a non-root admin user and at least one key are present.
Changes:
- Auto-resolve
ssh_admin_userfrom control-node$USERand auto-detect a default~/.sshpublic key whenssh_admin_*values are empty (explicit values still override). - Add
ssh_admin_extra_pubkeysto authorize additional keys for the admin user. - Make the lockout guard an unconditional assert that runs before account creation and SSH hardening, preventing root-only/keyless hardening.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| ansible/roles/baseline/tasks/main.yml | Adds control-node user/key resolution, aggregates key list (including extras), and enforces an unconditional lockout-guard assert before hardening. |
| ansible/roles/baseline/README.md | Documents “deploy as yourself” behavior, extra keys, and the strengthened lockout guard semantics. |
| ansible/roles/baseline/defaults/main.yml | Changes defaults to empty user/key with autodetect enabled and introduces ssh_admin_extra_pubkeys. |
| ansible/README.md | Updates operator docs to reflect auto-resolution defaults and new extra-keys support. |
| ansible/molecule/default/molecule.yml | Removes now-unneeded ssh_admin_* overrides since Molecule doesn’t exercise baseline. |
| ansible/inventory/group_vars/all.yml | Updates inventory defaults/docs to match the new resolution behavior and new variables. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Address PR review:
- Copilot: the debug task labeled the primary key source "autodetected"
whenever ssh_admin_pubkey was empty, even when autodetect found nothing and
all keys came from ssh_admin_extra_pubkeys. Now distinguishes explicit /
autodetected / none (extras only) by checking the resolved detected key.
- gemini: use ~/.ssh tilde paths for the autodetect candidates instead of an
env('HOME') lookup. expanduser falls back to the passwd DB when $HOME is
unset (cron/CI), so it's more robust; verified first_found expands ~.
Declined two suggestions: a /dev/null fallback for lookup('file') (the
existing `when: found | length > 0` guard already prevents an empty-path
lookup) and initializing ssh_admin_detected_pubkey in defaults (it's an
internal computed fact already handled by `| default('')`).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
What & why
A real deploy previously required the operator to hand-set
ssh_admin_pubkey(and accept the shareddeployaccount), with the lockout-guard assert failing on an empty key. The operator's own username and~/.sshkey are right there on the control machine — this removes that friction and adds support for additional team keys.baselineis now "deploy as yourself" by default:ssh_admin_user: ""→ resolves to the control-machine local$USERssh_admin_pubkey: ""→ autodetected from~/.ssh(id_ed25519>ecdsa>rsa)ssh_admin_extra_pubkeyslist authorizes additional keys (e.g. teammates)Hardened lockout guard
The guard is now an unconditional assert that runs before account creation and hardening, aborting the play unless a non-root admin user and ≥1 key resolve. This closes two footguns surfaced in review:
root(whichssh_hardeningthen locks out)$USER(cron/CI/sudowithenv_reset) silently skipping hardening with a green exitA
debugtask echoes the resolved user + key count so a wrong control machine or a silently-dropped entry is visible. Docs note that resolution reads the control node's$USER/$HOMEof whoever runsansible-playbook.Verification
yamllint+ansible-lint(production profile, 23 files): passsite.yml/anvil.yml--syntax-check: pass$USER=root, empty user$USERunset, empty usermolecule.ymldrops the now-inertssh_admin_*skip vars —converge.ymlruns onlyanvil+caddy, sobaselineis never exercised there.🤖 Generated with Claude Code