From 95dd63273f6e6b97bd0f7208e08753315650729e Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Tue, 14 Jan 2025 18:22:06 +0800 Subject: [PATCH] Support alternate password for PTF container (#16457) What is the motivation for this PR? The PTF container is always using default password. If the PTF container is on same bridge with the host server's management IP, then it is easily accessible from other host servers. This is not secure enough. We need to support alternate password for the PTF container and password rotation. How did you do it? This change improved the ansible related code to support accessing the PTF containers using the multi_ssh_pass ansible plugin. Then we can specify alternate passwords for the PTF container. When alternate passwords are specified, the default password of PTF container is updated after PTF creation. How did you verify/test it? Tested remove-topo/add-topo/restart-ptf on KVM and physical testbed. --- ansible/group_vars/ptf/secrets.yml | 7 ++ ansible/group_vars/ptf_host/secrets.yml | 4 - ansible/roles/vm_set/tasks/add_topo.yml | 6 ++ .../roles/vm_set/tasks/announce_routes.yml | 14 +--- ansible/roles/vm_set/tasks/ptf_change_mac.yml | 14 +--- .../roles/vm_set/tasks/ptf_portchannel.yml | 14 +--- ansible/roles/vm_set/tasks/renumber_topo.yml | 3 + ansible/roles/vm_set/tasks/start_ptf_tgen.yml | 84 ++++++++----------- .../vm_set/tasks/update_ptf_password.yml | 63 ++++++++++++++ ansible/veos_vtb | 3 - tests/scp/test_scp_copy.py | 30 ++++++- 11 files changed, 152 insertions(+), 90 deletions(-) create mode 100644 ansible/group_vars/ptf/secrets.yml delete mode 100644 ansible/group_vars/ptf_host/secrets.yml create mode 100644 ansible/roles/vm_set/tasks/update_ptf_password.yml diff --git a/ansible/group_vars/ptf/secrets.yml b/ansible/group_vars/ptf/secrets.yml new file mode 100644 index 0000000000..6b34c12f6a --- /dev/null +++ b/ansible/group_vars/ptf/secrets.yml @@ -0,0 +1,7 @@ +ansible_connection: multi_passwd_ssh + +ansible_user: root +ansible_ssh_pass: root +# ansible_altpasswords: +# - fakepassword1 +# - fakepassword2 diff --git a/ansible/group_vars/ptf_host/secrets.yml b/ansible/group_vars/ptf_host/secrets.yml deleted file mode 100644 index ba70b8b113..0000000000 --- a/ansible/group_vars/ptf_host/secrets.yml +++ /dev/null @@ -1,4 +0,0 @@ ---- -# Please update the actual ptf username and password based on your lab config -ptf_host_user: root -ptf_host_pass: root diff --git a/ansible/roles/vm_set/tasks/add_topo.yml b/ansible/roles/vm_set/tasks/add_topo.yml index 96c97a9796..99040fa4c2 100644 --- a/ansible/roles/vm_set/tasks/add_topo.yml +++ b/ansible/roles/vm_set/tasks/add_topo.yml @@ -79,6 +79,9 @@ memory_swap: 8G become: yes + - name: Update ptf password + include_tasks: update_ptf_password.yml + - name: Bind ptf_ip to keysight_api_server vm_topology: cmd: "bind_keysight_api_server_ip" @@ -170,6 +173,9 @@ memory_swap: 32G become: yes + - name: Update ptf password + include_tasks: update_ptf_password.yml + - name: Enable ipv6 for docker container ptf_{{ vm_set_name }} command: docker exec -i ptf_{{ vm_set_name }} sysctl -w net.ipv6.conf.all.disable_ipv6=0 become: yes diff --git a/ansible/roles/vm_set/tasks/announce_routes.yml b/ansible/roles/vm_set/tasks/announce_routes.yml index fe309231cb..ffdb682515 100644 --- a/ansible/roles/vm_set/tasks/announce_routes.yml +++ b/ansible/roles/vm_set/tasks/announce_routes.yml @@ -1,22 +1,14 @@ --- -- name: Include variables for PTF containers - include_vars: - dir: "{{ playbook_dir }}/group_vars/ptf_host/" - - name: Set ptf host set_fact: - ptf_host: "ptf_{{ vm_set_name }}" + ptf_host: "{{ ptf_ip.split('/')[0] }}" ptf_host_ip: "{{ ptf_ip.split('/')[0] }}" - name: Add ptf host add_host: - hostname: "{{ ptf_host }}" - ansible_user: "{{ ptf_host_user }}" - ansible_ssh_host: "{{ ptf_host_ip }}" - ansible_ssh_pass: "{{ ptf_host_pass }}" - ansible_python_interpreter: "/usr/bin/python" + name: "{{ ptf_host }}" groups: - - ptf_host + - ptf - name: Set default exabgp_action set_fact: diff --git a/ansible/roles/vm_set/tasks/ptf_change_mac.yml b/ansible/roles/vm_set/tasks/ptf_change_mac.yml index 2ecd6951dd..4e169b1c39 100644 --- a/ansible/roles/vm_set/tasks/ptf_change_mac.yml +++ b/ansible/roles/vm_set/tasks/ptf_change_mac.yml @@ -1,22 +1,14 @@ --- -- name: Include variables for PTF containers - include_vars: - dir: "{{ playbook_dir }}/group_vars/ptf_host/" - - name: Set ptf host set_fact: - ptf_host: "ptf_{{ vm_set_name }}" + ptf_host: "{{ ptf_ip.split('/')[0] }}" ptf_host_ip: "{{ ptf_ip.split('/')[0] }}" - name: Add ptf host add_host: - hostname: "{{ ptf_host }}" - ansible_user: "{{ ptf_host_user }}" - ansible_ssh_host: "{{ ptf_host_ip }}" - ansible_ssh_pass: "{{ ptf_host_pass }}" - ansible_python_interpreter: "/usr/bin/python" + name: "{{ ptf_host }}" groups: - - ptf_host + - ptf - name: wait until ptf is reachable wait_for: diff --git a/ansible/roles/vm_set/tasks/ptf_portchannel.yml b/ansible/roles/vm_set/tasks/ptf_portchannel.yml index 413dbf1566..dc72dc8fd7 100644 --- a/ansible/roles/vm_set/tasks/ptf_portchannel.yml +++ b/ansible/roles/vm_set/tasks/ptf_portchannel.yml @@ -1,22 +1,14 @@ --- -- name: Include variables for PTF containers - include_vars: - dir: "{{ playbook_dir }}/group_vars/ptf_host/" - - name: Set ptf host set_fact: - ptf_host: "ptf_{{ vm_set_name }}" + ptf_host: "{{ ptf_ip.split('/')[0] }}" ptf_host_ip: "{{ ptf_ip.split('/')[0] }}" - name: Add ptf host add_host: - hostname: "{{ ptf_host }}" - ansible_user: "{{ ptf_host_user }}" - ansible_ssh_host: "{{ ptf_host_ip }}" - ansible_ssh_pass: "{{ ptf_host_pass }}" - ansible_python_interpreter: "/usr/bin/python" + name: "{{ ptf_host }}" groups: - - ptf_host + - ptf - name: find downlink portchannel configuration set_fact: diff --git a/ansible/roles/vm_set/tasks/renumber_topo.yml b/ansible/roles/vm_set/tasks/renumber_topo.yml index eefa8450eb..ed24ade8d3 100644 --- a/ansible/roles/vm_set/tasks/renumber_topo.yml +++ b/ansible/roles/vm_set/tasks/renumber_topo.yml @@ -122,6 +122,9 @@ memory_swap: 32G become: yes + - name: Update ptf password + include_tasks: update_ptf_password.yml + - name: Enable ipv6 for docker container ptf_{{ vm_set_name }} command: docker exec -i ptf_{{ vm_set_name }} sysctl -w net.ipv6.conf.all.disable_ipv6=0 become: yes diff --git a/ansible/roles/vm_set/tasks/start_ptf_tgen.yml b/ansible/roles/vm_set/tasks/start_ptf_tgen.yml index 26aecd1f23..981065dfa7 100644 --- a/ansible/roles/vm_set/tasks/start_ptf_tgen.yml +++ b/ansible/roles/vm_set/tasks/start_ptf_tgen.yml @@ -1,55 +1,43 @@ --- -- name: Include variables for PTF containers - include_vars: - dir: "{{ playbook_dir }}/group_vars/ptf_host/" +- name: Set ptf host + set_fact: + ptf_host: "{{ ptf_ip.split('/')[0] }}" + +- name: Add ptf host + add_host: + name: "{{ ptf_host }}" + groups: + - ptf + +- name: Check if ptf_tgen exists + supervisorctl: + name: ptf_tgen + state: present + become: True + delegate_to: "{{ ptf_host }}" + ignore_errors: True + register: ptf_tgen_state - block: - - name: Set ptf host - set_fact: - ptf_host: "ptf_{{ vm_set_name }}" - ptf_host_ip: "{{ ptf_ip.split('/')[0] }}" + - name: Copy scapy scripts to ptf host + copy: + src: "{{ item }}" + dest: "/ptf_tgen/" + with_fileglob: + - "{{ playbook_dir }}/../spytest/spytest/tgen/scapy/*" + - "{{ playbook_dir }}/../spytest/spytest/dicts.py" - - name: Add ptf host - add_host: - hostname: "{{ ptf_host }}" - ansible_user: "{{ ptf_host_user }}" - ansible_ssh_host: "{{ ptf_host_ip }}" - ansible_ssh_pass: "{{ ptf_host_pass }}" - groups: - - ptf_host + - name: Create ptf_tgen service + copy: + src: "/ptf_tgen/service.sh" + dest: "/ptf_tgen/ptf_tgen.sh" + mode: "0755" + remote_src: yes - - name: Check if ptf_tgen exists + - name: Start ptf_tgen supervisorctl: name: ptf_tgen - state: present - become: True - delegate_to: "{{ ptf_host }}" - ignore_errors: True - register: ptf_tgen_state - - - block: - - name: Copy scapy scripts to ptf host - copy: - src: "{{ item }}" - dest: "/ptf_tgen/" - with_fileglob: - - "{{ playbook_dir }}/../spytest/spytest/tgen/scapy/*" - - "{{ playbook_dir }}/../spytest/spytest/dicts.py" - - - name: Create ptf_tgen service - copy: - src: "/ptf_tgen/service.sh" - dest: "/ptf_tgen/ptf_tgen.sh" - mode: "0755" - remote_src: yes - - - name: Start ptf_tgen - supervisorctl: - name: ptf_tgen - state: restarted - become: True - delegate_to: "{{ ptf_host }}" - when: ptf_tgen_state is not failed - when: - - ptf_host_user is defined - - ptf_host_pass is defined + state: restarted + become: True + delegate_to: "{{ ptf_host }}" + when: ptf_tgen_state is not failed diff --git a/ansible/roles/vm_set/tasks/update_ptf_password.yml b/ansible/roles/vm_set/tasks/update_ptf_password.yml new file mode 100644 index 0000000000..5bfe4ad392 --- /dev/null +++ b/ansible/roles/vm_set/tasks/update_ptf_password.yml @@ -0,0 +1,63 @@ +- include_vars: + file: "{{ playbook_dir }}/group_vars/ptf/secrets.yml" + name: raw_ptf_secrets + no_log: true + +- name: Render ptf secrets + set_fact: + ptf_secrets: >- + {{ + dict( + raw_ptf_secrets.keys() | zip(raw_ptf_secrets.values() + ) + ) + }} + no_log: true + +- block: + + - name: Init default ptf_username + set_fact: + ptf_username: "root" + when: ptf_username is not defined + + - name: Init default ptf_password + set_fact: + ptf_password: "root" + when: ptf_password is not defined + no_log: true + + - name: Override default ptf_username + set_fact: + ptf_username: "{{ ptf_secrets['ansible_user'] }}" + when: "'ansible_user' in ptf_secrets" + + - name: Override default ptf_password + set_fact: + ptf_password: "{{ ptf_secrets['ansible_ssh_pass'] }}" + when: "'ansible_ssh_pass' in ptf_secrets" + + - name: Get ptf_alt_passwords from ptf_secrets + set_fact: + ptf_alt_passwords: "{{ ptf_secrets['ansible_altpasswords'] }}" + no_log: true + + - name: If ptf_alt_passwords is a list, set ptf_password to its first value + set_fact: + ptf_password: "{{ ptf_alt_passwords[0] }}" + when: ptf_alt_passwords | type_debug == "list" and ptf_alt_passwords | length > 0 + no_log: true + + - name: If ptf_alt_passwords is not a list, log a debug message + debug: + msg: >- + The 'ansible_altpasswords' field in group_vars/ptf/secrets.yml is not a list. + Falling back to use the 'ansible_ssh_pass' field." + when: ptf_alt_passwords | type_debug != "list" + + - name: Update ptf username and password + command: docker exec -t ptf_{{ vm_set_name }} sh -c 'echo "{{ ptf_username }}:{{ ptf_password }}" | chpasswd' + become: yes + no_log: true + + when: ptf_secrets is defined and 'ansible_altpasswords' in ptf_secrets diff --git a/ansible/veos_vtb b/ansible/veos_vtb index 99a0b73c03..e455ec31da 100644 --- a/ansible/veos_vtb +++ b/ansible/veos_vtb @@ -96,9 +96,6 @@ all: ptf-08: ansible_host: 10.250.0.119 ansible_hostv6: fec0::ffff:afb:5 - vars: - ansible_user: root - ansible_password: root sonic: vars: mgmt_subnet_mask_length: 24 diff --git a/tests/scp/test_scp_copy.py b/tests/scp/test_scp_copy.py index 17cc8ab719..a4a676e287 100644 --- a/tests/scp/test_scp_copy.py +++ b/tests/scp/test_scp_copy.py @@ -1,5 +1,9 @@ +import logging import pytest from tests.common.helpers.assertions import pytest_assert +from tests.common.utilities import get_dut_current_passwd + +logger = logging.getLogger(__name__) pytestmark = [ pytest.mark.disable_loganalyzer, @@ -33,11 +37,33 @@ def setup_teardown(duthosts, enum_rand_one_per_hwsku_hostname, ptfhost, creds): ptfhost.file(path=file, state="absent") +def _gather_passwords(ptfhost, duthost): + + ptfhostvars = duthost.host.options['variable_manager']._hostvars[ptfhost.hostname] + passwords = [] + alt_passwords = ptfhostvars.get("ansible_altpasswords", []) + if alt_passwords: + passwords.extend(alt_passwords) + + for key in ["ansible_password", "ptf_host_pass", "ansible_altpassword"]: + if key in ptfhostvars: + value = ptfhostvars.get(key, None) + if value: + passwords.append(value) + + return passwords + + def test_scp_copy(duthosts, enum_rand_one_per_hwsku_hostname, ptfhost, setup_teardown, creds): duthost = duthosts[enum_rand_one_per_hwsku_hostname] ptf_ip = ptfhost.mgmt_ip + # After PTF default password rotation is supported, need to figure out which password is currently working + _passwords = _gather_passwords(ptfhost, duthost) + logger.warn("_password: " + str(_passwords)) + current_password = get_dut_current_passwd(ptf_ip, "", creds["ptf_host_user"], _passwords) + # Generate the file from /dev/urandom ptfhost.command(("dd if=/dev/urandom of=./{} count=1 bs={} iflag=fullblock" .format(TEST_FILE_NAME, BLOCK_SIZE))) @@ -59,7 +85,7 @@ def test_scp_copy(duthosts, enum_rand_one_per_hwsku_hostname, ptfhost, setup_tea duthost.command("{} perform_scp.py in {} /root/{} /home/{} {} {}" .format(python_version, ptf_ip, TEST_FILE_NAME, - creds['sonicadmin_user'], creds["ptf_host_user"], creds["ptf_host_pass"])) + creds['sonicadmin_user'], creds["ptf_host_user"], current_password)) # Validate file was received res = duthost.command( @@ -80,7 +106,7 @@ def test_scp_copy(duthosts, enum_rand_one_per_hwsku_hostname, ptfhost, setup_tea # Use scp to copy the file into the PTF duthost.command("{} perform_scp.py out {} /home/{}/{} /root/{} {} {}" .format(python_version, ptf_ip, creds['sonicadmin_user'], TEST_FILE_NAME, TEST_FILE_2_NAME, - creds["ptf_host_user"], creds["ptf_host_pass"])) + creds["ptf_host_user"], current_password)) # Validate that the file copied is now present in the PTF res = ptfhost.command(