From 916e46621b879b09c389d97bb6d3415ca1edd17f Mon Sep 17 00:00:00 2001 From: David Wilson Date: Sun, 12 Aug 2018 11:53:06 +0100 Subject: [PATCH] issue #340: add connection delegation tests. --- ansible_mitogen/connection.py | 4 +- tests/ansible/common-hosts | 36 ++ tests/ansible/integration/all.yml | 3 +- tests/ansible/integration/delegation/all.yml | 2 + .../delegation/stack_construction.yml | 311 ++++++++++++++++++ tests/ansible/lib/action/mitogen_get_stack.py | 22 ++ 6 files changed, 376 insertions(+), 2 deletions(-) create mode 100644 tests/ansible/integration/delegation/all.yml create mode 100644 tests/ansible/integration/delegation/stack_construction.yml create mode 100644 tests/ansible/lib/action/mitogen_get_stack.py diff --git a/ansible_mitogen/connection.py b/ansible_mitogen/connection.py index f3a6dac2..ccfc12b4 100644 --- a/ansible_mitogen/connection.py +++ b/ansible_mitogen/connection.py @@ -566,7 +566,9 @@ class Connection(ansible.plugins.connection.ConnectionBase): def _build_stack(self): """ Construct a list of dictionaries representing the connection - configuration between the controller and the target. + configuration between the controller and the target. This is + additionally used by the integration tests "mitogen_get_stack" action + to fetch the would-be connection configuration. """ if hasattr(self._play_context, 'delegate_to'): target_config = config_from_hostvars( diff --git a/tests/ansible/common-hosts b/tests/ansible/common-hosts index 0aead1be..6dafaf47 100644 --- a/tests/ansible/common-hosts +++ b/tests/ansible/common-hosts @@ -1,3 +1,5 @@ +# vim: syntax=dosini + [connection-delegation-test] cd-bastion cd-rack11 mitogen_via=ssh-user@cd-bastion @@ -13,3 +15,37 @@ cdc-rack11a-docker mitogen_via=docker-admin@cdc-rack11a ansible_connection=docke [conn-delegation] cd-user1 ansible_user=mitogen__user1 ansible_connection=mitogen_sudo mitogen_via=target + + +# Connection delegation scenarios. It's impossible to connection to them, but +# you can inspect the would-be config via "mitogen_get_stack" action. +[cd-no-connect] +# Normal inventory host, no aliasing. +cd-normal ansible_connection=mitogen_doas ansible_user=normal-user +# Inventory host that is really a different host. +cd-alias ansible_connection=ssh ansible_user=alias-user ansible_host=alias-host + +# Via one normal host. +cd-normal-normal mitogen_via=cd-normal +# Via one aliased host. +cd-normal-alias mitogen_via=cd-alias + +# newuser@host via host with explicit username. +cd-newuser-normal-normal mitogen_via=cd-normal ansible_user=newuser-normal-normal-user + +# doas:newuser via host. +cd-newuser-doas-normal mitogen_via=cd-normal ansible_connection=mitogen_doas ansible_user=newuser-doas-normal-user + + +# Connection Delegation issue #340 reproduction. +# Path to jails is SSH to H -> mitogen_sudo to root -> jail to J + +[issue340] +# 'target' plays the role of the normal host machine H. +# 'mitogen__sudo1' plays the role of root@H via mitogen_sudo. +# 'mitogen__user1' plays the role of root@J via mitogen__user1. +# 'mitogen__user2' plays the role of E, the delgate_to target for certs. + +i340-root ansible_user=mitogen__sudo1 ansible_connection=mitogen_sudo mitogen_via=target +i340-jail ansible_user=mitogen__user1 ansible_connection=mitogen_sudo mitogen_via=i340-root +i340-certs ansible_user=mitogen__user2 ansible_connection=mitogen_sudo mitogen_via=target diff --git a/tests/ansible/integration/all.yml b/tests/ansible/integration/all.yml index bf534aed..ffea7a46 100644 --- a/tests/ansible/integration/all.yml +++ b/tests/ansible/integration/all.yml @@ -8,6 +8,8 @@ - import_playbook: become/all.yml - import_playbook: connection_loader/all.yml - import_playbook: context_service/all.yml +- import_playbook: delegation/all.yml +- import_playbook: glibc_caches/all.yml - import_playbook: local/all.yml - import_playbook: module_utils/all.yml - import_playbook: playbook_semantics/all.yml @@ -15,4 +17,3 @@ - import_playbook: runner/all.yml - import_playbook: ssh/all.yml - import_playbook: strategy/all.yml -- import_playbook: glibc_caches/all.yml diff --git a/tests/ansible/integration/delegation/all.yml b/tests/ansible/integration/delegation/all.yml new file mode 100644 index 00000000..9646d09c --- /dev/null +++ b/tests/ansible/integration/delegation/all.yml @@ -0,0 +1,2 @@ + +- import_playbook: stack_construction.yml diff --git a/tests/ansible/integration/delegation/stack_construction.yml b/tests/ansible/integration/delegation/stack_construction.yml new file mode 100644 index 00000000..c6851a4c --- /dev/null +++ b/tests/ansible/integration/delegation/stack_construction.yml @@ -0,0 +1,311 @@ +# https://github.com/dw/mitogen/issues/251 + +# ansible_mitogen.connection internally reinterprets Ansible state into a +# 'connection stack' -- this is just a list of dictionaries specifying a +# sequence of proxied Router connection methods and their kwargs used to +# establish the connection. That list is passed to ContextService, which loops +# over the stack specifying via=(None or previous entry) for each connection +# method. + +# mitogen_get_stack is a magic action that returns the stack, so we can test +# all kinds of scenarios without actually needing a real environmnt. + +# Updating this file? Install 'pprintpp' and hack lib/callbacks/nice_stdout.py +# to use it instead of the built-in function, then simply s/'/'/ to get the +# cutpasteable formatted dicts below. WARNING: remove the trailing comma from +# the result list element, it seems to cause assert to silently succeed! + + +- name: integration/delegation/stack_construction.yml + hosts: cd-normal + any_errors_fatal: true + tasks: + - mitogen_get_stack: + register: out + - assert: + that: | + out.result == [ + { + "kwargs": { + "connect_timeout": 10, + "doas_path": None, + "password": None, + "python_path": ["/usr/bin/python"], + "username": "normal-user", + }, + "method": "doas", + } + ] + + +- hosts: cd-normal + tasks: + - mitogen_get_stack: + delegate_to: cd-alias + register: out + - assert: + that: | + out.result == [ + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'connect_timeout': 10, + 'hostname': 'alias-host', + 'identity_file': None, + 'password': None, + 'port': None, + 'python_path': None, + 'ssh_args': [ + '-o', + 'ForwardAgent=yes', + '-o', + 'ControlMaster=auto', + '-o', + 'ControlPersist=60s', + ], + 'ssh_debug_level': None, + 'ssh_path': 'ssh', + 'username': 'alias-user', + }, + 'method': 'ssh', + }, + ] + + +- hosts: cd-alias + tasks: + - mitogen_get_stack: + register: out + - assert: + that: | + out.result == [ + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'connect_timeout': 10, + 'hostname': 'alias-host', + 'identity_file': None, + 'password': None, + 'port': None, + 'python_path': ['/usr/bin/python'], + 'ssh_args': [ + '-o', + 'ForwardAgent=yes', + '-o', + 'ControlMaster=auto', + '-o', + 'ControlPersist=60s', + ], + 'ssh_debug_level': None, + 'ssh_path': 'ssh', + 'username': 'alias-user', + }, + 'method': 'ssh', + }, + ] + + +- hosts: cd-normal-normal + tasks: + - mitogen_get_stack: + register: out + - assert: + that: | + out.result == [ + { + 'kwargs': { + 'connect_timeout': 10, + 'doas_path': None, + 'password': None, + 'python_path': None, + 'username': 'normal-user', + }, + 'method': 'doas', + }, + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'connect_timeout': 10, + 'hostname': 'cd-normal-normal', + 'identity_file': None, + 'password': None, + 'port': None, + 'python_path': ['/usr/bin/python'], + 'ssh_args': [ + '-o', + 'ForwardAgent=yes', + '-o', + 'ControlMaster=auto', + '-o', + 'ControlPersist=60s', + ], + 'ssh_debug_level': None, + 'ssh_path': 'ssh', + 'username': None, + }, + 'method': 'ssh', + }, + ] + + +- hosts: cd-normal-alias + tasks: + - mitogen_get_stack: + register: out + - assert: + that: | + out.result == [ + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'connect_timeout': 10, + 'hostname': 'alias-host', + 'identity_file': None, + 'password': None, + 'port': None, + 'python_path': None, + 'ssh_args': [ + '-o', + 'ForwardAgent=yes', + '-o', + 'ControlMaster=auto', + '-o', + 'ControlPersist=60s', + ], + 'ssh_debug_level': None, + 'ssh_path': 'ssh', + 'username': 'alias-user', + }, + 'method': 'ssh', + }, + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'connect_timeout': 10, + 'hostname': 'cd-normal-alias', + 'identity_file': None, + 'password': None, + 'port': None, + 'python_path': ['/usr/bin/python'], + 'ssh_args': [ + '-o', + 'ForwardAgent=yes', + '-o', + 'ControlMaster=auto', + '-o', + 'ControlPersist=60s', + ], + 'ssh_debug_level': None, + 'ssh_path': 'ssh', + 'username': None, + }, + 'method': 'ssh', + }, + ] + + +- hosts: cd-newuser-normal-normal + tasks: + - mitogen_get_stack: + register: out + - assert: + that: | + out.result == [ + { + 'kwargs': { + 'connect_timeout': 10, + 'doas_path': None, + 'password': None, + 'python_path': None, + 'username': 'normal-user', + }, + 'method': 'doas', + }, + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'connect_timeout': 10, + 'hostname': 'cd-newuser-normal-normal', + 'identity_file': None, + 'password': None, + 'port': None, + 'python_path': ['/usr/bin/python'], + 'ssh_args': [ + '-o', + 'ForwardAgent=yes', + '-o', + 'ControlMaster=auto', + '-o', + 'ControlPersist=60s', + ], + 'ssh_debug_level': None, + 'ssh_path': 'ssh', + 'username': 'newuser-normal-normal-user', + }, + 'method': 'ssh', + }, + ] + + +- hosts: cd-newuser-normal-normal + tasks: + - mitogen_get_stack: + delegate_to: cd-alias + register: out + - assert: + that: | + out.result == [ + { + 'kwargs': { + 'check_host_keys': 'ignore', + 'connect_timeout': 10, + 'hostname': 'alias-host', + 'identity_file': None, + 'password': None, + 'port': None, + 'python_path': None, + 'ssh_args': [ + '-o', + 'ForwardAgent=yes', + '-o', + 'ControlMaster=auto', + '-o', + 'ControlPersist=60s', + ], + 'ssh_debug_level': None, + 'ssh_path': 'ssh', + 'username': 'alias-user', + }, + 'method': 'ssh', + }, + ] + + +- hosts: cd-newuser-doas-normal + tasks: + - mitogen_get_stack: + register: out + - assert: + that: | + out.result == [ + { + 'kwargs': { + 'connect_timeout': 10, + 'doas_path': None, + 'password': None, + 'python_path': None, + 'username': 'normal-user', + }, + 'method': 'doas', + }, + { + 'kwargs': { + 'connect_timeout': 10, + 'doas_path': None, + 'password': None, + 'python_path': ['/usr/bin/python'], + 'username': 'newuser-doas-normal-user', + }, + 'method': 'doas', + }, + ] diff --git a/tests/ansible/lib/action/mitogen_get_stack.py b/tests/ansible/lib/action/mitogen_get_stack.py new file mode 100644 index 00000000..f1b87f35 --- /dev/null +++ b/tests/ansible/lib/action/mitogen_get_stack.py @@ -0,0 +1,22 @@ +""" +Fetch the connection configuration stack that would be used to connect to a +target, without actually connecting to it. +""" + +import ansible_mitogen.connection + +from ansible.plugins.action import ActionBase + + +class ActionModule(ActionBase): + def run(self, tmp=None, task_vars=None): + if not isinstance(self._connection, + ansible_mitogen.connection.Connection): + return { + 'skipped': True, + } + + return { + 'changed': True, + 'result': self._connection._build_stack(), + }