From 411ba0d8178a6b4939abd146036f0e89f32e90e5 Mon Sep 17 00:00:00 2001 From: Sonic Build Admin Date: Fri, 31 Jan 2025 04:58:07 +0000 Subject: [PATCH] Support alternate password for PTF container ### Description of PR Summary: Fixes # (issue) ### Type of change - [ ] Bug fix - [ ] Testbed and Framework(new/improvement) - [ ] New Test case - [ ] Skipped for non-supported platforms - [ ] Add ownership [here](https://msazure.visualstudio.com/AzureWiki/_wiki/wikis/AzureWiki.wiki/744287/TSG-for-ownership-modification)(Microsft required only) - [ ] Test case improvement ### Back port request - [ ] 202012 - [ ] 202205 - [ ] 202305 - [ ] 202311 - [ ] 202405 - [ ] 202411 ### Approach #### 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. #### Any platform specific information? #### Supported testbed topology if it's a new test case? ### Documentation --- 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 ++++++++----------- .../tasks/start_tacacs_daily_daemon.yml | 9 +- .../vm_set/tasks/update_ptf_password.yml | 63 ++++++++++++++ ansible/veos_vtb | 3 - tests/scp/test_scp_copy.py | 30 ++++++- 12 files changed, 160 insertions(+), 91 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 fc886241fe..95a52d7141 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 2c8d22e8f9..3dd69966e0 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/start_tacacs_daily_daemon.yml b/ansible/roles/vm_set/tasks/start_tacacs_daily_daemon.yml index e48ce0113f..08a574a975 100644 --- a/ansible/roles/vm_set/tasks/start_tacacs_daily_daemon.yml +++ b/ansible/roles/vm_set/tasks/start_tacacs_daily_daemon.yml @@ -43,7 +43,14 @@ - 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: + name: "{{ ptf_host }}" + groups: + - ptf - debug: msg="ptf_host {{ ptf_host }}" 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 c3d37c1dcd..ca09865cfc 100644 --- a/ansible/veos_vtb +++ b/ansible/veos_vtb @@ -100,9 +100,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 4ebcbaf7bd..f3b0000c4b 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(