Skip to content

Commit

Permalink
Support alternate password for PTF container (#16457)
Browse files Browse the repository at this point in the history
Manually cherry-pick #16457 to 202311 branch due to conflicts.

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.

Signed-off-by: Xin Wang <xiwang5@microsoft.com>
  • Loading branch information
wangxin committed Jan 14, 2025
1 parent 251f02f commit 87a3ddd
Show file tree
Hide file tree
Showing 11 changed files with 157 additions and 93 deletions.
7 changes: 7 additions & 0 deletions ansible/group_vars/ptf/secrets.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
ansible_connection: multi_passwd_ssh

ansible_user: root
ansible_ssh_pass: root
# ansible_altpasswords:
# - fakepassword1
# - fakepassword2
4 changes: 0 additions & 4 deletions ansible/group_vars/ptf_host/secrets.yml

This file was deleted.

6 changes: 6 additions & 0 deletions ansible/roles/vm_set/tasks/add_topo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
Expand Down
14 changes: 3 additions & 11 deletions ansible/roles/vm_set/tasks/announce_routes.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
14 changes: 3 additions & 11 deletions ansible/roles/vm_set/tasks/ptf_change_mac.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
14 changes: 3 additions & 11 deletions ansible/roles/vm_set/tasks/ptf_portchannel.yml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
3 changes: 3 additions & 0 deletions ansible/roles/vm_set/tasks/renumber_topo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,9 @@
memory_swap: 8G
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
Expand Down
84 changes: 36 additions & 48 deletions ansible/roles/vm_set/tasks/start_ptf_tgen.yml
Original file line number Diff line number Diff line change
@@ -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
63 changes: 63 additions & 0 deletions ansible/roles/vm_set/tasks/update_ptf_password.yml
Original file line number Diff line number Diff line change
@@ -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
3 changes: 0 additions & 3 deletions ansible/veos_vtb
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,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
Expand Down
38 changes: 33 additions & 5 deletions tests/scp/test_scp_copy.py
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -32,11 +36,33 @@ def setup_teardown(duthosts, enum_rand_one_per_hwsku_hostname, ptfhost):
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)))
Expand All @@ -55,8 +81,10 @@ def test_scp_copy(duthosts, enum_rand_one_per_hwsku_hostname, ptfhost, setup_tea
"python3 -c 'import pexpect'", module_ignore_errors=True)["rc"]
if p3_pexp_exists != 0:
python_version = "python"
duthost.command("{} perform_scp.py in {} /root/{} /home/admin {} {}"
.format(python_version, ptf_ip, TEST_FILE_NAME, creds["ptf_host_user"], creds["ptf_host_pass"]))

duthost.command("{} perform_scp.py in {} /root/{} /home/{} {} {}"
.format(python_version, ptf_ip, TEST_FILE_NAME,
creds['sonicadmin_user'], creds["ptf_host_user"], current_password))

# Validate file was received
res = duthost.command(
Expand All @@ -75,9 +103,9 @@ def test_scp_copy(duthosts, enum_rand_one_per_hwsku_hostname, ptfhost, setup_tea
.format(TEST_FILE_NAME, orig_checksum, TEST_FILE_NAME, new_checksum))

# Use scp to copy the file into the PTF
duthost.command("{} perform_scp.py out {} /home/admin/{} /root/{} {} {}"
.format(python_version, ptf_ip, TEST_FILE_NAME, TEST_FILE_2_NAME,
creds["ptf_host_user"], creds["ptf_host_pass"]))
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"], current_password))

# Validate that the file copied is now present in the PTF
res = ptfhost.command(
Expand Down

0 comments on commit 87a3ddd

Please sign in to comment.