mirror of
https://github.com/dredx/prole.git
synced 2026-09-28 12:44:31 +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>
78 lines
3.4 KiB
Markdown
78 lines
3.4 KiB
Markdown
# tplink_switch_harden
|
|
|
|
Repeatable security hardening for TP-Link JetStream managed switches
|
|
(developed for the **SG2428LP** at `10.0.0.153`), driven from the control node
|
|
over SSH by a rendered `expect` engine.
|
|
|
|
## Secret pipeline
|
|
|
|
```
|
|
1Password ──op read──▶ ENV ──ansible-vault──▶ group_vars/switches/vault.yml ──▶ playbook
|
|
op://knoey/TP-Link SG2428LP/password (vault_sg2428lp_admin_password)
|
|
```
|
|
|
|
Refresh the vault from 1Password whenever the password changes:
|
|
|
|
```bash
|
|
etc/set-switch-1password.sh
|
|
```
|
|
|
|
The admin password is passed to the engine only via the `SWITCH_PW` environment
|
|
variable (`no_log`), so it never lands in inventory, the rendered script, or
|
|
process arguments.
|
|
|
|
## Usage
|
|
|
|
```bash
|
|
# Dry run — log in, capture `show system-info`, change nothing:
|
|
ansible-playbook infrastructure/playbooks/harden_switch.yml
|
|
|
|
# Apply hardening and save to startup-config:
|
|
ansible-playbook infrastructure/playbooks/harden_switch.yml -e switch_apply=true
|
|
```
|
|
|
|
`switch_apply` defaults to **false**; nothing is changed until you pass
|
|
`-e switch_apply=true`.
|
|
|
|
## What it hardens
|
|
|
|
Config-mode commands live in [`defaults/main.yml`](defaults/main.yml)
|
|
(`switch_harden_commands`): disable plaintext HTTP, ensure HTTPS, disable
|
|
Telnet, SSH v2 only, ensure SNMP off, enable RSTP, enable loopback-detection,
|
|
then `copy running-config startup-config`.
|
|
|
|
The command list was **verified against SG2428LP firmware** via on-device `?`
|
|
help. JetStream tokens vary by model/firmware — reconfirm with `?` if you point
|
|
this at a different model.
|
|
|
|
Management IP access-control and CLI idle-timeout are intentionally **opt-in**
|
|
(commented in `defaults/main.yml`) because a wrong ACL can lock you out. Apply
|
|
those last, from a host inside the permitted range.
|
|
|
|
## Static-IP cutover (DHCP → static)
|
|
|
|
`switch_network_commands` (toggled by `switch_apply_network`) sets the static
|
|
mgmt IP. In steady state — when the host's `ansible_host` already equals the
|
|
static IP — re-asserting it is a no-op. The **one-time** DHCP→static cutover is
|
|
different: the `ip address` line drops the session, so it cannot save afterward
|
|
over the same connection. Do the cutover deliberately (connect on the DHCP
|
|
address, set the static IP, reconnect on the new IP, then save), and afterward
|
|
set the host's `ansible_host` to the static IP. Same-subnet control hosts reach
|
|
the new IP directly, so gateway correctness isn't required to regain access.
|
|
|
|
## Gotchas (these cost real debugging time)
|
|
|
|
- **SSH publickey auth breaks login.** The JetStream SSH server drops the
|
|
connection when the client offers `publickey` first (the default). The engine
|
|
forces `-o PubkeyAuthentication=no -o PreferredAuthentications=password`.
|
|
Symptom without it: "connection closed after KEX, before password prompt",
|
|
intermittently (depends on which keys your agent offers).
|
|
- **Unsaved config reverts on reboot.** Web-UI changes (incl. the first-login
|
|
password) live in running-config until you explicitly save. A reboot with no
|
|
save silently reverts to factory. The engine always ends with
|
|
`copy running-config startup-config` (look for "Saving user config OK!").
|
|
- **One management session at a time.** A logged-in web-UI session occupies the
|
|
single mgmt slot and blocks SSH login. Log out of the web UI before running.
|
|
- **`no ip http server` doesn't close port 80.** It disables HTTP *management*;
|
|
port 80 stays open serving only a JS redirect to HTTPS. That's expected/secure.
|