--- # tplink_switch_harden — drive the JetStream CLI via a rendered expect engine. # Runs on the control node (play uses connection: local); the engine SSHes to # the switch. The admin password is passed only through the SWITCH_PW env var. - name: Assert admin password is available from vault ansible.builtin.assert: that: - switch_admin_password is defined - switch_admin_password | length > 0 fail_msg: >- vault_sg2428lp_admin_password is empty/undefined. Populate it with etc/set-switch-1password.sh (op -> ansible-vault bridge). - name: Create temp dir for the rendered engine ansible.builtin.tempfile: state: directory suffix: switch-harden register: _sw_tmp changed_when: false - name: Render hardening expect engine ansible.builtin.template: src: harden.exp.j2 dest: "{{ _sw_tmp.path }}/harden.exp" mode: "0700" changed_when: false - name: Run switch hardening engine ({{ 'APPLY' if switch_apply | bool else 'DRY/show-only' }}) ansible.builtin.command: cmd: "expect -f {{ _sw_tmp.path }}/harden.exp" environment: SWITCH_PW: "{{ switch_admin_password }}" register: _sw_run no_log: true # protects SWITCH_PW in the environment changed_when: "'>>>===APPLY-END===<<<' in _sw_run.stdout" failed_when: false # evaluate explicitly below so output prints - name: Show switch engine output ansible.builtin.debug: var: _sw_run.stdout_lines - name: Remove temp dir ansible.builtin.file: path: "{{ _sw_tmp.path }}" state: absent changed_when: false - name: Assert hardening engine succeeded ansible.builtin.assert: that: - _sw_run.rc == 0 - "'AUTH-FAILED' not in _sw_run.stdout" - "'FORCED-PASSWORD-CHANGE' not in _sw_run.stdout" - "'LOGIN-TIMEOUT' not in _sw_run.stdout" fail_msg: "Switch engine failed — review the output above (rc={{ _sw_run.rc }})." success_msg: >- {{ 'Hardening applied and saved to startup-config.' if switch_apply | bool else 'Access verified (dry run). Re-run with -e switch_apply=true to apply.' }}