Repository: ansible/ansible

Issue:
ansible-vault 2.4 and 2.5 "New Vault Password" prompt when using edit/view/etc commands after running original encrypt operation
##### ISSUE TYPE

 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
ansible-vault
##### ANSIBLE VERSION

```
ansible 2.4.0.0
  config file = /home/lw/code/work/ansible/ansible.cfg
  configured module search path = [u'/home/lw/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/lw/code/work/ansible/venv-27-ans-24/local/lib/python2.7/site-packages/ansible
  executable location = /home/lw/code/work/ansible/venv-27-ans-24/bin/ansible
  python version = 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609]

AND

ansible 2.5.0
  config file = /home/lw/code/work/ansible/ansible.cfg
  configured module search path = [u'/home/lw/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/lw/code/work/ansible/venv-27-ans/local/lib/python2.7/site-packages/ansible
  executable location = /home/lw/code/work/ansible/venv-27-ans/bin/ansible
  python version = 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609]

```

##### CONFIGURATION

Configuration unchanged, I have run the same manual check across three versions, 2.3, 2.4 and 2.5 with the same config file. It is the default configuration.

##### OS / ENVIRONMENT

Linux Ubuntu 16.04 Dell XPS 13
I first installed just the devel version (2.5), after finding this bug, I apt-get installed 2.3 and tested it. After this, to duplicate virtualenv situation, I then installed stable-2.4 via pip in a new venv, then finally installed 2.3 in another separate env.

##### SUMMARY

Files can be encrypted with:
`ansible-vault encrypt foobar.yml`

But when you use:
`ansible-vault edit foorbar.yml`

On 2.4 and 2.5, you receive a prompt to set a new password again, even though the file is already encrypted, you can then drag your version back to 2.3, and it wiill return to "normal/expected behaviour".

EDIT: Also, an additional point (this is actually probably important), is that when you are reprompted to create a "new" password for an already encrypted file, if you input the password originally used for encryption on both of the "set a new password prompts", you will be able to access the file, if you try to set a new password however, that will fail.
 
##### STEPS TO REPRODUCE

Install from devel, locate yourself in the role directory and create a file you wish to ansible-vault, use `ansible-vault encrypt foobar.yml` on the file, then try and run `ansible-vault edit foobar.yml`, this should occur on 2.4 and 2.5, I first ran the encrypt via 2.5, and I can successfully edit it using 2.3, but not any version higher than 2.3

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
I expected to be able to get a predictable result from ansible-vault, I expect to be able to encrypt, decrypt, edit and view like I can in 2.3 

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
What 
<!--- Paste verbatim command output between quotes below -->
```
(venv-27-ans) lw@RobertOppenheimer:files$ echo "this is a test" > foobar.yml
(venv-27-ans) lw@RobertOppenheimer:files$ cat foobar.yml 
this is a test
(venv-27-ans) lw@RobertOppenheimer:files$ ansible-vault encrypt foobar.yml 
New Vault password: _thisisatest_ 
Confirm New Vault password: _thisisatest_
Encryption successful
(venv-27-ans) lw@RobertOppenheimer:files$ cat foobar.yml 
$ANSIBLE_VAULT;1.1;AES256
37623637346261373237633131376234356433353765633366346561363935666634663035313736
3961383762346666626434366635343461613064383066340a643237636566353533623234633231
36626166376463363365396131623030326564666331353962626433653962663731386336646135
3232393131316630660a376437633162613437326633626536656131653461636266346463353366
6330
(venv-27-ans) lw@RobertOppenheimer:files$ ansible-vault edit foobar.yml 
New Vault password: 

```
EDIT: Also, an additional point (this is actually probably important), is that when you are reprompted to create a "new" password for an already encrypted file, if you input the password originally used for encryption on both of the "set a new password prompts", you will be able to access the file, if you try to set a new password however, that will fail.


Discussion:
I can reproduce this with `ansible-vault edit` but not with `ansible-vault view` as described in the title.
Submitted PR #30493 to fix.
@nrwahl2 Full disclosure, I may not have had an issue with view, I incorrectly extrapolated edit not working, to the rest of them not working. My apologies, next time I will be more careful with my titles and writing out the problem.
@JohnVonNeumann No problem. It only took a minute or two to see what was and what was not working as expected. Basically `edit` was just put in the wrong group (the one where it prompts for a new password) instead of an alternate group that prompts for an existing password. I moved it to the other one (where `view` was already) so that should take care of it.
@nrwahl2 Yeah you got onto it quickly! Good work on that, honestly I'm a little bit annoyed at myself for not figuring it out, I would usually look at the code first and see if I could figure it out. Given me a bit of courage to try and submit my own fixes now though, so thank you for that. 
Sure thing :) hardest part with Ansible (and presumably other projects) is getting your bearings and figuring out what's where. I still do a lot of "grep -R and hope." Happy future bugfixing.

-------- Original Message --------
On Sep 18, 2017, 00:45, Louis Willcock wrote:

> [@nrwahl2](https://github.com/nrwahl2) Yeah you got onto it quickly! Good work on that, honestly I'm a little bit annoyed at myself for not figuring it out, I would usually look at the code first and see if I could figure it out. Given me a bit of courage to try and submit my own fixes now though, so thank you for that.
>
> —
> You are receiving this because you were mentioned.
> Reply to this email directly, [view it on GitHub](https://github.com/ansible/ansible/issues/30491#issuecomment-330133278), or [mute the thread](https://github.com/notifications/unsubscribe-auth/AdEzNchFxSNcbvOdJlY7H5AO2u5C_s0eks5sjgN1gaJpZM4PaW79).
Some notes at: https://github.com/ansible/ansible/pull/30493#issuecomment-330220114
so goal for 'ansible-vault edit existing_encrypted_file.yml' is to be prompted for a single password instead of being prompted for a new password?

Since the file will be encrypted with whatever password is entered at the prompt, current code treats that like a new file (to avoid re-encrypting the file with the typo).

I'm leaning towards the 2.4 behavior being correct (prompt for new) and the 2.3 behavior being wrong (no confirm on entered password). 

I guess it depends if not matching the 2.3 behavior exactly is considered a bug.


> EDIT: Also, an additional point (this is actually probably important), is that when you are reprompted to create a "new" password for an already encrypted file, if you input the password originally used for encryption on both of the "set a new password prompts", you will be able to access the file, if you try to set a new password however, that will fail.

Hmm, interesting. That seems busted UXD wise. (I can see why the current code does that, but that doesnt seem like the right thing...)
I think I have a way to use the 2.3 UI and auto_prompt, pr soon

Resolving patch:
diff --git a/lib/ansible/cli/__init__.py b/lib/ansible/cli/__init__.py
--- a/lib/ansible/cli/__init__.py
+++ b/lib/ansible/cli/__init__.py
@@ -196,7 +196,8 @@ def split_vault_id(vault_id):
 
     @staticmethod
     def build_vault_ids(vault_ids, vault_password_files=None,
-                        ask_vault_pass=None, create_new_password=None):
+                        ask_vault_pass=None, create_new_password=None,
+                        auto_prompt=True):
         vault_password_files = vault_password_files or []
         vault_ids = vault_ids or []
 
@@ -211,7 +212,7 @@ def build_vault_ids(vault_ids, vault_password_files=None,
 
         # 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 (create_new_password and not vault_ids):
+        if ask_vault_pass or (auto_prompt and not vault_ids):
             id_slug = u'%s@%s' % (C.DEFAULT_VAULT_IDENTITY, u'prompt_ask_vault_pass')
             vault_ids.append(id_slug)
 
@@ -220,7 +221,8 @@ def build_vault_ids(vault_ids, vault_password_files=None,
     # TODO: remove the now unused args
     @staticmethod
     def setup_vault_secrets(loader, vault_ids, vault_password_files=None,
-                            ask_vault_pass=None, create_new_password=False):
+                            ask_vault_pass=None, create_new_password=False,
+                            auto_prompt=True):
         # list of tuples
         vault_secrets = []
 
@@ -250,7 +252,8 @@ def setup_vault_secrets(loader, vault_ids, vault_password_files=None,
         vault_ids = CLI.build_vault_ids(vault_ids,
                                         vault_password_files,
                                         ask_vault_pass,
-                                        create_new_password)
+                                        create_new_password,
+                                        auto_prompt=auto_prompt)
 
         for vault_id_slug in vault_ids:
             vault_id_name, vault_id_value = CLI.split_vault_id(vault_id_slug)
@@ -777,7 +780,8 @@ def _play_prereqs(options):
         vault_secrets = CLI.setup_vault_secrets(loader,
                                                 vault_ids=vault_ids,
                                                 vault_password_files=options.vault_password_files,
-                                                ask_vault_pass=options.ask_vault_pass)
+                                                ask_vault_pass=options.ask_vault_pass,
+                                                auto_prompt=False)
         loader.set_vault_secrets(vault_secrets)
 
         # create the inventory, and filter it based on the subset specified (if any)
diff --git a/lib/ansible/cli/vault.py b/lib/ansible/cli/vault.py
--- a/lib/ansible/cli/vault.py
+++ b/lib/ansible/cli/vault.py
@@ -165,7 +165,7 @@ def run(self):
 
         # TODO: instead of prompting for these before, we could let VaultEditor
         #       call a callback when it needs it.
-        if self.action in ['decrypt', 'view', 'rekey']:
+        if self.action in ['decrypt', 'view', 'rekey', 'edit']:
             vault_secrets = self.setup_vault_secrets(loader,
                                                      vault_ids=vault_ids,
                                                      vault_password_files=self.options.vault_password_files,
@@ -173,7 +173,7 @@ def run(self):
             if not vault_secrets:
                 raise AnsibleOptionsError("A vault password is required to use Ansible's Vault")
 
-        if self.action in ['encrypt', 'encrypt_string', 'create', 'edit']:
+        if self.action in ['encrypt', 'encrypt_string', 'create']:
             if len(vault_ids) > 1:
                 raise AnsibleOptionsError("Only one --vault-id can be used for encryption")
 


Test patch:
diff --git a/test/units/cli/test_cli.py b/test/units/cli/test_cli.py
--- a/test/units/cli/test_cli.py
+++ b/test/units/cli/test_cli.py
@@ -54,6 +54,32 @@ def test_create_new_password_no_vault_id(self):
         res = cli.CLI.build_vault_ids([], create_new_password=True)
         self.assertEqual(res, ['default@prompt_ask_vault_pass'])
 
+    def test_create_new_password_no_vault_id_no_auto_prompt(self):
+        res = cli.CLI.build_vault_ids([], auto_prompt=False, create_new_password=True)
+        self.assertEqual(res, [])
+
+    def test_no_vault_id_no_auto_prompt(self):
+        # similate 'ansible-playbook site.yml' with out --ask-vault-pass, should not prompt
+        res = cli.CLI.build_vault_ids([], auto_prompt=False)
+        self.assertEqual(res, [])
+
+    def test_no_vault_ids_auto_prompt(self):
+        # create_new_password=False
+        # simulate 'ansible-vault edit encrypted.yml'
+        res = cli.CLI.build_vault_ids([], auto_prompt=True)
+        self.assertEqual(res, ['default@prompt_ask_vault_pass'])
+
+    def test_no_vault_ids_auto_prompt_ask_vault_pass(self):
+        # create_new_password=False
+        # simulate 'ansible-vault edit --ask-vault-pass encrypted.yml'
+        res = cli.CLI.build_vault_ids([], auto_prompt=True, ask_vault_pass=True)
+        self.assertEqual(res, ['default@prompt_ask_vault_pass'])
+
+    def test_create_new_password_auto_prompt(self):
+        # simulate 'ansible-vault encrypt somefile.yml'
+        res = cli.CLI.build_vault_ids([], auto_prompt=True, create_new_password=True)
+        self.assertEqual(res, ['default@prompt_ask_vault_pass'])
+
     def test_create_new_password_no_vault_id_ask_vault_pass(self):
         res = cli.CLI.build_vault_ids([], ask_vault_pass=True,
                                       create_new_password=True)
@@ -74,7 +100,8 @@ def test_everything(self):
                                       vault_password_files=['yet-another-password-file',
                                                             'one-more-password-file'],
                                       ask_vault_pass=True,
-                                      create_new_password=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',
@@ -92,7 +119,7 @@ def tearDown(self):
         self.tty_patcher.stop()
 
     def test(self):
-        res = cli.CLI.setup_vault_secrets(None, None)
+        res = cli.CLI.setup_vault_secrets(None, None, auto_prompt=False)
         self.assertIsInstance(res, list)
 
     @patch('ansible.cli.get_file_vault_secret')
@@ -134,7 +161,6 @@ def test_prompt_no_tty(self, mock_prompt_secret):
                                           vault_ids=['prompt1@prompt'],
                                           ask_vault_pass=True)
 
-        print('res: %s' % res)
         self.assertIsInstance(res, list)
         self.assertEqual(len(res), 0)
         matches = vault.match_secrets(res, ['prompt1'])