diff options
author | Brian Coca <bcoca@users.noreply.github.com> | 2022-09-20 09:07:21 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-09-20 09:07:21 -0400 |
commit | b1ff0f4ebc7e964f8f67ffc344815a0d23577f45 (patch) | |
tree | 791c0278cbe48e545e78bacca85adf6d9630af00 | |
parent | e5e87a3927fdc7cf55790c7d37461b804d61b841 (diff) | |
download | ansible-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.yml | 2 | ||||
-rw-r--r-- | lib/ansible/cli/arguments/option_helpers.py | 8 | ||||
-rwxr-xr-x | lib/ansible/cli/vault.py | 3 | ||||
-rw-r--r-- | lib/ansible/parsing/vault/__init__.py | 24 | ||||
-rw-r--r-- | test/integration/targets/ansible-vault/realpath.yml | 10 | ||||
-rwxr-xr-x | test/integration/targets/ansible-vault/runme.sh | 25 | ||||
-rwxr-xr-x | test/integration/targets/ansible-vault/script/vault-secret.sh | 24 | ||||
-rw-r--r-- | test/integration/targets/ansible-vault/symlink.yml | 10 | ||||
l--------- | test/integration/targets/ansible-vault/symlink/get-password-symlink | 1 | ||||
-rw-r--r-- | test/integration/targets/ansible-vault/vars/vaulted.yml | 15 |
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 |