prole/etc/set-switch-1password.sh
chrisfu ca8dd6a525 Add TP-Link SG2428LP switch hardening as repeatable IaC + static IP/DNS
Secure and configure the SG2428LP managed switch (10.0.0.10) as repeatable
Ansible, driven from the control node over SSH by an expect engine.

- tplink_switch_harden role (verified JetStream CLI syntax): HTTP off/
  HTTPS-only, Telnet off, SSH v2, SNMP off, RSTP + loopback-detection,
  then copy running-config startup-config (persist).
- playbooks/harden_switch.yml + [switches] inventory group; dry by default,
  -e switch_apply=true to apply, -e switch_apply_network=true for addressing.
- Static mgmt IP 10.0.0.10/24 (off DHCP, below the .20-.199 pool), gw 10.0.0.1,
  hostname sg2428lp.
- op -> ansible-vault bridge (etc/set-switch-1password.sh); admin password in
  group_vars/switches/vault.yml.
- DNS: A/PTR sg2428lp.prole.org -> 10.0.0.10 (records added to dns.yml/ptr_records).
- Fix: tag the samba_reverse_dns zone-list set_fact so tag-limited runs define
  samba_reverse_zones.

Engine notes (hard-won): forces password-only SSH auth (the switch drops
publickey-first logins); always saves config (unsaved changes revert on reboot).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-10 00:20:55 -07:00

63 lines
2.6 KiB
Bash
Executable File

#!/usr/bin/env bash
# set-switch-1password.sh
# 1Password -> Ansible Vault bridge for the managed switch admin credential.
#
# Reads the switch admin password from 1Password (op) and writes it, encrypted
# with the repo .vault_pass, into the switches group_vars vault file. This keeps
# 1Password the human source of truth while ansible-vault is what the playbook
# consumes at run time.
#
# op://knoey/TP-Link SG2428LP/password --(op read)--> ENV --(ansible-vault)-->
# infrastructure/inventory/group_vars/switches/vault.yml
#
# Usage:
# etc/set-switch-1password.sh # default item/account below
# OP_ACCOUNT=... OP_REFERENCE=... etc/set-switch-1password.sh
#
# Requires: op (1Password CLI, unlocked), ansible-vault, repo .vault_pass.
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
cd "${REPO_ROOT}"
OP_ACCOUNT="${OP_ACCOUNT:-VM7LPYA4XFF4TE2DEI7ZHMZWEA}"
OP_REFERENCE="${OP_REFERENCE:-op://knoey/TP-Link SG2428LP/password}"
VAR_NAME="${VAR_NAME:-vault_sg2428lp_admin_password}"
VAULT_FILE="${VAULT_FILE:-infrastructure/inventory/group_vars/switches/vault.yml}"
command -v op >/dev/null 2>&1 || { echo "ERROR: 1Password CLI (op) not found. brew install 1password-cli" >&2; exit 1; }
command -v ansible-vault >/dev/null 2>&1 || { echo "ERROR: ansible-vault not found." >&2; exit 1; }
[[ -f .vault_pass ]] || { echo "ERROR: .vault_pass not found at repo root." >&2; exit 1; }
# op desktop integration sometimes needs an explicit unlock.
if ! op read --account "$OP_ACCOUNT" "$OP_REFERENCE" >/dev/null 2>&1; then
echo "Unlocking 1Password (op signin)..." >&2
op signin --account "$OP_ACCOUNT" >/dev/null || true
fi
SECRET="$(op read --account "$OP_ACCOUNT" "$OP_REFERENCE" 2>/dev/null || true)"
if [[ -z "$SECRET" ]]; then
echo "ERROR: could not read secret from $OP_REFERENCE (is 1Password unlocked?)" >&2
exit 1
fi
echo "Read secret from 1Password (${#SECRET} chars)." >&2
# Encrypt as an inline !vault var. Rely on ansible.cfg's vault_password_file so
# we don't create a duplicate 'default' vault-id (which ansible-vault rejects).
ENC="$(printf '%s' "$SECRET" | ansible-vault encrypt_string --stdin-name "$VAR_NAME")"
unset SECRET
{
echo "---"
echo "# Managed-switch admin password — DO NOT EDIT BY HAND."
echo "# Source of truth: ${OP_REFERENCE}"
echo "# Regenerate with: etc/set-switch-1password.sh"
echo "${ENC}"
} > "${VAULT_FILE}"
echo "Wrote encrypted ${VAR_NAME} -> ${VAULT_FILE}"
echo "Verify (length only):"
echo " ansible localhost -m debug -a \"msg={{ ${VAR_NAME} | length }}\" -e @${VAULT_FILE}"