mirror of
https://github.com/dredx/prole.git
synced 2026-09-28 04:04:30 +00:00
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>
93 lines
3.2 KiB
Django/Jinja
93 lines
3.2 KiB
Django/Jinja
#!/usr/bin/expect -f
|
|
# RENDERED by Ansible (role: tplink_switch_harden). Do not edit the rendered copy.
|
|
# Drives a TP-Link JetStream switch CLI over SSH. Password comes from $env(SWITCH_PW)
|
|
# so it never appears in this file, the inventory, or the process arguments.
|
|
set timeout {{ switch_expect_timeout | default(30) }}
|
|
set host "{{ switch_mgmt_ip }}"
|
|
set user "{{ switch_admin_user }}"
|
|
set pw $env(SWITCH_PW)
|
|
set apply {{ '1' if switch_apply | bool else '0' }}
|
|
log_user 1
|
|
|
|
# Device prompt: User EXEC '>' or Privileged '#', optionally prefixed by hostname.
|
|
set PROMPT {[\w./-]*[>#] ?$}
|
|
|
|
proc wait_prompt {} {
|
|
global PROMPT
|
|
expect {
|
|
-re {(?i)--more--|press any key|\(q to quit\)} { send -- " "; exp_continue }
|
|
-re $PROMPT {}
|
|
timeout { puts "\n>>>TIMEOUT-AT-PROMPT<<<"; exit 21 }
|
|
eof { puts "\n>>>EOF-AT-PROMPT<<<"; exit 22 }
|
|
}
|
|
}
|
|
|
|
proc do {cmd} {
|
|
send -- "$cmd\r"
|
|
wait_prompt
|
|
}
|
|
|
|
# NOTE: PubkeyAuthentication=no + PreferredAuthentications=password is REQUIRED.
|
|
# The JetStream SSH server drops the connection when the client offers publickey
|
|
# auth first (the default), which made logins appear to "close after KEX" at
|
|
# random. Forcing password-only auth makes login deterministic.
|
|
spawn ssh -tt -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null \
|
|
-o ConnectTimeout=15 -o NumberOfPasswordPrompts=1 \
|
|
-o PreferredAuthentications=password -o PubkeyAuthentication=no $user@$host
|
|
|
|
expect {
|
|
-re {(?i)password:} { log_user 0; send -- "$pw\r"; log_user 1; exp_continue }
|
|
-re {(?i)(change.*password|new password|must.*change|set.*new password)} {
|
|
puts "\n>>>FORCED-PASSWORD-CHANGE<<< switch still at first-login; aborting."; exit 9
|
|
}
|
|
-re {(?i)(permission denied|authentication fail)} { puts "\n>>>AUTH-FAILED<<<"; exit 8 }
|
|
-re $PROMPT {}
|
|
timeout { puts "\n>>>LOGIN-TIMEOUT<<<"; exit 2 }
|
|
eof { puts "\n>>>LOGIN-EOF<<<"; exit 3 }
|
|
}
|
|
|
|
# Enter privileged EXEC (default enable password is blank).
|
|
send -- "enable\r"
|
|
expect {
|
|
-re {(?i)password:} { send -- "\r"; wait_prompt }
|
|
-re $PROMPT {}
|
|
timeout {}
|
|
}
|
|
|
|
# Always capture a read-only snapshot — proves access, changes nothing.
|
|
puts "\n>>>===SHOW-START===<<<"
|
|
do "show system-info"
|
|
puts "\n>>>===SHOW-END===<<<"
|
|
|
|
if {$apply == 1} {
|
|
puts "\n>>>===APPLY-START===<<<"
|
|
do "configure"
|
|
{% for c in switch_harden_commands %}
|
|
do {{ "{" ~ c.cmd ~ "}" }} ;# {{ c.desc }}
|
|
{% endfor %}
|
|
{% for c in switch_identity_commands %}
|
|
do {{ "{" ~ c.cmd ~ "}" }} ;# {{ c.desc }}
|
|
{% endfor %}
|
|
{% if switch_apply_network | bool %}
|
|
# NETWORK re-addressing. In steady state (already at the static IP) these are
|
|
# no-ops. During the one-time DHCP->static cutover the `ip address` line drops
|
|
# the session — do that via the dedicated migration step, not this engine.
|
|
{% for c in switch_network_commands %}
|
|
do {{ "{" ~ c.cmd ~ "}" }} ;# {{ c.desc }}
|
|
{% endfor %}
|
|
{% endif %}
|
|
do "end"
|
|
# Persist running-config to startup (JetStream may ask Y/N).
|
|
send -- "copy running-config startup-config\r"
|
|
expect {
|
|
-re {(?i)(y/n|are you sure|\[y/n\]|continue)} { send -- "Y\r"; wait_prompt }
|
|
-re $PROMPT {}
|
|
timeout {}
|
|
}
|
|
puts "\n>>>===APPLY-END===<<<"
|
|
}
|
|
|
|
send -- "exit\r"
|
|
catch {expect eof}
|
|
puts "\n>>>===DONE===<<<"
|