Skip to content

feat(baseline): key-only admin — passwordless sudo + locked password - #22

Merged
thiras merged 2 commits into
mainfrom
feat/admin-passwordless-sudo
Jul 11, 2026
Merged

thiras merged 2 commits into
mainfrom
feat/admin-passwordless-sudo

Conversation

@thiras

@thiras thiras commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

What

Makes the baseline role's admin/operator account (ssh_admin_user, which defaults to the operator's local $USER — not the decdn service user) fully key-only, behind a new ssh_admin_passwordless_sudo knob (default true):

  • writes a /etc/sudoers.d/<user> NOPASSWD drop-in (0440 root:root, visudo -cf validated), and
  • locks the account password (password_lock: true), so no password can authenticate.
  • When the knob is false: the drop-in is removed and a loud warning tells the operator to passwd <user> (classic password sudo).

Why

Closes a latent gap: the admin account was added to the sudo group (so sudo needs a password) but created with no password, so sudo / non-interactive make deploy as that user could not escalate after ssh_hardening disables root + SSH password login. It now works non-interactively, and no password can be used to authenticate.

The decdn service user (nologin, no sudo) is untouched.

Notable hardening detail

The sudoers.d filename is sanitized (regex_replace('[^A-Za-z0-9_-]', '_')): sudo's #includedir silently ignores any file whose name contains . or ends in ~ (e.g. deploy.bot). Without sanitizing, such a username would get a valid-but-ignored NOPASSWD drop-in which, combined with the locked password, is a silent sudo lockout. visudo -cf validates content, not the filename, so it cannot catch this. (Surfaced by PR review.)

Files

  • roles/baseline/tasks/main.yml — the gated tasks (core)
  • roles/baseline/defaults/main.yml — new knob
  • roles/baseline/README.md, inventory/group_vars/all.yml, inventory/hosts.yml.example — docs + reconciled the stale "no NOPASSWD" / sudo-rs note

Verification

  • make lint ✅ · make lint-ansible (production profile) ✅ · make security (KICS: 0 HIGH / 0 CRITICAL) ✅ · ansible-playbook --syntax-check ✅
  • Filename sanitization spot-checked: deploy.bot→deploy_bot, john.doe~→john_doe_
  • Molecule unaffected — baseline is real-host-only and not converged there.

End-to-end (make check / make deploy) requires a real target host. On the host as the admin user, expect: sudo -n true succeeds, passwd -S <user> reports L, /etc/sudoers.d/<user> is 440 root:root, decdn has no sudo, and a re-run is idempotent.

🤖 Generated with Claude Code

Make the operator's admin account (ssh_admin_user, defaults to the local
$USER; not the decdn service user) fully key-only, behind a new
ssh_admin_passwordless_sudo knob (default true):

- write a /etc/sudoers.d/<user> NOPASSWD drop-in (0440 root:root,
  visudo-validated), and lock the account password (password_lock)
- remove the drop-in and warn the operator to set a password when the
  knob is disabled

This closes a latent gap: the account was in the sudo group (password
required) but created with no password, so `make deploy` as that user
could not escalate after ssh_hardening disables root + password login.

The sudoers.d filename is sanitized because sudo's #includedir silently
ignores names containing '.' or ending in '~' (e.g. deploy.bot), which
combined with the password lock would be a silent sudo lockout.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 11, 2026 19:48
@coderabbitai

coderabbitai Bot commented Jul 11, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

@thiras, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: c45b4b54-2fc4-4738-ac75-57528261c1b5

📥 Commits

Reviewing files that changed from the base of the PR and between fb5635f and dd932aa.

📒 Files selected for processing (5)
  • ansible/inventory/group_vars/all.yml
  • ansible/inventory/hosts.yml.example
  • ansible/roles/baseline/README.md
  • ansible/roles/baseline/defaults/main.yml
  • ansible/roles/baseline/tasks/main.yml
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/admin-passwordless-sudo

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the ssh_admin_passwordless_sudo configuration option to enable key-only admin accounts with passwordless sudo and locked passwords. Feedback on the changes highlights two issues in the new Ansible tasks: a critical risk where an empty admin username could lead to the deletion of the entire /etc/sudoers.d/ directory, and a dry-run failure where locking the password of a non-existent user would fail in check mode.

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.

Comment thread ansible/roles/baseline/tasks/main.yml Outdated
Comment thread ansible/roles/baseline/tasks/main.yml

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR updates the baseline Ansible role to make the admin/operator SSH account key-only by default by (1) granting passwordless sudo via a sudoers drop-in and (2) locking the account password, with a new ssh_admin_passwordless_sudo toggle to fall back to classic password-based sudo.

Changes:

  • Add ssh_admin_passwordless_sudo (default true) and implement NOPASSWD sudo + password lock behavior in the baseline role.
  • Sanitize the sudoers drop-in filename to avoid sudoers.d include-skip edge cases for usernames containing . / ending with ~.
  • Update baseline and inventory documentation to reflect the new behavior and the sudo-rs implications.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
ansible/roles/baseline/tasks/main.yml Adds the sudoers drop-in + password lock behavior for the admin user, gated by ssh_admin_passwordless_sudo.
ansible/roles/baseline/defaults/main.yml Introduces the new ssh_admin_passwordless_sudo default and documents intent.
ansible/roles/baseline/README.md Documents the key-only admin default and the new knob in the role docs.
ansible/inventory/hosts.yml.example Updates the sudo-rs workaround explanation to account for default NOPASSWD sudo.
ansible/inventory/group_vars/all.yml Exposes the new knob in inventory defaults/documentation.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread ansible/roles/baseline/tasks/main.yml
…warning

Address PR review:
- Add `ssh_admin_user_effective | length > 0` guard to the drop-in
  removal task. The lockout-guard assert already blocks an empty user,
  but defence-in-depth: an empty user would collapse the path to the
  bare /etc/sudoers.d/ directory and state:absent would recursively
  delete it, breaking sudo host-wide.
- Extend the disabled-path warning to note that flipping true->false on
  an already-hardened host (connected as the admin user) breaks the
  current run once the drop-in is removed; set a password first and
  re-run with --ask-become-pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@thiras
thiras merged commit b26ced6 into main Jul 11, 2026
9 checks passed
@thiras
thiras deleted the feat/admin-passwordless-sudo branch July 11, 2026 20:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants