Fix vault --ask-vault-pass with no tty (#31493)
* Fix vault --ask-vault-pass with no tty 2.4.0 added a check for isatty() that would skip setting up interactive vault password prompts if not running on a tty. But... getpass.getpass() will fallback to reading from stdin if it gets that far without a tty. Since 2.4.0 skipped the interactive prompts / getpass.getpass() in that case, it would never get a chance to fall back to stdin. So if 'echo $VAULT_PASSWORD| ansible-playbook --ask-vault-pass site.yml' was ran without a tty (ie, from a jenkins job or via the vagrant ansible provisioner) the 2.4 behavior was different than 2.3. 2.4 would never read the password from stdin, resulting in a vault password error like: ERROR! Attempting to decrypt but no vault secrets found Fix is just to always call the interactive password prompts based on getpass.getpass() on --ask-vault-pass or --vault-id @prompt and let getpass sort it out. * up test_prompt_no_tty to expect prompt with no tty We do call the PromptSecret class if there is no tty, but we are back to expecting it to read from stdin in that case. * Fix logic for when to auto-prompt vault pass If --ask-vault-pass is used, then pretty much always prompt. If it is not used, then prompt if there are no other vault ids provided and 'auto_prompt==True'. Fixes vagrant bug https://github.com/hashicorp/vagrant/issues/9033 Fixes #30993
This commit is contained in:
parent
f93b98661a
commit
86dc3c09ac
4 changed files with 97 additions and 14 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in a new issue