summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrian Coca <bcoca@users.noreply.github.com>2022-09-20 09:07:21 -0400
committerGitHub <noreply@github.com>2022-09-20 09:07:21 -0400
commitb1ff0f4ebc7e964f8f67ffc344815a0d23577f45 (patch)
tree791c0278cbe48e545e78bacca85adf6d9630af00
parente5e87a3927fdc7cf55790c7d37461b804d61b841 (diff)
downloadansible-b1ff0f4ebc7e964f8f67ffc344815a0d23577f45.tar.gz
vault secrets file, keep context when symlink (#78734)
* vault secrets file, keep context when symlink fixes #18319 Co-authored-by: Sloane Hertel <19572925+s-hertel@users.noreply.github.com>
-rw-r--r--changelogs/fragments/vault_syml_allow.yml2
-rw-r--r--lib/ansible/cli/arguments/option_helpers.py8
-rwxr-xr-xlib/ansible/cli/vault.py3
-rw-r--r--lib/ansible/parsing/vault/__init__.py24
-rw-r--r--test/integration/targets/ansible-vault/realpath.yml10
-rwxr-xr-xtest/integration/targets/ansible-vault/runme.sh25
-rwxr-xr-xtest/integration/targets/ansible-vault/script/vault-secret.sh24
-rw-r--r--test/integration/targets/ansible-vault/symlink.yml10
l---------test/integration/targets/ansible-vault/symlink/get-password-symlink1
-rw-r--r--test/integration/targets/ansible-vault/vars/vaulted.yml15
10 files changed, 107 insertions, 15 deletions
diff --git a/changelogs/fragments/vault_syml_allow.yml b/changelogs/fragments/vault_syml_allow.yml
new file mode 100644
index 0000000000..3442303852
--- /dev/null
+++ b/changelogs/fragments/vault_syml_allow.yml
@@ -0,0 +1,2 @@
+bugfixes:
+ - vault secrets file now executes in the correct context when it is a symlink (not resolved to canonical file).
diff --git a/lib/ansible/cli/arguments/option_helpers.py b/lib/ansible/cli/arguments/option_helpers.py
index bd4297edbb..cb37d57c19 100644
--- a/lib/ansible/cli/arguments/option_helpers.py
+++ b/lib/ansible/cli/arguments/option_helpers.py
@@ -87,16 +87,16 @@ def ensure_value(namespace, name, value):
#
# Callbacks to validate and normalize Options
#
-def unfrack_path(pathsep=False):
+def unfrack_path(pathsep=False, follow=True):
"""Turn an Option's data into a single path in Ansible locations"""
def inner(value):
if pathsep:
- return [unfrackpath(x) for x in value.split(os.pathsep) if x]
+ return [unfrackpath(x, follow=follow) for x in value.split(os.pathsep) if x]
if value == '-':
return value
- return unfrackpath(value)
+ return unfrackpath(value, follow=follow)
return inner
@@ -388,4 +388,4 @@ def add_vault_options(parser):
base_group.add_argument('--ask-vault-password', '--ask-vault-pass', default=C.DEFAULT_ASK_VAULT_PASS, dest='ask_vault_pass', action='store_true',
help='ask for vault password')
base_group.add_argument('--vault-password-file', '--vault-pass-file', default=[], dest='vault_password_files',
- help="vault password file", type=unfrack_path(), action='append')
+ help="vault password file", type=unfrack_path(follow=False), action='append')
diff --git a/lib/ansible/cli/vault.py b/lib/ansible/cli/vault.py
index 3491b22e42..3e60329de6 100755
--- a/lib/ansible/cli/vault.py
+++ b/lib/ansible/cli/vault.py
@@ -408,8 +408,7 @@ class VaultCLI(CLI):
# (the text itself, which input it came from, its name)
b_plaintext, src, name = b_plaintext_info
- b_ciphertext = self.editor.encrypt_bytes(b_plaintext, self.encrypt_secret,
- vault_id=vault_id)
+ b_ciphertext = self.editor.encrypt_bytes(b_plaintext, self.encrypt_secret, vault_id=vault_id)
# block formatting
yaml_text = self.format_ciphertext_yaml(b_ciphertext, name=name)
diff --git a/lib/ansible/parsing/vault/__init__.py b/lib/ansible/parsing/vault/__init__.py
index 3c83d2b1aa..8ac22d4ca9 100644
--- a/lib/ansible/parsing/vault/__init__.py
+++ b/lib/ansible/parsing/vault/__init__.py
@@ -57,7 +57,7 @@ from ansible import constants as C
from ansible.module_utils.six import binary_type
from ansible.module_utils._text import to_bytes, to_text, to_native
from ansible.utils.display import Display
-from ansible.utils.path import makedirs_safe
+from ansible.utils.path import makedirs_safe, unfrackpath
display = Display()
@@ -349,17 +349,25 @@ def script_is_client(filename):
def get_file_vault_secret(filename=None, vault_id=None, encoding=None, loader=None):
- this_path = os.path.realpath(os.path.expanduser(filename))
+ ''' Get secret from file content or execute file and get secret from stdout '''
+ # we unfrack but not follow the full path/context to possible vault script
+ # so when the script uses 'adjacent' file for configuration or similar
+ # it still works (as inventory scripts often also do).
+ # while files from --vault-password-file are already unfracked, other sources are not
+ this_path = unfrackpath(filename, follow=False)
if not os.path.exists(this_path):
raise AnsibleError("The vault password file %s was not found" % this_path)
+ # it is a script?
if loader.is_executable(this_path):
+
if script_is_client(filename):
- display.vvvv(u'The vault password file %s is a client script.' % to_text(filename))
+ # this is special script type that handles vault ids
+ display.vvvv(u'The vault password file %s is a client script.' % to_text(this_path))
# TODO: pass vault_id_name to script via cli
- return ClientScriptVaultSecret(filename=this_path, vault_id=vault_id,
- encoding=encoding, loader=loader)
+ return ClientScriptVaultSecret(filename=this_path, vault_id=vault_id, encoding=encoding, loader=loader)
+
# just a plain vault password script. No args, returns a byte array
return ScriptVaultSecret(filename=this_path, encoding=encoding, loader=loader)
@@ -432,8 +440,7 @@ class ScriptVaultSecret(FileVaultSecret):
vault_pass = stdout.strip(b'\r\n')
empty_password_msg = 'Invalid vault password was provided from script (%s)' % filename
- verify_secret_is_not_empty(vault_pass,
- msg=empty_password_msg)
+ verify_secret_is_not_empty(vault_pass, msg=empty_password_msg)
return vault_pass
@@ -659,8 +666,7 @@ class VaultLib:
msg += "%s is not a vault encrypted file" % to_native(filename)
raise AnsibleError(msg)
- b_vaulttext, dummy, cipher_name, vault_id = parse_vaulttext_envelope(b_vaulttext,
- filename=filename)
+ b_vaulttext, dummy, cipher_name, vault_id = parse_vaulttext_envelope(b_vaulttext, filename=filename)
# create the cipher object, note that the cipher used for decrypt can
# be different than the cipher used for encrypt
diff --git a/test/integration/targets/ansible-vault/realpath.yml b/test/integration/targets/ansible-vault/realpath.yml
new file mode 100644
index 0000000000..667963506f
--- /dev/null
+++ b/test/integration/targets/ansible-vault/realpath.yml
@@ -0,0 +1,10 @@
+- hosts: localhost
+ gather_facts: false
+ vars_files:
+ - vaulted.yml
+ tasks:
+ - name: see if we can decrypt
+ assert:
+ that:
+ - control is defined
+ - realpath == 'this is a secret'
diff --git a/test/integration/targets/ansible-vault/runme.sh b/test/integration/targets/ansible-vault/runme.sh
index bb893433f0..50720ea9f9 100755
--- a/test/integration/targets/ansible-vault/runme.sh
+++ b/test/integration/targets/ansible-vault/runme.sh
@@ -549,3 +549,28 @@ grep out.txt -e "[WARNING]: Error in vault password file loading (id2)"
grep out.txt -e "ERROR! Did not find a match for --encrypt-vault-id=id2 in the known vault-ids ['id3']"
set -e
+unset ANSIBLE_VAULT_IDENTITY_LIST
+
+# 'real script'
+ansible-playbook realpath.yml "$@" --vault-password-file script/vault-secret.sh
+
+# using symlink
+ansible-playbook symlink.yml "$@" --vault-password-file symlink/get-password-symlink
+
+### NEGATIVE TESTS
+
+ER='Attempting to decrypt'
+#### no secrets
+# 'real script'
+ansible-playbook realpath.yml "$@" 2>&1 |grep "${ER}"
+
+# using symlink
+ansible-playbook symlink.yml "$@" 2>&1 |grep "${ER}"
+
+ER='Decryption failed'
+### wrong secrets
+# 'real script'
+ansible-playbook realpath.yml "$@" --vault-password-file symlink/get-password-symlink 2>&1 |grep "${ER}"
+
+# using symlink
+ansible-playbook symlink.yml "$@" --vault-password-file script/vault-secret.sh 2>&1 |grep "${ER}"
diff --git a/test/integration/targets/ansible-vault/script/vault-secret.sh b/test/integration/targets/ansible-vault/script/vault-secret.sh
new file mode 100755
index 0000000000..3aa1c2efc9
--- /dev/null
+++ b/test/integration/targets/ansible-vault/script/vault-secret.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+
+set -eu
+
+# shellcheck disable=SC2086
+basename="$(basename $0)"
+# shellcheck disable=SC2046
+# shellcheck disable=SC2086
+dirname="$(basename $(dirname $0))"
+basename_prefix="get-password"
+default_password="foo-bar"
+
+case "${basename}" in
+ "${basename_prefix}"-*)
+ password="${default_password}-${basename#${basename_prefix}-}"
+ ;;
+ *)
+ password="${default_password}"
+ ;;
+esac
+
+# the password is different depending on the path used (direct or symlink)
+# it would be the same if symlink is 'resolved'.
+echo "${password}_${dirname}"
diff --git a/test/integration/targets/ansible-vault/symlink.yml b/test/integration/targets/ansible-vault/symlink.yml
new file mode 100644
index 0000000000..2dcf8a9739
--- /dev/null
+++ b/test/integration/targets/ansible-vault/symlink.yml
@@ -0,0 +1,10 @@
+- hosts: localhost
+ gather_facts: false
+ vars_files:
+ - vaulted.yml
+ tasks:
+ - name: see if we can decrypt
+ assert:
+ that:
+ - control is defined
+ - symlink == 'this is a test'
diff --git a/test/integration/targets/ansible-vault/symlink/get-password-symlink b/test/integration/targets/ansible-vault/symlink/get-password-symlink
new file mode 120000
index 0000000000..95a8abc5f1
--- /dev/null
+++ b/test/integration/targets/ansible-vault/symlink/get-password-symlink
@@ -0,0 +1 @@
+../script/vault-secret.sh \ No newline at end of file
diff --git a/test/integration/targets/ansible-vault/vars/vaulted.yml b/test/integration/targets/ansible-vault/vars/vaulted.yml
new file mode 100644
index 0000000000..40f5c54969
--- /dev/null
+++ b/test/integration/targets/ansible-vault/vars/vaulted.yml
@@ -0,0 +1,15 @@
+control: 1
+realpath: !vault |
+ $ANSIBLE_VAULT;1.1;AES256
+ 64343436666664636436363065356463363630653766323230333931366661656262343030386366
+ 6536616433353864616132303033623835316430623762360a646234383932656637623439353333
+ 36336362616564333663353739313766363333376461353962643531366338633336613565636636
+ 3663663664653538620a646132623835666336393333623439363361313934666530646334333765
+ 39386364646262396234616666666438313233626336376330366539663765373566
+symlink: !vault |
+ $ANSIBLE_VAULT;1.1;AES256
+ 61656138353366306464386332353938623338336333303831353164633834353437643635343635
+ 3461646235303261613766383437623664323032623137350a663934653735316334363832383534
+ 33623733346164376430643535616433383331663238383363316634353339326235663461353166
+ 3064663735353766660a653963373432383432373365633239313033646466653664346236363635
+ 6637