feat(infra-ops): commission a dedicated NOPASSWD-sudo agent identity for PFI boxes
Adds a host-agnostic elway play + fleet driver that stand up an `infra-ops` system user (dedicated ed25519 key, NOPASSWD sudo with log_output audit, docker group) so the infra-ops agent completes DevOps work end-to-end instead of handing sudo steps back to the operator. Scoped to PFI-owned Linux boxes; tiered (compute/app/ sensitive-infra) with SureFire/corviduo/esh/Synology explicitly excluded. Validated live on irv-ml1.
This commit is contained in:
@@ -0,0 +1,77 @@
|
|||||||
|
# Bootstrap the `infra-ops` agent identity on a PFI-OWNED box.
|
||||||
|
#
|
||||||
|
# Creates a dedicated, key-authenticated, NOPASSWD-sudo system user so the
|
||||||
|
# infra-ops agent can complete DevOps work (apt, systemctl, service lifecycle)
|
||||||
|
# end-to-end instead of handing sudo steps back to the operator.
|
||||||
|
#
|
||||||
|
# WHY a dedicated identity (not the agent borrowing lkraven/vh sudo):
|
||||||
|
# - Clean audit trail — every agent action attributable to `infra-ops`,
|
||||||
|
# separate from human accounts, with sudo I/O logging on.
|
||||||
|
# - Revocation = pull one authorized_keys line + one sudoers file.
|
||||||
|
# - No human password ever in the agent's reach (key-gated NOPASSWD).
|
||||||
|
#
|
||||||
|
# RUN (operator, with YOUR sudo — infra-ops doesn't exist yet, so you bootstrap
|
||||||
|
# it as your normal account; elway prompts for your sudo password once, lazily
|
||||||
|
# on the first sudo step):
|
||||||
|
# scripts/elway <pfi-host> --playbook playbooks/bootstrap-infra-ops-user.yaml
|
||||||
|
# Fleet (a lot of servers):
|
||||||
|
# scripts/bootstrap-infra-ops-fleet.sh
|
||||||
|
#
|
||||||
|
# SCOPE — PFI-owned Linux boxes ONLY. NEVER run on:
|
||||||
|
# - SureFire sf-* / sfsrv-ana (tenant property — coordinate, don't own)
|
||||||
|
# - corviduo-dev (Worldtree partner app-layer)
|
||||||
|
# - esh-* (ESH home lab — non-PFI)
|
||||||
|
# - nh3-nas (Synology DSM) (no standard useradd / sudoers.d)
|
||||||
|
#
|
||||||
|
# Idempotent: re-running reconciles the key + sudoers without error.
|
||||||
|
|
||||||
|
vars:
|
||||||
|
ops_user: infra-ops
|
||||||
|
ops_pubkey: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN+1HBwfXrkfTYWdcnWCjLJ6VLAGC87gxH5h5vKaaA3c infra-ops@pfi-fleet"
|
||||||
|
# Optional authorized_keys from="..." source restriction. Empty = none.
|
||||||
|
# Hardening follow-up once per-path source IPs (LAN vs WG tunnel) are pinned.
|
||||||
|
ssh_from: ""
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Create the infra-ops system user (home + bash shell)
|
||||||
|
sudo: true
|
||||||
|
shell: useradd -m -s /bin/bash {{ ops_user }}
|
||||||
|
when: "! id {{ ops_user }} >/dev/null 2>&1"
|
||||||
|
|
||||||
|
- name: Add infra-ops to the docker group (only if docker is installed)
|
||||||
|
sudo: true
|
||||||
|
shell: getent group docker >/dev/null && usermod -aG docker {{ ops_user }} || echo "no docker group — skipped"
|
||||||
|
|
||||||
|
- name: Ensure infra-ops ~/.ssh exists (700, owned)
|
||||||
|
sudo: true
|
||||||
|
shell: install -d -m 700 -o {{ ops_user }} -g {{ ops_user }} /home/{{ ops_user }}/.ssh
|
||||||
|
|
||||||
|
- name: Install infra-ops authorized_keys (exact line, mode 600)
|
||||||
|
sudo: true
|
||||||
|
shell: |
|
||||||
|
line='{{ ops_pubkey }}'
|
||||||
|
[ -n '{{ ssh_from }}' ] && line='from="{{ ssh_from }}" {{ ops_pubkey }}'
|
||||||
|
printf '%s\n' "$line" > /home/{{ ops_user }}/.ssh/authorized_keys
|
||||||
|
chown {{ ops_user }}:{{ ops_user }} /home/{{ ops_user }}/.ssh/authorized_keys
|
||||||
|
chmod 600 /home/{{ ops_user }}/.ssh/authorized_keys
|
||||||
|
|
||||||
|
- name: Install NOPASSWD sudoers + command logging (visudo-validated before install)
|
||||||
|
sudo: true
|
||||||
|
shell: |
|
||||||
|
f=/etc/sudoers.d/{{ ops_user }}
|
||||||
|
printf 'Defaults:%s log_output\n%s ALL=(ALL) NOPASSWD:ALL\n' '{{ ops_user }}' '{{ ops_user }}' > "$f.tmp"
|
||||||
|
chmod 440 "$f.tmp"
|
||||||
|
if visudo -cf "$f.tmp"; then mv "$f.tmp" "$f"; else rm -f "$f.tmp"; echo "VISUDO VALIDATION FAILED"; exit 1; fi
|
||||||
|
|
||||||
|
verify:
|
||||||
|
- name: infra-ops user exists
|
||||||
|
shell: id {{ ops_user }}
|
||||||
|
changed_when: "false"
|
||||||
|
- name: authorized_keys present, mode 600, correct key
|
||||||
|
sudo: true
|
||||||
|
shell: test "$(stat -c %a /home/{{ ops_user }}/.ssh/authorized_keys)" = 600 && grep -q 'infra-ops@pfi-fleet' /home/{{ ops_user }}/.ssh/authorized_keys
|
||||||
|
changed_when: "false"
|
||||||
|
- name: sudoers file valid + NOPASSWD present
|
||||||
|
sudo: true
|
||||||
|
shell: visudo -cf /etc/sudoers.d/{{ ops_user }} && grep -q 'NOPASSWD:ALL' /etc/sudoers.d/{{ ops_user }}
|
||||||
|
changed_when: "false"
|
||||||
Executable
+62
@@ -0,0 +1,62 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Fleet bootstrap of the `infra-ops` agent identity across PFI-OWNED boxes.
|
||||||
|
# Runs playbooks/bootstrap-infra-ops-user.yaml on each host via elway.
|
||||||
|
#
|
||||||
|
# RUN THIS YOURSELF (operator). Each host's first bootstrap needs YOUR sudo, so
|
||||||
|
# elway prompts for your sudo password once per host (lazy). After bootstrap the
|
||||||
|
# agent authenticates as `infra-ops` with its own key (NOPASSWD) and never needs
|
||||||
|
# your password again — re-runs are then promptless and idempotent.
|
||||||
|
#
|
||||||
|
# Usage:
|
||||||
|
# scripts/bootstrap-infra-ops-fleet.sh # tiers 1+2 (default)
|
||||||
|
# PFI_TIER=compute scripts/bootstrap-infra-ops-fleet.sh # tier 1 only
|
||||||
|
# PFI_TIER=all scripts/bootstrap-infra-ops-fleet.sh # + tier 3 sensitive infra
|
||||||
|
# scripts/bootstrap-infra-ops-fleet.sh irv-ml1 # explicit host(s)
|
||||||
|
#
|
||||||
|
# SCOPE — PFI-owned Linux boxes ONLY. This list deliberately EXCLUDES:
|
||||||
|
# SureFire sf-*/sfsrv-ana (tenant), corviduo-dev (partner app-layer),
|
||||||
|
# esh-* (non-PFI home lab), nh3-nas (Synology DSM — no useradd/sudoers.d).
|
||||||
|
# Hosts must resolve as ssh aliases (or be reachable as your account).
|
||||||
|
|
||||||
|
set -uo pipefail
|
||||||
|
cd "$(dirname "$0")/.."
|
||||||
|
|
||||||
|
PLAYBOOK=playbooks/bootstrap-infra-ops-user.yaml
|
||||||
|
|
||||||
|
# Tier 1 — compute / docker hosts (where the agent recurs)
|
||||||
|
TIER1=(irv-ml1 ana-ml2 ana-docker nh3-docker)
|
||||||
|
# Tier 2 — PFI app VMs / LXCs
|
||||||
|
TIER2=(pfi-ana-webhost ana-filebot pfi-pteradactyl pfi-tacticalrmm ana-nas)
|
||||||
|
# Tier 3 — sensitive infra (DB, backup, network, hypervisors): opt-in via PFI_TIER=all
|
||||||
|
TIER3=(pfi-postgres pbs-ana pbs-nh3 ana-wg pfi-pve nh3-pve)
|
||||||
|
|
||||||
|
if [ "$#" -gt 0 ]; then
|
||||||
|
HOSTS=("$@")
|
||||||
|
else
|
||||||
|
case "${PFI_TIER:-default}" in
|
||||||
|
compute) HOSTS=("${TIER1[@]}") ;;
|
||||||
|
all) HOSTS=("${TIER1[@]}" "${TIER2[@]}" "${TIER3[@]}") ;;
|
||||||
|
*) HOSTS=("${TIER1[@]}" "${TIER2[@]}") ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "infra-ops fleet bootstrap → ${#HOSTS[@]} host(s):"
|
||||||
|
printf ' %s\n' "${HOSTS[@]}"
|
||||||
|
echo
|
||||||
|
|
||||||
|
declare -a OK=() FAIL=()
|
||||||
|
for h in "${HOSTS[@]}"; do
|
||||||
|
echo "================ $h ================"
|
||||||
|
if scripts/elway "$h" --playbook "$PLAYBOOK"; then
|
||||||
|
OK+=("$h")
|
||||||
|
else
|
||||||
|
FAIL+=("$h")
|
||||||
|
echo "!! bootstrap FAILED on $h (continuing)"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "================ summary ================"
|
||||||
|
echo "ok (${#OK[@]}): ${OK[*]:-none}"
|
||||||
|
echo "fail (${#FAIL[@]}): ${FAIL[*]:-none}"
|
||||||
|
[ "${#FAIL[@]}" -eq 0 ]
|
||||||
Reference in New Issue
Block a user