diff --git a/lib/ansible/cli/__init__.py b/lib/ansible/cli/__init__.py index 18559d69c9..ffaece5bbc 100644 --- a/lib/ansible/cli/__init__.py +++ b/lib/ansible/cli/__init__.py @@ -213,7 +213,9 @@ class CLI(with_metaclass(ABCMeta, object)): # if an action needs an encrypt password (create_new_password=True) and we dont # have other secrets setup, then automatically add a password prompt as well. - if ask_vault_pass or (auto_prompt and not vault_ids): + # prompts cant/shouldnt work without a tty, so dont add prompt secrets + if ask_vault_pass or (not vault_ids and auto_prompt): + id_slug = u'%s@%s' % (C.DEFAULT_VAULT_IDENTITY, u'prompt_ask_vault_pass') vault_ids.append(id_slug) @@ -260,10 +262,6 @@ class CLI(with_metaclass(ABCMeta, object)): vault_id_name, vault_id_value = CLI.split_vault_id(vault_id_slug) if vault_id_value in ['prompt', 'prompt_ask_vault_pass']: - # prompts cant/shouldnt work without a tty, so dont add prompt secrets - if not sys.stdin.isatty(): - continue - # --vault-id some_name@prompt_ask_vault_pass --vault-id other_name@prompt_ask_vault_pass will be a little # confusing since it will use the old format without the vault id in the prompt built_vault_id = vault_id_name or C.DEFAULT_VAULT_IDENTITY diff --git a/test/integration/targets/vault/runme.sh b/test/integration/targets/vault/runme.sh index 0895f4ffe5..5bdf5bb46e 100755 --- a/test/integration/targets/vault/runme.sh +++ b/test/integration/targets/vault/runme.sh @@ -34,6 +34,45 @@ WRONG_RC=$? echo "rc was $WRONG_RC (1 is expected)" [ $WRONG_RC -eq 1 ] +# Use linux setsid to test without a tty. No setsid if osx/bsd though... +if [ -x "$(command -v setsid)" ]; then + # tests related to https://github.com/ansible/ansible/issues/30993 + CMD='ansible-playbook -vvvvv --ask-vault-pass test_vault.yml' + setsid sh -c "echo test-vault-password|${CMD}" < /dev/null > log 2>&1 && : + WRONG_RC=$? + cat log + echo "rc was $WRONG_RC (0 is expected)" + [ $WRONG_RC -eq 0 ] + + setsid sh -c 'tty; ansible-vault --ask-vault-pass -vvvvv view test_vault.yml' < /dev/null > log 2>&1 && : + WRONG_RC=$? + echo "rc was $WRONG_RC (1 is expected)" + [ $WRONG_RC -eq 1 ] + cat log + + setsid sh -c 'tty; echo passbhkjhword|ansible-playbook -vvvvv --ask-vault-pass test_vault.yml' < /dev/null > log 2>&1 && : + WRONG_RC=$? + echo "rc was $WRONG_RC (1 is expected)" + [ $WRONG_RC -eq 1 ] + cat log + + setsid sh -c 'tty; echo test-vault-password |ansible-playbook -vvvvv --ask-vault-pass test_vault.yml' < /dev/null > log 2>&1 + echo $? + cat log + + setsid sh -c 'tty; echo test-vault-password|ansible-playbook -vvvvv --ask-vault-pass test_vault.yml' < /dev/null > log 2>&1 + echo $? + cat log + + setsid sh -c 'tty; echo test-vault-password |ansible-playbook -vvvvv --ask-vault-pass test_vault.yml' < /dev/null > log 2>&1 + echo $? + cat log + + setsid sh -c 'tty; echo test-vault-password|ansible-vault --ask-vault-pass -vvvvv view vaulted.inventory' < /dev/null > log 2>&1 + echo $? + cat log +fi + # old format ansible-vault view "$@" --vault-password-file vault-password-ansible format_1_0_AES.yml diff --git a/test/units/cli/test_cli.py b/test/units/cli/test_cli.py index c881cf78c2..dc94c204d2 100644 --- a/test/units/cli/test_cli.py +++ b/test/units/cli/test_cli.py @@ -46,6 +46,13 @@ class TestCliVersion(unittest.TestCase): class TestCliBuildVaultIds(unittest.TestCase): + def setUp(self): + self.tty_patcher = patch('ansible.cli.sys.stdin.isatty', return_value=True) + self.mock_isatty = self.tty_patcher.start() + + def tearDown(self): + self.tty_patcher.stop() + def test(self): res = cli.CLI.build_vault_ids(['foo@bar']) self.assertEqual(res, ['foo@bar']) @@ -102,6 +109,7 @@ class TestCliBuildVaultIds(unittest.TestCase): ask_vault_pass=True, create_new_password=True, auto_prompt=False) + self.assertEqual(set(res), set(['blip@prompt', 'baz@prompt_ask_vault_pass', 'default@prompt_ask_vault_pass', 'some-password-file', 'qux@another-password-file', @@ -115,8 +123,14 @@ class TestCliSetupVaultSecrets(unittest.TestCase): self.tty_patcher = patch('ansible.cli.sys.stdin.isatty', return_value=True) self.mock_isatty = self.tty_patcher.start() + self.display_v_patcher = patch('ansible.cli.display.verbosity', return_value=6) + self.mock_display_v = self.display_v_patcher.start() + cli.display.verbosity = 5 + def tearDown(self): self.tty_patcher.stop() + self.display_v_patcher.stop() + cli.display.verbosity = 0 def test(self): res = cli.CLI.setup_vault_secrets(None, None, auto_prompt=False) @@ -144,7 +158,8 @@ class TestCliSetupVaultSecrets(unittest.TestCase): res = cli.CLI.setup_vault_secrets(loader=self.fake_loader, vault_ids=['prompt1@prompt'], - ask_vault_pass=True) + ask_vault_pass=True, + auto_prompt=False) self.assertIsInstance(res, list) matches = vault.match_secrets(res, ['prompt1']) @@ -156,15 +171,19 @@ class TestCliSetupVaultSecrets(unittest.TestCase): def test_prompt_no_tty(self, mock_prompt_secret): self.mock_isatty.return_value = False mock_prompt_secret.return_value = MagicMock(bytes=b'prompt1_password', - vault_id='prompt1') + vault_id='prompt1', + name='bytes_should_be_prompt1_password', + spec=vault.PromptVaultSecret) res = cli.CLI.setup_vault_secrets(loader=self.fake_loader, vault_ids=['prompt1@prompt'], - ask_vault_pass=True) + ask_vault_pass=True, + auto_prompt=False) self.assertIsInstance(res, list) - self.assertEqual(len(res), 0) + self.assertEqual(len(res), 2) matches = vault.match_secrets(res, ['prompt1']) - self.assertEquals(len(matches), 0) + self.assertIn('prompt1', [x[0] for x in matches]) + self.assertEquals(len(matches), 1) @patch('ansible.cli.get_file_vault_secret') @patch('ansible.cli.PromptVaultSecret') @@ -192,7 +211,7 @@ class TestCliSetupVaultSecrets(unittest.TestCase): self.assertIsInstance(res, list) len_ids = len(vault_id_names) matches = vault.match_secrets(res, vault_id_names) - self.assertEqual(len(res), len_ids) + self.assertEqual(len(res), len_ids, 'len(res):%s does not match len_ids:%s' % (len(res), len_ids)) self.assertEqual(len(matches), len_ids) for index, prompt in enumerate(vault_id_names): self.assertIn(prompt, [x[0] for x in matches]) @@ -214,6 +233,7 @@ class TestCliSetupVaultSecrets(unittest.TestCase): @patch('ansible.cli.PromptVaultSecret') def test_multiple_prompts_and_ask_vault_pass(self, mock_prompt_secret): + self.mock_isatty.return_value = False mock_prompt_secret.return_value = MagicMock(bytes=b'prompt1_password', vault_id='prompt1') @@ -223,6 +243,8 @@ class TestCliSetupVaultSecrets(unittest.TestCase): 'prompt3@prompt_ask_vault_pass'], ask_vault_pass=True) + # We provide some vault-ids and secrets, so auto_prompt shouldn't get triggered, + # so there is vault_id_names = ['prompt1', 'prompt2', 'prompt3', 'default'] self._assert_ids(vault_id_names, res) @@ -242,14 +264,27 @@ class TestCliSetupVaultSecrets(unittest.TestCase): res = cli.CLI.setup_vault_secrets(loader=self.fake_loader, vault_ids=[], create_new_password=False, - ask_vault_pass=True) + ask_vault_pass=False) self.assertIsInstance(res, list) matches = vault.match_secrets(res, ['default']) # --vault-password-file/DEFAULT_VAULT_PASSWORD_FILE is higher precendce than prompts # if the same vault-id ('default') regardless of cli order since it didn't matter in 2.3 + + self.assertEqual(matches[0][1].bytes, b'file1_password') + self.assertEqual(len(matches), 1) + + res = cli.CLI.setup_vault_secrets(loader=self.fake_loader, + vault_ids=[], + create_new_password=False, + ask_vault_pass=True, + auto_prompt=True) + + self.assertIsInstance(res, list) + matches = vault.match_secrets(res, ['default']) self.assertEqual(matches[0][1].bytes, b'file1_password') self.assertEqual(matches[1][1].bytes, b'prompt1_password') + self.assertEqual(len(matches), 2) @patch('ansible.cli.get_file_vault_secret') @patch('ansible.cli.PromptVaultSecret') diff --git a/test/units/cli/test_vault.py b/test/units/cli/test_vault.py index 4f703121a5..d12a14fad9 100644 --- a/test/units/cli/test_vault.py +++ b/test/units/cli/test_vault.py @@ -32,6 +32,13 @@ from ansible.cli.vault import VaultCLI class TestVaultCli(unittest.TestCase): + def setUp(self): + self.tty_patcher = patch('ansible.cli.sys.stdin.isatty', return_value=False) + self.mock_isatty = self.tty_patcher.start() + + def tearDown(self): + self.tty_patcher.stop() + def test_parse_empty(self): cli = VaultCLI([]) self.assertRaisesRegexp(errors.AnsibleOptionsError, @@ -46,14 +53,18 @@ class TestVaultCli(unittest.TestCase): cli = VaultCLI(args=['ansible-vault', 'view', '/dev/null/foo']) cli.parse() - def test_view_missing_file_no_secret(self): + @patch('ansible.cli.vault.VaultCLI.setup_vault_secrets') + def test_view_missing_file_no_secret(self, mock_setup_vault_secrets): + mock_setup_vault_secrets.return_value = [] cli = VaultCLI(args=['ansible-vault', 'view', '/dev/null/foo']) cli.parse() self.assertRaisesRegexp(errors.AnsibleOptionsError, "A vault password is required to use Ansible's Vault", cli.run) - def test_encrypt_missing_file_no_secret(self): + @patch('ansible.cli.vault.VaultCLI.setup_vault_secrets') + def test_encrypt_missing_file_no_secret(self, mock_setup_vault_secrets): + mock_setup_vault_secrets.return_value = [] cli = VaultCLI(args=['ansible-vault', 'encrypt', '/dev/null/foo']) cli.parse() self.assertRaisesRegexp(errors.AnsibleOptionsError,