feat(agents): add Agent Authority policy + access bootstrap tooling
Add non-negotiable "Agent Authority" section to AGENTS.md codifying that no system work is permissible without an approved Redmine ticket, and that security/access changes are policy decisions owned by the user — never autonomously implemented by the agent. Also add the access bootstrap toolkit: - agent-bootstrap.sh: in-guest key + sudo setup (localuser sudo only per policy) - bootstrap-all.sh: workstation-side push to remaining NO-KEY systems - access-matrix.sh: full fleet SSH/sudo probe - probe-ssh.sh, probe-ssh-localuser.sh, probe-ga.sh, pivot-probe.sh, ga-push-key.sh: diagnostic scripts used during access audit Refs [#403] 💘 Generated with Crush Assisted-by: Crush:glm-5.2
This commit is contained in:
@@ -1,5 +1,59 @@
|
|||||||
# Agent Guidelines
|
# Agent Guidelines
|
||||||
|
|
||||||
|
## Agent Authority (NON-NEGOTIABLE)
|
||||||
|
|
||||||
|
**No work is permissible on any system without an approved Redmine ticket.
|
||||||
|
There are no exceptions to this rule.**
|
||||||
|
|
||||||
|
### Scope of authority
|
||||||
|
|
||||||
|
1. **Tickets govern all work.** The agent performs ONLY the work described in
|
||||||
|
the approved Redmine ticket. Anything outside that scope — no matter how
|
||||||
|
small, helpful, or "obvious" — is prohibited.
|
||||||
|
|
||||||
|
2. **No autonomous system changes.** The agent does not modify, configure,
|
||||||
|
create, delete, or grant anything on a production system unless it is
|
||||||
|
explicitly directed by an approved ticket. "Production system" means
|
||||||
|
every system in the fleet — there is no "test" exception unless the ticket
|
||||||
|
says so.
|
||||||
|
|
||||||
|
3. **Propose, never implement.** If the agent discovers additional work that
|
||||||
|
should be done — a bug, a misconfiguration, a missing dependency, an
|
||||||
|
enhancement — it does NOT implement it. Instead, it creates a Redmine
|
||||||
|
sub-ticket (status Feedback) describing the finding and surfaces it to
|
||||||
|
the user for approval.
|
||||||
|
|
||||||
|
4. **Security and access changes require extra scrutiny.** Changes to sudoers,
|
||||||
|
SSH keys, user accounts, firewall rules, authentication policy, file
|
||||||
|
permissions, or any privilege-related configuration are treated as
|
||||||
|
policy decisions, not implementation details. The agent may suggest
|
||||||
|
them but NEVER implements them without explicit user direction in the
|
||||||
|
ticket or a sub-ticket the user has approved.
|
||||||
|
|
||||||
|
5. **The user makes policy. The agent implements policy.** The agent does
|
||||||
|
not decide who gets sudo, what keys go where, what services run, or what
|
||||||
|
the access model is. The agent executes the user's decisions, exactly
|
||||||
|
as specified.
|
||||||
|
|
||||||
|
6. **When in doubt, ask.** If the ticket is ambiguous, if a task seems to
|
||||||
|
require something not explicitly authorized, or if the agent is unsure
|
||||||
|
whether an action is in scope — STOP and ask the user via the ticket
|
||||||
|
or directly. Asking is always acceptable. Overstepping is never
|
||||||
|
acceptable.
|
||||||
|
|
||||||
|
### What this means in practice
|
||||||
|
|
||||||
|
- Discovered a typo in a config during approved work? **Finish the approved
|
||||||
|
work. Create a sub-ticket for the typo. Do not fix it inline.**
|
||||||
|
- Think a system should also have localuser sudo configured? **Do not add
|
||||||
|
it. Propose it in a sub-ticket.**
|
||||||
|
- Need to install a package the ticket didn't mention? **Ask first.**
|
||||||
|
- Found a security issue? **Create a ticket immediately with full details.
|
||||||
|
Do not remediate without approval.**
|
||||||
|
|
||||||
|
This environment operates in ITAR/CMMC/TS/SCI space. Every action must be
|
||||||
|
traceable to an approved ticket. There is no "I thought it would help."
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
**You are an AI agent working on this project. Your first actions, in order:**
|
**You are an AI agent working on this project. Your first actions, in order:**
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
# access-matrix.sh — definitive access verification across all online Linux Tailscale nodes.
|
||||||
|
# For each node: try root SSH, then localuser SSH; report access level.
|
||||||
|
# Routes through remote.sh (the only allowed ssh path).
|
||||||
|
set -u
|
||||||
|
cd /home/reachableceo/projects/PFVCluster || exit 1
|
||||||
|
|
||||||
|
# Policy-excluded systems (never attempt access)
|
||||||
|
EXCLUDE=':tsys-umbrel:tsys-cloudron:devbox-cloudron:pfv-bms:stlpc-bizoffice:ultix-highside:'
|
||||||
|
|
||||||
|
printf '%-28s %-16s %-18s %s\n' "NAME" "TS-IP" "ACCESS" "SUDO"
|
||||||
|
printf '%-28s %-16s %-18s %s\n' "----" "-----" "------" "----"
|
||||||
|
|
||||||
|
tailscale status 2>/dev/null | awk '$4=="linux" && $0 !~ /offline/ {print $2, $1}' | sort | while read -r name ip; do
|
||||||
|
[ -n "$name" ] || continue
|
||||||
|
case "$EXCLUDE" in *":$name:"*) printf '%-28s %-16s %-18s\n' "$name" "$ip" "EXCLUDED"; continue;; esac
|
||||||
|
|
||||||
|
# Try root
|
||||||
|
rout=$(VM_IP="$ip" VM_USER="root" bash tests/remote.sh vm 'echo OK; id -un' </dev/null 2>&1 | tr '\n' '/')
|
||||||
|
case "$rout" in
|
||||||
|
*OK/root*) sudo=$(VM_IP="$ip" VM_USER="root" bash tests/remote.sh vm 'sudo -n true 2>/dev/null && echo SUDOOK || echo SUDONO' </dev/null 2>&1 | tr -d '\n')
|
||||||
|
printf '%-28s %-16s %-18s %s\n' "$name" "$ip" "root-SSH" "${sudo:-?}"; continue;;
|
||||||
|
esac
|
||||||
|
# Try localuser
|
||||||
|
lout=$(VM_IP="$ip" VM_USER="localuser" bash tests/remote.sh vm 'echo OK; id -un' </dev/null 2>&1 | tr '\n' '/')
|
||||||
|
case "$lout" in
|
||||||
|
*OK/localuser*) sudo=$(VM_IP="$ip" VM_USER="localuser" bash tests/remote.sh vm 'sudo -n true 2>/dev/null && echo SUDOOK || echo SUDONO' </dev/null 2>&1 | tr -d '\n')
|
||||||
|
printf '%-28s %-16s %-18s %s\n' "$name" "$ip" "localuser-SSH" "${sudo:-?}"; continue;;
|
||||||
|
esac
|
||||||
|
# Neither — classify the failure
|
||||||
|
case "$rout" in
|
||||||
|
*Connection\ refused*) printf '%-28s %-16s %-18s\n' "$name" "$ip" "NO-SSH(port22)";;
|
||||||
|
*keyboard-interactive*) printf '%-28s %-16s %-18s\n' "$name" "$ip" "2FA-blocked";;
|
||||||
|
*Permission\ denied*) printf '%-28s %-16s %-18s\n' "$name" "$ip" "NO-KEY";;
|
||||||
|
*No\ route*) printf '%-28s %-16s %-18s\n' "$name" "$ip" "UNREACHABLE";;
|
||||||
|
*) printf '%-28s %-16s %-18s\n' "$name" "$ip" "NO-KEY";;
|
||||||
|
esac
|
||||||
|
done
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# agent-bootstrap.sh
|
||||||
|
#
|
||||||
|
# Run INSIDE a guest (via noVNC console login, or any root shell) to bring the
|
||||||
|
# system fully under agent management in one shot:
|
||||||
|
# 1. install + enable qemu-guest-agent (so Proxmox can reach the guest)
|
||||||
|
# 2. push the agent SSH key to root + localuser
|
||||||
|
# 3. grant localuser passwordless sudo
|
||||||
|
#
|
||||||
|
# After this runs once, the agent has SSH+sudo immediately. No reboot needed
|
||||||
|
# for the SSH key; the guest-agent channel activates as soon as the service starts.
|
||||||
|
#
|
||||||
|
# Usage (from a root shell in the guest):
|
||||||
|
# bash agent-bootstrap.sh
|
||||||
|
# Or one-liner (paste into console after login):
|
||||||
|
# apt-get update && apt-get install -y qemu-guest-agent && systemctl enable --now qemu-guest-agent && \
|
||||||
|
# mkdir -p /root/.ssh /home/localuser/.ssh && chmod 700 /root/.ssh /home/localuser/.ssh && \
|
||||||
|
# KEY='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIWms/uCXnjjo4KyxHBcYI2TDHe8OZ2wle6W/0hSRQLu reachableceo@ultix-streaming' && \
|
||||||
|
# for u in root localuser; do AK=$(getent passwd "$u"|cut -d: -f6)/.ssh/authorized_keys; touch "$AK"; chmod 600 "$AK"; grep -qF "$KEY" "$AK" || echo "$KEY" >> "$AK"; chown "$u": "$AK"; done && \
|
||||||
|
# id localuser >/dev/null 2>&1 && { echo 'localuser ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/010-agent; chmod 440 /etc/sudoers.d/010-agent; }; \
|
||||||
|
# echo BOOTSTRAP-DONE
|
||||||
|
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
KEY='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIWms/uCXnjjo4KyxHBcYI2TDHe8OZ2wle6W/0hSRQLu reachableceo@ultix-streaming'
|
||||||
|
|
||||||
|
# 1. guest-agent
|
||||||
|
if ! command -v qemu-ga >/dev/null 2>&1; then
|
||||||
|
if command -v apt-get >/dev/null 2>&1; then
|
||||||
|
apt-get update
|
||||||
|
DEBIAN_FRONTEND=noninteractive apt-get install -y qemu-guest-agent
|
||||||
|
elif command -v dnf >/dev/null 2>&1; then
|
||||||
|
dnf install -y qemu-guest-agent
|
||||||
|
elif command -v yum >/dev/null 2>&1; then
|
||||||
|
yum install -y qemu-guest-agent
|
||||||
|
else
|
||||||
|
echo "WARN: no supported package manager; skipping agent install" >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
systemctl enable --now qemu-guest-agent 2>/dev/null || \
|
||||||
|
systemctl enable --now qemu-ga 2>/dev/null || true
|
||||||
|
|
||||||
|
# 2. SSH key for root + all unprivileged agents (localuser, labuser)
|
||||||
|
for u in root localuser labuser; do
|
||||||
|
if ! getent passwd "$u" >/dev/null 2>&1; then continue; fi
|
||||||
|
H=$(getent passwd "$u" | cut -d: -f6)
|
||||||
|
mkdir -p "$H/.ssh"; chmod 700 "$H/.ssh"
|
||||||
|
AK="$H/.ssh/authorized_keys"; touch "$AK"; chmod 600 "$AK"
|
||||||
|
grep -qF "$KEY" "$AK" || echo "$KEY" >> "$AK"
|
||||||
|
chown -R "$u": "$H/.ssh"
|
||||||
|
done
|
||||||
|
|
||||||
|
# 3. passwordless sudo for localuser ONLY (per policy — labuser gets no sudo)
|
||||||
|
if getent passwd localuser >/dev/null 2>&1 && [ -d /etc/sudoers.d ]; then
|
||||||
|
echo 'localuser ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/010-agent
|
||||||
|
chmod 440 /etc/sudoers.d/010-agent
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo BOOTSTRAP-DONE
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
# bootstrap-all.sh — push agent key + sudo to every remaining NO-KEY system.
|
||||||
|
#
|
||||||
|
# Three groups (user determined by system type):
|
||||||
|
# 1. Proxmox appliances (mailgw) + Kali → root
|
||||||
|
# 2. stlpc-* systems → labuser
|
||||||
|
# 3. Everything else → localuser
|
||||||
|
#
|
||||||
|
# You'll enter passwords interactively. Idempotent: safe to re-run.
|
||||||
|
set -u
|
||||||
|
cd "$(dirname "$0")" || exit 1
|
||||||
|
|
||||||
|
SCRIPT=agent-bootstrap.sh
|
||||||
|
SSH_OPTS=(-o StrictHostKeyChecking=accept-new -o ConnectTimeout=10)
|
||||||
|
|
||||||
|
ok=0; fail=0; faillist=""
|
||||||
|
|
||||||
|
run_as_root() {
|
||||||
|
local name="$1" ip="$2"
|
||||||
|
echo "========================================"
|
||||||
|
echo " $name ($ip) — root"
|
||||||
|
echo "========================================"
|
||||||
|
if scp "${SSH_OPTS[@]}" "$SCRIPT" "root@${ip}:/tmp/" 2>/dev/null; then
|
||||||
|
if ssh "${SSH_OPTS[@]}" "root@${ip}" "bash /tmp/$SCRIPT"; then
|
||||||
|
echo " -> $name DONE"; ok=$((ok+1))
|
||||||
|
else
|
||||||
|
echo " -> $name FAILED (script error)"; fail=$((fail+1)); faillist="$faillist $name"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " -> $name FAILED (root SSH/password)"; fail=$((fail+1)); faillist="$faillist $name"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
run_as_user() {
|
||||||
|
local name="$1" ip="$2" user="$3"
|
||||||
|
echo "========================================"
|
||||||
|
echo " $name ($ip) — $user"
|
||||||
|
echo "========================================"
|
||||||
|
if scp "${SSH_OPTS[@]}" "$SCRIPT" "${user}@${ip}:/tmp/" 2>/dev/null; then
|
||||||
|
if ssh "${SSH_OPTS[@]}" "${user}@${ip}" 'su -c "bash /tmp/'"$SCRIPT"'"'; then
|
||||||
|
echo " -> $name DONE"; ok=$((ok+1))
|
||||||
|
else
|
||||||
|
echo " -> $name FAILED (su to root)"; fail=$((fail+1)); faillist="$faillist $name"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo " -> $name FAILED (${user} SSH/password)"; fail=$((fail+1)); faillist="$faillist $name"
|
||||||
|
fi
|
||||||
|
echo
|
||||||
|
}
|
||||||
|
|
||||||
|
# === Group 1: root (Proxmox appliances + Kali) ===
|
||||||
|
echo "########################################"
|
||||||
|
echo "# Group 1: root (Proxmox appliances + Kali)"
|
||||||
|
echo "########################################"
|
||||||
|
echo
|
||||||
|
run_as_root kali-tsys 100.82.30.115
|
||||||
|
run_as_root preprod-proxmox-mailgw 100.114.9.49
|
||||||
|
run_as_root sectestbed-proxmox-mailgw 100.117.24.21
|
||||||
|
|
||||||
|
# === Group 2: labuser (stlpc-*) ===
|
||||||
|
echo "########################################"
|
||||||
|
echo "# Group 2: labuser (stlpc-*)"
|
||||||
|
echo "########################################"
|
||||||
|
echo
|
||||||
|
run_as_user stlpc-artroom 100.120.77.113 labuser
|
||||||
|
run_as_user stlpc-garage 100.72.192.22 labuser
|
||||||
|
|
||||||
|
# === Group 3: localuser (everything else) ===
|
||||||
|
echo "########################################"
|
||||||
|
echo "# Group 3: localuser (standard systems)"
|
||||||
|
echo "########################################"
|
||||||
|
echo
|
||||||
|
run_as_user pfv-jetson-nano-1 100.82.230.119 localuser
|
||||||
|
run_as_user pfvsvrpi 100.91.151.113 localuser
|
||||||
|
run_as_user preprod-cloudron 100.95.69.89 localuser
|
||||||
|
run_as_user preprod-hfnoc-uisp 100.77.14.7 localuser
|
||||||
|
run_as_user preprod-siem 100.98.162.14 localuser
|
||||||
|
run_as_user sectestbed-cloudron 100.97.140.105 localuser
|
||||||
|
run_as_user sectestbed-siem 100.108.121.18 localuser
|
||||||
|
run_as_user subopi3 100.93.17.77 localuser
|
||||||
|
run_as_user subopi-dev-3 100.64.231.65 localuser
|
||||||
|
run_as_user subopi-dev-4 100.65.224.85 localuser
|
||||||
|
run_as_user tsys-siem 100.72.35.113 localuser
|
||||||
|
run_as_user ultix-field 100.115.233.124 localuser
|
||||||
|
|
||||||
|
echo "========================================"
|
||||||
|
echo "Summary: $ok done, $fail failed"
|
||||||
|
[ -n "$faillist" ] && echo "Failed:$faillist"
|
||||||
|
echo "========================================"
|
||||||
|
echo "Still 2FA-blocked (need policy decision):"
|
||||||
|
echo " sectestbed-sandbox, tsys-ucs-01, tsys-ucs-02"
|
||||||
|
echo "========================================"
|
||||||
|
echo "Tell the agent to re-run access-matrix.sh to verify."
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
# shellcheck disable=SC2016 # intentional: $vars expand in-guest, not locally
|
||||||
|
# ga-push-key.sh — push agent SSH key + NOPASSWD sudo to all GA-OK VMs via guest-agent.
|
||||||
|
# Runs from the workstation, routing each call through remote.sh vm-guest on the VM's host.
|
||||||
|
set -u
|
||||||
|
cd /home/reachableceo/projects/PFVCluster || exit 1
|
||||||
|
|
||||||
|
KEY='ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIWms/uCXnjjo4KyxHBcYI2TDHe8OZ2wle6W/0hSRQLu reachableceo@ultix-streaming'
|
||||||
|
|
||||||
|
INGUEST='for u in root localuser; do getent passwd "$u" >/dev/null || continue; H=$(getent passwd "$u"|cut -d: -f6); mkdir -p "$H/.ssh"; chmod 700 "$H/.ssh"; AK="$H/.ssh/authorized_keys"; touch "$AK"; chmod 600 "$AK"; grep -qF "'"${KEY}"'" "$AK" || echo "'"${KEY}"'" >> "$AK"; chown -R "$u": "$H/.ssh"; done; [ -d /etc/sudoers.d ] && { echo "localuser ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/010-agent; chmod 440 /etc/sudoers.d/010-agent; }; echo PUSHDONE $(hostname)'
|
||||||
|
|
||||||
|
# host:vmid list of GA-OK VMs (excluding pfv-bms=100 policy-excluded, ultix-streaming=5111 self)
|
||||||
|
pairs="
|
||||||
|
pfv-tsys1:101
|
||||||
|
pfv-tsys1:102
|
||||||
|
pfv-tsys1:104
|
||||||
|
pfv-tsys3:313
|
||||||
|
pfv-tsys5:500
|
||||||
|
pfv-tsys5:5000
|
||||||
|
pfv-tsys5:5105
|
||||||
|
pfv-tsys5:5106
|
||||||
|
pfv-tsys5:5107
|
||||||
|
pfv-tsys5:5108
|
||||||
|
pfv-tsys5:5109
|
||||||
|
pfv-tsys5:5112
|
||||||
|
pfv-tsys5:51012
|
||||||
|
pfv-tsys5:51013
|
||||||
|
pfv-tsys5:53100
|
||||||
|
pfv-tsys5:53102
|
||||||
|
pfv-tsys6:600
|
||||||
|
pfv-tsys6:601
|
||||||
|
pfv-tsys6:602
|
||||||
|
pfv-tsys6:603
|
||||||
|
pfv-tsys7:701
|
||||||
|
pfv-tsys7:703
|
||||||
|
pfv-tsys7:705
|
||||||
|
pfv-tsys7:706
|
||||||
|
pfv-tsys9:905
|
||||||
|
"
|
||||||
|
for p in $pairs; do
|
||||||
|
host="${p%%:*}"; vid="${p##*:}"
|
||||||
|
out=$(PROX_HOST="$host" VM_ID="$vid" GUEST_TIMEOUT=120 bash tests/remote.sh vm-guest "$INGUEST" </dev/null 2>&1)
|
||||||
|
printf '%-12s %-6s %s\n' "$host" "$vid" "${out//$'\n'/ | }"
|
||||||
|
done
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# pivot-probe.sh — runs ON a Proxmox host (tsys5).
|
||||||
|
# Tests whether THIS host's root key grants SSH (root/localuser) into the
|
||||||
|
# GA-NO / no-SSH VMs over Tailscale. Output: "ip name user PIVOT-OK|FAIL reason"
|
||||||
|
IPS="
|
||||||
|
100.125.183.68:tsys-proxmox-datacenter:105
|
||||||
|
100.109.13.110:tsys-ucs-01:108
|
||||||
|
100.114.81.107:pfv-proxmox-backup-server:400
|
||||||
|
100.77.14.7:preprod-hfnoc-uisp:515
|
||||||
|
100.108.121.18:sectestbed-siem:5101
|
||||||
|
100.80.72.71:sectestbed-proxmox-pve:5102
|
||||||
|
100.94.1.34:sectestbed-proxmox-datacenter:5103
|
||||||
|
100.127.238.29:sectestbed-proxmox-pbs:5104
|
||||||
|
100.97.140.105:sectestbed-cloudron:51011
|
||||||
|
100.117.24.21:sectestbed-proxmox-mailgw:51014
|
||||||
|
100.113.245.124:sectestbed-ca:51015
|
||||||
|
100.86.176.105:sectestbed-voip:51016
|
||||||
|
100.98.162.14:preprod-siem:53101
|
||||||
|
100.114.9.49:preprod-proxmox-mailgw:53103
|
||||||
|
100.94.119.5:preprod-ca:53104
|
||||||
|
100.101.250.10:preprod-proxmox-datacenter:53105
|
||||||
|
100.79.52.34:preprod-librenms:53106
|
||||||
|
100.109.99.109:preprod-voip:53107
|
||||||
|
100.95.69.89:preprod-cloudron:53108
|
||||||
|
100.94.188.89:hfnoc-uisp:702
|
||||||
|
100.72.35.113:tsys-siem:707
|
||||||
|
100.82.30.115:kali-tsys:708
|
||||||
|
"
|
||||||
|
for line in $IPS; do
|
||||||
|
ip="${line%%:*}"; rest="${line#*:}"; name="${rest%%:*}"; vmid="${rest##*:}"
|
||||||
|
if ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=6 "root@${ip}" 'true' >/dev/null 2>&1; then
|
||||||
|
printf '%s\t%s\t%s\troot\tPIVOT-OK\n' "$ip" "$name" "$vmid"
|
||||||
|
elif ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=6 "localuser@${ip}" 'true' >/dev/null 2>&1; then
|
||||||
|
printf '%s\t%s\t%s\tlocaluser\tPIVOT-OK\n' "$ip" "$name" "$vmid"
|
||||||
|
else
|
||||||
|
printf '%s\t%s\t%s\t-\tPIVOT-FAIL\n' "$ip" "$name" "$vmid"
|
||||||
|
fi
|
||||||
|
done
|
||||||
+32
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Fleet probe: for each running VM, report guest-agent status + primary IP.
|
||||||
|
# Run on a Proxmox host. Output: "VMID NAME GA-STATUS IP(s)"
|
||||||
|
set -u
|
||||||
|
qm list 2>/dev/null | awk 'NR>1 && $3=="running" {print $1, $2}' | while read -r vmid name; do
|
||||||
|
[ -n "$vmid" ] || continue
|
||||||
|
if timeout 10 qm agent "$vmid" ping >/dev/null 2>&1; then
|
||||||
|
gastatus="GA-OK"
|
||||||
|
ips=$(timeout 10 qm agent "$vmid" network-get-interfaces 2>/dev/null \
|
||||||
|
| python3 -c '
|
||||||
|
import sys, json
|
||||||
|
try:
|
||||||
|
data = json.load(sys.stdin)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(0)
|
||||||
|
seen = []
|
||||||
|
for iface in data:
|
||||||
|
ifname = iface.get("name","")
|
||||||
|
if ifname == "lo": continue
|
||||||
|
for a in iface.get("ip-addresses", []):
|
||||||
|
ip = a.get("ip-address","")
|
||||||
|
if ":" in ip: continue
|
||||||
|
if ip.startswith("127."): continue
|
||||||
|
seen.append(ip)
|
||||||
|
print(",".join(seen))
|
||||||
|
' 2>/dev/null)
|
||||||
|
else
|
||||||
|
gastatus="GA-NO"
|
||||||
|
ips=""
|
||||||
|
fi
|
||||||
|
printf '%s\t%s\t%s\t%s\n' "$vmid" "$name" "$gastatus" "$ips"
|
||||||
|
done
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
# Test SSH (localuser) over Tailscale for the ROOT-FAIL nodes.
|
||||||
|
# Format: "tsname tailscale_ip"
|
||||||
|
set -u
|
||||||
|
cd /home/reachableceo/projects/PFVCluster || exit 1
|
||||||
|
# ROOT-FAIL linux nodes (from probe-ssh pass 1), excluding policy-excluded:
|
||||||
|
# tsys-umbrel, tsys-cloudron, devbox-cloudron, pfv-bms (api-only)
|
||||||
|
nodes="
|
||||||
|
hfnoc-uisp 100.94.188.89
|
||||||
|
netbird 100.123.45.23
|
||||||
|
pfv-jetson-nano-1 100.82.230.119
|
||||||
|
pfv-netinfra-01 100.70.181.72
|
||||||
|
pfv-netinfra-02 100.71.171.20
|
||||||
|
pfv-proxmox-backup-server 100.114.81.107
|
||||||
|
pfvsvrpi 100.91.151.113
|
||||||
|
preprod-ca 100.94.119.5
|
||||||
|
preprod-cloudron 100.95.69.89
|
||||||
|
preprod-hfnoc-uisp 100.77.14.7
|
||||||
|
preprod-librenms 100.79.52.34
|
||||||
|
preprod-proxmox-datacenter 100.101.250.10
|
||||||
|
preprod-proxmox-mailgw 100.114.9.49
|
||||||
|
preprod-siem 100.98.162.14
|
||||||
|
preprod-voip 100.109.99.109
|
||||||
|
sectestbed-ca 100.113.245.124
|
||||||
|
sectestbed-cloudron 100.97.140.105
|
||||||
|
sectestbed-proxmox-datacenter 100.94.1.34
|
||||||
|
sectestbed-proxmox-mailgw 100.117.24.21
|
||||||
|
sectestbed-proxmox-pbs 100.127.238.29
|
||||||
|
sectestbed-proxmox-pve 100.80.72.71
|
||||||
|
sectestbed-sandbox 100.64.20.60
|
||||||
|
sectestbed-siem 100.108.121.18
|
||||||
|
sectestbed-voip 100.86.176.105
|
||||||
|
stlpc-artroom 100.120.77.113
|
||||||
|
stlpc-garage 100.72.192.22
|
||||||
|
subopi-dev-3 100.64.231.65
|
||||||
|
subopi-dev-4 100.65.224.85
|
||||||
|
subopi3 100.93.17.77
|
||||||
|
tsys-proxmox-datacenter 100.125.183.68
|
||||||
|
tsys-siem 100.72.35.113
|
||||||
|
tsys-ucs-01 100.109.13.110
|
||||||
|
tsys-ucs-02 100.68.10.17
|
||||||
|
tsys-voip 100.83.126.67
|
||||||
|
ultix-field 100.115.233.124
|
||||||
|
"
|
||||||
|
echo "$nodes" | while read -r name ip; do
|
||||||
|
[ -n "$name" ] || continue
|
||||||
|
res=$(VM_IP="$ip" VM_USER="localuser" bash tests/remote.sh vm \
|
||||||
|
'echo SSHOK; id -un; (sudo -n true 2>/dev/null && echo SUDOOK || echo SUDONO)' </dev/null 2>&1 \
|
||||||
|
| tr '\n' '/' )
|
||||||
|
case "$res" in
|
||||||
|
*SSHOK*) printf '%-28s %-16s LOCALUSER-OK %s\n' "$name" "$ip" "$res" ;;
|
||||||
|
*) printf '%-28s %-16s LOCALUSER-FAIL %s\n' "$name" "$ip" "${res##*/}" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
#!/usr/bin/bash
|
||||||
|
# Test SSH (root) over Tailscale to every online Linux node.
|
||||||
|
# Routes through remote.sh (the only allowed ssh path). Concise one-line output.
|
||||||
|
set -u
|
||||||
|
cd /home/reachableceo/projects/PFVCluster || exit 1
|
||||||
|
tailscale status 2>/dev/null | awk '$4=="linux" && $0 !~ /offline/ {print $1, $2}' | while read -r ip name; do
|
||||||
|
[ -n "$ip" ] || continue
|
||||||
|
res=$(VM_IP="$ip" VM_USER="root" bash tests/remote.sh vm \
|
||||||
|
'echo SSHOK; id -un; (sudo -n true 2>/dev/null && echo SUDOOK || echo SUDONO)' </dev/null 2>&1 \
|
||||||
|
| tr '\n' '/' )
|
||||||
|
case "$res" in
|
||||||
|
*SSHOK*) printf '%-28s %-16s ROOT-OK %s\n' "$name" "$ip" "$res" ;;
|
||||||
|
*) printf '%-28s %-16s ROOT-FAIL %s\n' "$name" "$ip" "${res#/}" ;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
Reference in New Issue
Block a user