5

From reading the documentation,

You cannot rekey encrypted variables

For example, if this is the content of group_vars/all.yaml, I would like to rekey all the encrypted variables.

key_tab: !vault |
  $ANSIBLE_VAULT;1.1;AES256
  30333939663734636530386263663437343431353539643366633534366239643763326138653232
  3562383132623937346138613833396563653038646165300a623061363063663132373739373031
  66623133393239376366383235353332366336386532643637343438653634633734346639636334
  3633363032376339340a663531346633623466643163353638303534313937663931633962383637
  3637
certs:
  - file: client.cert
    password: !vault |
      $ANSIBLE_VAULT;1.1;AES256
      35626163653930386265393064326330393433343763626534373330393432373231633365656534
      6237626631326634333963313733356531623239653161370a356666326631663565396633396139
      32303962343064343530383364616235343130373935313161353135613539653061363735336337
      3636633036313565640a663736613065396262336433653564373161393431636661666134643761
      3639

I have tried to do this with a few bash commands, but it gets complicated with indentation.

Is there an automated way to accomplish this rekey?

applewil
  • 336
  • 1
  • 5
  • 15
  • 3
    You should do it in Python using ansible libs, see https://stackoverflow.com/questions/43467180/how-to-decrypt-string-with-ansible-vault-2-3-0 – Gaël Marziou Apr 14 '21 at 15:44

1 Answers1

9

From Gaël's suggestion, I created a python tool that uses ansible libs to accomplish the rekey.

It preserves indentation and updates files in place. Works for vault variables and regular vault files.

Script

#!/usr/bin/env python3

import sys
import re
from tempfile import NamedTemporaryFile
from ansible.parsing.vault import VaultEditor, VaultLib, VaultSecret
from ansible.constants import DEFAULT_VAULT_IDENTITY

def rekey(content, old_secret, new_secret):
  vault_regex = re.compile(r'(^(\s*)\$ANSIBLE_VAULT\S*\n(\s*\w+\n)*)', re.MULTILINE)
  vaults = {match[0]: match[1] for match in vault_regex.findall(content)}
  for old_vault, indentation in vaults.items():
    with NamedTemporaryFile(mode='w', delete=False) as f:
      f.write(old_vault.replace(indentation, ''))
    VaultEditor(VaultLib([(DEFAULT_VAULT_IDENTITY, old_secret)])).rekey_file(f.name, new_secret)
    with open(f.name) as f:
      new_vault = indentation + indentation.join(f.readlines())
      content = content.replace(old_vault, new_vault)
  return content

def main(old_password, new_password, files):
  for file_name in files:
    with open(file_name) as f:
      content = f.read()
    with open(file_name, 'w') as f:
      f.write(rekey(content, VaultSecret(old_password.encode()), VaultSecret(new_password.encode())))

main(sys.argv[1], sys.argv[2], sys.argv[3:])

Usage

./rekey.py my-old-pass my-new-pass $(find . -type f -name "*.yaml") another-file.vault

Explanation

For each input file:

  1. Read the input file and extract sequences that match the vault regex
  2. Save the extracted vaults to temporary files
  3. Rekey the temporary files
  4. Use the contents of the rekeyed files to make a replacement in the input file
applewil
  • 336
  • 1
  • 5
  • 15
  • 1
    This (or the equivalent feature) should actually be integrated to ansible-vault as key rotation is mandatory in quite a few companies and full file encryption is not always the best solution. – Zeitounator Apr 22 '21 at 13:33
  • 1
    Wow this saved me a ton of time when the order came from above to change our password and I have about 50+ in-line encrypted strings. Nice work, thank you. – JoeB Jun 23 '21 at 14:47
  • 1
    Thank you for that! I based this on your solution, which deletes temporary files that contain vaults, prompt for passwords, and has error messages that pint at the failed line: https://gist.github.com/panzi/81892af865a4818e9ccf578ab5766d36 – panzi Jan 27 '23 at 00:33