mirror of
https://github.com/ansible-collections/community.general.git
synced 2025-07-25 14:20:22 -07:00
add a vault --encrypt-vault-to specify vault id to use for encrypt (#31067)
Enforce that there can be only one --new-vault-id or --new-vault-password-file and use this instead of --encrypt-vault-id * Add a config option for default vault encrypt id
This commit is contained in:
parent
866239e01a
commit
ffe0ddea96
5 changed files with 110 additions and 19 deletions
|
@ -367,7 +367,7 @@ class CLI(with_metaclass(ABCMeta, object)):
|
||||||
if self.options.ask_su_pass or self.options.su_user:
|
if self.options.ask_su_pass or self.options.su_user:
|
||||||
_dep('su')
|
_dep('su')
|
||||||
|
|
||||||
def validate_conflicts(self, vault_opts=False, runas_opts=False, fork_opts=False):
|
def validate_conflicts(self, vault_opts=False, runas_opts=False, fork_opts=False, vault_rekey_opts=False):
|
||||||
''' check for conflicting options '''
|
''' check for conflicting options '''
|
||||||
|
|
||||||
op = self.options
|
op = self.options
|
||||||
|
@ -377,6 +377,10 @@ class CLI(with_metaclass(ABCMeta, object)):
|
||||||
if (op.ask_vault_pass and op.vault_password_files):
|
if (op.ask_vault_pass and op.vault_password_files):
|
||||||
self.parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive")
|
self.parser.error("--ask-vault-pass and --vault-password-file are mutually exclusive")
|
||||||
|
|
||||||
|
if vault_rekey_opts:
|
||||||
|
if (op.new_vault_id and op.new_vault_password_file):
|
||||||
|
self.parser.error("--new-vault-password-file and --new-vault-id are mutually exclusive")
|
||||||
|
|
||||||
if runas_opts:
|
if runas_opts:
|
||||||
# Check for privilege escalation conflicts
|
# Check for privilege escalation conflicts
|
||||||
if ((op.su or op.su_user) and (op.sudo or op.sudo_user) or
|
if ((op.su or op.su_user) and (op.sudo or op.sudo_user) or
|
||||||
|
@ -452,8 +456,8 @@ class CLI(with_metaclass(ABCMeta, object)):
|
||||||
help='the vault identity to use')
|
help='the vault identity to use')
|
||||||
|
|
||||||
if vault_rekey_opts:
|
if vault_rekey_opts:
|
||||||
parser.add_option('--new-vault-password-file', default=[], dest='new_vault_password_files',
|
parser.add_option('--new-vault-password-file', default=None, dest='new_vault_password_file',
|
||||||
help="new vault password file for rekey", action="callback", callback=CLI.unfrack_paths, type='string')
|
help="new vault password file for rekey", action="callback", callback=CLI.unfrack_path, type='string')
|
||||||
parser.add_option('--new-vault-id', default=None, dest='new_vault_id', type='string',
|
parser.add_option('--new-vault-id', default=None, dest='new_vault_id', type='string',
|
||||||
help='the new vault identity to use for rekey')
|
help='the new vault identity to use for rekey')
|
||||||
|
|
||||||
|
|
|
@ -106,6 +106,12 @@ class VaultCLI(CLI):
|
||||||
elif self.action == "rekey":
|
elif self.action == "rekey":
|
||||||
self.parser.set_usage("usage: %prog rekey [options] file_name")
|
self.parser.set_usage("usage: %prog rekey [options] file_name")
|
||||||
|
|
||||||
|
# For encrypting actions, we can also specify which of multiple vault ids should be used for encrypting
|
||||||
|
if self.action in ['create', 'encrypt', 'encrypt_string', 'rekey']:
|
||||||
|
self.parser.add_option('--encrypt-vault-id', default=[], dest='encrypt_vault_id',
|
||||||
|
action='store', type='string',
|
||||||
|
help='the vault id used to encrypt (required if more than vault-id is provided)')
|
||||||
|
|
||||||
def parse(self):
|
def parse(self):
|
||||||
|
|
||||||
self.parser = CLI.base_parser(
|
self.parser = CLI.base_parser(
|
||||||
|
@ -119,6 +125,7 @@ class VaultCLI(CLI):
|
||||||
self.set_action()
|
self.set_action()
|
||||||
|
|
||||||
super(VaultCLI, self).parse()
|
super(VaultCLI, self).parse()
|
||||||
|
self.validate_conflicts(vault_opts=True, vault_rekey_opts=True)
|
||||||
|
|
||||||
display.verbosity = self.options.verbosity
|
display.verbosity = self.options.verbosity
|
||||||
|
|
||||||
|
@ -174,9 +181,12 @@ class VaultCLI(CLI):
|
||||||
if not vault_secrets:
|
if not vault_secrets:
|
||||||
raise AnsibleOptionsError("A vault password is required to use Ansible's Vault")
|
raise AnsibleOptionsError("A vault password is required to use Ansible's Vault")
|
||||||
|
|
||||||
if self.action in ['encrypt', 'encrypt_string', 'create']:
|
if self.action in ['encrypt', 'encrypt_string', 'create', 'edit']:
|
||||||
if len(vault_ids) > 1:
|
|
||||||
raise AnsibleOptionsError("Only one --vault-id can be used for encryption")
|
encrypt_vault_id = None
|
||||||
|
# no --encrypt-vault-id self.options.encrypt_vault_id for 'edit'
|
||||||
|
if self.action not in ['edit']:
|
||||||
|
encrypt_vault_id = self.options.encrypt_vault_id or C.DEFAULT_VAULT_ENCRYPT_IDENTITY
|
||||||
|
|
||||||
vault_secrets = None
|
vault_secrets = None
|
||||||
vault_secrets = \
|
vault_secrets = \
|
||||||
|
@ -186,36 +196,52 @@ class VaultCLI(CLI):
|
||||||
ask_vault_pass=self.options.ask_vault_pass,
|
ask_vault_pass=self.options.ask_vault_pass,
|
||||||
create_new_password=True)
|
create_new_password=True)
|
||||||
|
|
||||||
if len(vault_secrets) > 1:
|
if len(vault_secrets) > 1 and not encrypt_vault_id:
|
||||||
raise AnsibleOptionsError("Only one --vault-id can be used for encryption. This includes passwords from configuration and cli.")
|
raise AnsibleOptionsError("The vault-ids %s are available to encrypt. Specify the vault-id to encrypt with --encrypt-vault-id" %
|
||||||
|
','.join([x[0] for x in vault_secrets]))
|
||||||
|
|
||||||
if not vault_secrets:
|
if not vault_secrets:
|
||||||
raise AnsibleOptionsError("A vault password is required to use Ansible's Vault")
|
raise AnsibleOptionsError("A vault password is required to use Ansible's Vault")
|
||||||
|
|
||||||
encrypt_secret = match_encrypt_secret(vault_secrets)
|
encrypt_secret = match_encrypt_secret(vault_secrets,
|
||||||
|
encrypt_vault_id=encrypt_vault_id)
|
||||||
|
|
||||||
# only one secret for encrypt for now, use the first vault_id and use its first secret
|
# only one secret for encrypt for now, use the first vault_id and use its first secret
|
||||||
# self.encrypt_vault_id = list(vault_secrets.keys())[0]
|
# TODO: exception if more than one?
|
||||||
# self.encrypt_secret = vault_secrets[self.encrypt_vault_id][0]
|
|
||||||
self.encrypt_vault_id = encrypt_secret[0]
|
self.encrypt_vault_id = encrypt_secret[0]
|
||||||
self.encrypt_secret = encrypt_secret[1]
|
self.encrypt_secret = encrypt_secret[1]
|
||||||
|
|
||||||
if self.action in ['rekey']:
|
if self.action in ['rekey']:
|
||||||
|
encrypt_vault_id = self.options.encrypt_vault_id or C.DEFAULT_VAULT_ENCRYPT_IDENTITY
|
||||||
|
# print('encrypt_vault_id: %s' % encrypt_vault_id)
|
||||||
|
# print('default_encrypt_vault_id: %s' % default_encrypt_vault_id)
|
||||||
|
|
||||||
|
# new_vault_ids should only ever be one item, from
|
||||||
|
# load the default vault ids if we are using encrypt-vault-id
|
||||||
new_vault_ids = []
|
new_vault_ids = []
|
||||||
|
if encrypt_vault_id:
|
||||||
|
new_vault_ids = default_vault_ids
|
||||||
if self.options.new_vault_id:
|
if self.options.new_vault_id:
|
||||||
new_vault_ids.append(self.options.new_vault_id)
|
new_vault_ids.append(self.options.new_vault_id)
|
||||||
|
|
||||||
|
new_vault_password_files = []
|
||||||
|
if self.options.new_vault_password_file:
|
||||||
|
new_vault_password_files.append(self.options.new_vault_password_file)
|
||||||
|
|
||||||
new_vault_secrets = \
|
new_vault_secrets = \
|
||||||
self.setup_vault_secrets(loader,
|
self.setup_vault_secrets(loader,
|
||||||
vault_ids=new_vault_ids,
|
vault_ids=new_vault_ids,
|
||||||
vault_password_files=self.options.new_vault_password_files,
|
vault_password_files=new_vault_password_files,
|
||||||
ask_vault_pass=self.options.ask_vault_pass,
|
ask_vault_pass=self.options.ask_vault_pass,
|
||||||
create_new_password=True)
|
create_new_password=True)
|
||||||
|
|
||||||
if not new_vault_secrets:
|
if not new_vault_secrets:
|
||||||
raise AnsibleOptionsError("A new vault password is required to use Ansible's Vault rekey")
|
raise AnsibleOptionsError("A new vault password is required to use Ansible's Vault rekey")
|
||||||
|
|
||||||
# There is only one new_vault_id currently and one new_vault_secret
|
# There is only one new_vault_id currently and one new_vault_secret, or we
|
||||||
new_encrypt_secret = match_encrypt_secret(new_vault_secrets)
|
# use the id specified in --encrypt-vault-id
|
||||||
|
new_encrypt_secret = match_encrypt_secret(new_vault_secrets,
|
||||||
|
encrypt_vault_id=encrypt_vault_id)
|
||||||
|
|
||||||
self.new_encrypt_vault_id = new_encrypt_secret[0]
|
self.new_encrypt_vault_id = new_encrypt_secret[0]
|
||||||
self.new_encrypt_secret = new_encrypt_secret[1]
|
self.new_encrypt_secret = new_encrypt_secret[1]
|
||||||
|
|
|
@ -1102,6 +1102,14 @@ DEFAULT_VAULT_IDENTITY:
|
||||||
ini:
|
ini:
|
||||||
- {key: vault_identity, section: defaults}
|
- {key: vault_identity, section: defaults}
|
||||||
yaml: {key: defaults.vault_identity}
|
yaml: {key: defaults.vault_identity}
|
||||||
|
DEFAULT_VAULT_ENCRYPT_IDENTITY:
|
||||||
|
name: Vault id to use for encryption
|
||||||
|
default:
|
||||||
|
description: 'The vault_id to use for encrypting by default. If multiple vault_ids are provided, this specifies which to use for encryption. The --encrypt-vault-id cli option overrides the configured value.'
|
||||||
|
env: [{name: ANSIBLE_VAULT_ENCRYPT_IDENTITY}]
|
||||||
|
ini:
|
||||||
|
- {key: vault_encrypt_identity, section: defaults}
|
||||||
|
yaml: {key: defaults.vault_encrypt_identity}
|
||||||
DEFAULT_VAULT_IDENTITY_LIST:
|
DEFAULT_VAULT_IDENTITY_LIST:
|
||||||
name: Default vault ids
|
name: Default vault ids
|
||||||
default: []
|
default: []
|
||||||
|
|
|
@ -556,12 +556,40 @@ def match_best_secret(secrets, target_vault_ids):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def match_encrypt_secret(secrets):
|
def match_encrypt_vault_id_secret(secrets, encrypt_vault_id=None):
|
||||||
|
# See if the --encrypt-vault-id matches a vault-id
|
||||||
|
display.vvvv('encrypt_vault_id=%s' % encrypt_vault_id)
|
||||||
|
|
||||||
|
if encrypt_vault_id is None:
|
||||||
|
raise AnsibleError('match_encrypt_vault_id_secret requires a non None encrypt_vault_id')
|
||||||
|
|
||||||
|
encrypt_vault_id_matchers = [encrypt_vault_id]
|
||||||
|
encrypt_secret = match_best_secret(secrets, encrypt_vault_id_matchers)
|
||||||
|
|
||||||
|
# return the best match for --encrypt-vault-id
|
||||||
|
if encrypt_secret:
|
||||||
|
return encrypt_secret
|
||||||
|
|
||||||
|
# If we specified a encrypt_vault_id and we couldn't find it, dont
|
||||||
|
# fallback to using the first/best secret
|
||||||
|
raise AnsibleVaultError('Did not find a match for --encrypt-vault-id=%s in the known vault-ids %s' % (encrypt_vault_id,
|
||||||
|
[_v for _v, _vs in secrets]))
|
||||||
|
|
||||||
|
|
||||||
|
def match_encrypt_secret(secrets, encrypt_vault_id=None):
|
||||||
'''Find the best/first/only secret in secrets to use for encrypting'''
|
'''Find the best/first/only secret in secrets to use for encrypting'''
|
||||||
|
|
||||||
|
display.vvvv('encrypt_vault_id=%s' % encrypt_vault_id)
|
||||||
|
# See if the --encrypt-vault-id matches a vault-id
|
||||||
|
if encrypt_vault_id:
|
||||||
|
return match_encrypt_vault_id_secret(secrets,
|
||||||
|
encrypt_vault_id=encrypt_vault_id)
|
||||||
|
|
||||||
|
# Find the best/first secret from secrets since we didnt specify otherwise
|
||||||
# ie, consider all of the available secrets as matches
|
# ie, consider all of the available secrets as matches
|
||||||
_vault_id_matchers = [_vault_id for _vault_id, dummy in secrets]
|
_vault_id_matchers = [_vault_id for _vault_id, dummy in secrets]
|
||||||
best_secret = match_best_secret(secrets, _vault_id_matchers)
|
best_secret = match_best_secret(secrets, _vault_id_matchers)
|
||||||
|
|
||||||
# can be empty list sans any tuple
|
# can be empty list sans any tuple
|
||||||
return best_secret
|
return best_secret
|
||||||
|
|
||||||
|
@ -625,7 +653,11 @@ class VaultLib:
|
||||||
raise AnsibleError(u"{0} cipher could not be found".format(self.cipher_name))
|
raise AnsibleError(u"{0} cipher could not be found".format(self.cipher_name))
|
||||||
|
|
||||||
# encrypt data
|
# encrypt data
|
||||||
display.vvvvv('Encrypting with vault secret %s' % secret)
|
if vault_id:
|
||||||
|
display.vvvvv('Encrypting with vault_id "%s" and vault secret %s' % (vault_id, secret))
|
||||||
|
else:
|
||||||
|
display.vvvvv('Encrypting without a vault_id using vault secret %s' % secret)
|
||||||
|
|
||||||
b_ciphertext = this_cipher.encrypt(b_plaintext, secret)
|
b_ciphertext = this_cipher.encrypt(b_plaintext, secret)
|
||||||
|
|
||||||
# format the data for output to the file
|
# format the data for output to the file
|
||||||
|
@ -725,7 +757,10 @@ class VaultLib:
|
||||||
b_plaintext = this_cipher.decrypt(b_vaulttext, vault_secret)
|
b_plaintext = this_cipher.decrypt(b_vaulttext, vault_secret)
|
||||||
if b_plaintext is not None:
|
if b_plaintext is not None:
|
||||||
vault_id_used = vault_secret_id
|
vault_id_used = vault_secret_id
|
||||||
display.vvvvv('decrypt successful with secret=%s and vault_id=%s' % (vault_secret, vault_secret_id))
|
file_slug = ''
|
||||||
|
if filename:
|
||||||
|
file_slug = ' of "%s"' % filename
|
||||||
|
display.vvvvv('Decrypt%s successful with secret=%s and vault_id=%s' % (file_slug, vault_secret, vault_secret_id))
|
||||||
break
|
break
|
||||||
except AnsibleVaultFormatError as exc:
|
except AnsibleVaultFormatError as exc:
|
||||||
msg = "There was a vault format error"
|
msg = "There was a vault format error"
|
||||||
|
@ -963,7 +998,7 @@ class VaultEditor:
|
||||||
vaulttext = to_text(b_vaulttext)
|
vaulttext = to_text(b_vaulttext)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
plaintext = self.vault.decrypt(vaulttext)
|
plaintext = self.vault.decrypt(vaulttext, filename=filename)
|
||||||
return plaintext
|
return plaintext
|
||||||
except AnsibleError as e:
|
except AnsibleError as e:
|
||||||
raise AnsibleVaultError("%s for %s" % (to_bytes(e), to_bytes(filename)))
|
raise AnsibleVaultError("%s for %s" % (to_bytes(e), to_bytes(filename)))
|
||||||
|
@ -978,8 +1013,10 @@ class VaultEditor:
|
||||||
b_vaulttext = self.read_data(filename)
|
b_vaulttext = self.read_data(filename)
|
||||||
vaulttext = to_text(b_vaulttext)
|
vaulttext = to_text(b_vaulttext)
|
||||||
|
|
||||||
|
display.vvvvv('Rekeying file "%s" to with new vault-id "%s" and vault secret %s' %
|
||||||
|
(filename, new_vault_id, new_vault_secret))
|
||||||
try:
|
try:
|
||||||
plaintext = self.vault.decrypt(vaulttext)
|
plaintext, vault_id_used = self.vault.decrypt_and_get_vault_id(vaulttext)
|
||||||
except AnsibleError as e:
|
except AnsibleError as e:
|
||||||
raise AnsibleError("%s for %s" % (to_bytes(e), to_bytes(filename)))
|
raise AnsibleError("%s for %s" % (to_bytes(e), to_bytes(filename)))
|
||||||
|
|
||||||
|
@ -1004,6 +1041,9 @@ class VaultEditor:
|
||||||
os.chmod(filename, prev.st_mode)
|
os.chmod(filename, prev.st_mode)
|
||||||
os.chown(filename, prev.st_uid, prev.st_gid)
|
os.chown(filename, prev.st_uid, prev.st_gid)
|
||||||
|
|
||||||
|
display.vvvvv('Rekeyed file "%s" (decrypted with vault id "%s") was encrypted with new vault-id "%s" and vault secret %s' %
|
||||||
|
(filename, vault_id_used, new_vault_id, new_vault_secret))
|
||||||
|
|
||||||
def read_data(self, filename):
|
def read_data(self, filename):
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -185,6 +185,13 @@ WRONG_RC=$?
|
||||||
echo "rc was $WRONG_RC (1 is expected)"
|
echo "rc was $WRONG_RC (1 is expected)"
|
||||||
[ $WRONG_RC -eq 1 ]
|
[ $WRONG_RC -eq 1 ]
|
||||||
|
|
||||||
|
# try specifying a --encrypt-vault-id that doesnt exist, should exit with an error indicating
|
||||||
|
# that --encrypt-vault-id and the known vault-ids
|
||||||
|
ansible-vault encrypt "$@" --vault-password-file vault-password --encrypt-vault-id doesnt_exist "${TEST_FILE}" && :
|
||||||
|
WRONG_RC=$?
|
||||||
|
echo "rc was $WRONG_RC (1 is expected)"
|
||||||
|
[ $WRONG_RC -eq 1 ]
|
||||||
|
|
||||||
# encrypt it
|
# encrypt it
|
||||||
ansible-vault encrypt "$@" --vault-password-file vault-password "${TEST_FILE}"
|
ansible-vault encrypt "$@" --vault-password-file vault-password "${TEST_FILE}"
|
||||||
|
|
||||||
|
@ -252,6 +259,12 @@ ansible-vault encrypt "$@" --vault-password-file vault-password "${TEST_FILE}"
|
||||||
|
|
||||||
ansible-vault rekey "$@" --vault-password-file vault-password --new-vault-password-file "${NEW_VAULT_PASSWORD}" "${TEST_FILE}"
|
ansible-vault rekey "$@" --vault-password-file vault-password --new-vault-password-file "${NEW_VAULT_PASSWORD}" "${TEST_FILE}"
|
||||||
|
|
||||||
|
# --new-vault-password-file and --new-vault-id should cause options error
|
||||||
|
ansible-vault rekey "$@" --vault-password-file vault-password --new-vault-id=foobar --new-vault-password-file "${NEW_VAULT_PASSWORD}" "${TEST_FILE}" && :
|
||||||
|
WRONG_RC=$?
|
||||||
|
echo "rc was $WRONG_RC (2 is expected)"
|
||||||
|
[ $WRONG_RC -eq 2 ]
|
||||||
|
|
||||||
ansible-vault view "$@" --vault-password-file "${NEW_VAULT_PASSWORD}" "${TEST_FILE}"
|
ansible-vault view "$@" --vault-password-file "${NEW_VAULT_PASSWORD}" "${TEST_FILE}"
|
||||||
|
|
||||||
# view with old password file and new password file
|
# view with old password file and new password file
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue