filter_plugins: Add decrypt Jinja2 filter
The `decrypt` filter decrypts an ASCII-armored string encrypted with `age`. It simply pipes the string to `age -d -i age.key` and returns the contents of standard output. The path to the key file (passed with the `-i` argument) can be changed using the `key` keyword to the filter. Using `age`-encrypted data in this way has a few advantages over Ansible Vault. Different values can be encrypted with different keys, which Ansible Vault does support with vault IDs, but it is very cumbersome, almost to the point of being useless. Using multiple IDs requires explicitly specifying the IDs to use (thus knowing ahead of time which ones are needed) and storing each password in a separate file. With the `decrypt` filter, all the keys one has can be stored in a single file, and `age` will find the correct one. More importantly, though, the values remain encrypted until they are **explicitly** decrypted (e.g. when rendered in a template). Contrast with Vault, where values are **implicitly** decrypted any time they are used (including printing with `debug`, etc.), which could potentially lead inappropriate exposure. Finally, the `age` tooling is easier to work with and more composable than Ansible Vault, especially given that the latter literally _only_ works with Ansible. In the next series of commits, I will be converting all usage of Ansible Vault in inventory variables (i.e. those in `host_vars` and `group_vars`) to use `age` (or outright removing those that are no longer relevant).no-vault-in-inventory
parent
60fa380e5d
commit
e3d0b5e918
|
@ -2,4 +2,6 @@
|
||||||
.fact-cache
|
.fact-cache
|
||||||
/victoria-metrics-*.tar.gz
|
/victoria-metrics-*.tar.gz
|
||||||
/victoria-metrics-*/
|
/victoria-metrics-*/
|
||||||
|
__pycache__/
|
||||||
/tmp/
|
/tmp/
|
||||||
|
/age.key
|
||||||
|
|
|
@ -0,0 +1,37 @@
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
from typing import Callable, Optional
|
||||||
|
|
||||||
|
from ansible.errors import AnsibleError
|
||||||
|
|
||||||
|
|
||||||
|
class AgeError(AnsibleError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FilterModule:
|
||||||
|
def filters(self) -> dict[str, Callable[..., str]]:
|
||||||
|
return {
|
||||||
|
'decrypt': age_filter,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def age_filter(data: str, key: Optional[str] = None) -> str:
|
||||||
|
if key is None:
|
||||||
|
key = 'age.key'
|
||||||
|
key = os.path.expanduser(key)
|
||||||
|
p = subprocess.Popen(
|
||||||
|
['age', '-d', '-i', key],
|
||||||
|
stdin=subprocess.PIPE,
|
||||||
|
stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.PIPE,
|
||||||
|
)
|
||||||
|
stdout, stderr = p.communicate(data.encode('utf-8'))
|
||||||
|
if p.returncode != 0:
|
||||||
|
error = ' '.join(
|
||||||
|
l
|
||||||
|
for l in stderr.decode('utf-8', errors='replace').splitlines()
|
||||||
|
if not l.startswith('age: report unexpected')
|
||||||
|
)
|
||||||
|
raise AgeError(error)
|
||||||
|
return stdout.decode('utf-8')
|
|
@ -0,0 +1,6 @@
|
||||||
|
[tool.black]
|
||||||
|
line-length = 79
|
||||||
|
skip-string-normalization = true
|
||||||
|
|
||||||
|
[tool.isort]
|
||||||
|
lines_after_imports = 2
|
Loading…
Reference in New Issue