mirror of
https://github.com/zebrajr/ansible.git
synced 2025-12-06 00:19:48 +01:00
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>
This commit is contained in:
parent
e5e87a3927
commit
b1ff0f4ebc
2
changelogs/fragments/vault_syml_allow.yml
Normal file
2
changelogs/fragments/vault_syml_allow.yml
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
bugfixes:
|
||||
- vault secrets file now executes in the correct context when it is a symlink (not resolved to canonical file).
|
||||
|
|
@ -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')
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
10
test/integration/targets/ansible-vault/realpath.yml
Normal file
10
test/integration/targets/ansible-vault/realpath.yml
Normal file
|
|
@ -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'
|
||||
|
|
@ -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}"
|
||||
|
|
|
|||
24
test/integration/targets/ansible-vault/script/vault-secret.sh
Executable file
24
test/integration/targets/ansible-vault/script/vault-secret.sh
Executable file
|
|
@ -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}"
|
||||
10
test/integration/targets/ansible-vault/symlink.yml
Normal file
10
test/integration/targets/ansible-vault/symlink.yml
Normal file
|
|
@ -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'
|
||||
|
|
@ -0,0 +1 @@
|
|||
../script/vault-secret.sh
|
||||
15
test/integration/targets/ansible-vault/vars/vaulted.yml
Normal file
15
test/integration/targets/ansible-vault/vars/vaulted.yml
Normal file
|
|
@ -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
|
||||
Loading…
Reference in New Issue
Block a user