Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions docs/runbooks/rotate-snmp-community.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,10 +207,15 @@ require deleting the row rather than blanking the field — then:
./scripts/snmp-verify.sh --old
```

It prompts for the old community without echoing it, and asserts each device now
refuses it. It requires a terminal and refuses a pipe on purpose — `echo "$old" |
...` would put the old community into your shell history, which is the leak this
tooling exists to close. Run it yourself; no script or agent can.
It asks for the old community **per device**, without echoing it, and asserts
each one now refuses it. Press Enter to skip a device — the usual case is
checking the one you just rotated, and a single string tested against all four
proves nothing about the three it never belonged to. It refuses to report
success if you skip everything.

It requires a terminal and refuses a pipe on purpose — `echo "$old" | ...` would
put the old community into your shell history, which is the leak this tooling
exists to close. Run it yourself; no script or agent can.

`--old` only checks devices that just passed their current-community check.
SNMPv2c has no "wrong community" reply — a device that rejects you simply drops
Expand Down
41 changes: 30 additions & 11 deletions scripts/snmp-verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
# Usage:
# scripts/snmp-verify.sh every device, current community
# scripts/snmp-verify.sh --device neo one device (name or IP)
# scripts/snmp-verify.sh --old also check the old one is refused
# scripts/snmp-verify.sh --old also check the old ones are refused
# scripts/snmp-verify.sh --dry-run show the mapping; no decrypt, no packets

set -euo pipefail
Expand Down Expand Up @@ -230,18 +230,21 @@ done <<< "${INVENTORY}"
# Old community
# ---------------------------------------------------------------------------
if ((CHECK_OLD)); then
# A terminal is required. Accepting the old community on stdin would let
# someone write `echo "$old" | scripts/snmp-verify.sh --old`, putting it into
# their shell history — the exact leak this script exists to close.
[[ -t 0 ]] || die "--old needs a terminal: it reads the old community without echoing it"
printf 'Old community (not echoed; written only to a 0600 file that is deleted on exit): ' >&2
IFS= read -rs OLD_COMMUNITY
printf '\n' >&2
[[ -n "${OLD_COMMUNITY}" ]] || die "no old community entered"
# A terminal is required. Accepting old communities on stdin would let someone
# write `echo "$old" | scripts/snmp-verify.sh --old`, putting them into their
# shell history — the exact leak this script exists to close.
[[ -t 0 ]] || die "--old needs a terminal: it reads the old communities without echoing them"

head_ "Old community (must be refused)"
write_conf "${WORK}/old" "${OLD_COMMUNITY}" "the old community"

# Asked for per device, not once. A single prompt was right when one community
# was shared across all four; after a rotation each device has its own
# predecessor, and testing one string against every device proves nothing
# about the three it never belonged to. Enter is a skip, because the usual
# case is checking one device you just rotated.
printf ' Each device is asked separately. Press Enter to skip one.\n\n' >&2

asked=0
while IFS=$'\t' read -r ip auth device var; do
[[ -n "${ip}" ]] || continue

Expand All @@ -258,8 +261,22 @@ if ((CHECK_OLD)); then
continue
fi

# Read from the terminal explicitly: this loop's stdin is the inventory.
printf ' old community for %-10s (not echoed, Enter to skip): ' "${device}" >&2
IFS= read -rs old_one < /dev/tty
printf '\n' >&2

if [[ -z "${old_one}" ]]; then
skip "$(printf '%-10s %-12s %s' "${device}" "${ip}" "not checked — no old community given")"
continue
fi
asked=$((asked + 1))

write_conf "${WORK}/old-${device}" "${old_one}" "the old community for ${device}"
old_one=""

# -r 0: a timeout is the expected outcome, so retrying only doubles the wait.
probe "${ip}" "${WORK}/old" 0
probe "${ip}" "${WORK}/old-${device}" 0

case "${PROBE_STATUS}" in
ok|nosuchobject)
Expand All @@ -273,6 +290,8 @@ if ((CHECK_OLD)); then
;;
esac
done <<< "${INVENTORY}"

((asked > 0)) || die "no old community entered for any device — nothing was checked"
fi

printf '\n'
Expand Down
Loading