ansible-vars 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- ansible_vars/__init__.py +0 -0
- ansible_vars/cli.py +983 -0
- ansible_vars/constants.py +51 -0
- ansible_vars/errors.py +28 -0
- ansible_vars/util.py +387 -0
- ansible_vars/vault.py +830 -0
- ansible_vars/vault_crypt.py +181 -0
- ansible_vars-1.0.0.dist-info/METADATA +254 -0
- ansible_vars-1.0.0.dist-info/RECORD +12 -0
- ansible_vars-1.0.0.dist-info/WHEEL +4 -0
- ansible_vars-1.0.0.dist-info/entry_points.txt +2 -0
- ansible_vars-1.0.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,181 @@
|
|
1
|
+
# Vault secret loading and management for ansible-vars
|
2
|
+
|
3
|
+
# Standard library imports
|
4
|
+
import re
|
5
|
+
from typing import Type
|
6
|
+
|
7
|
+
# External library imports
|
8
|
+
import ansible.constants as Ansible
|
9
|
+
from ansible.parsing.vault import VaultLib, VaultSecret, AnsibleVaultError
|
10
|
+
from ansible.cli import DataLoader, CLI
|
11
|
+
|
12
|
+
# Internal module imports
|
13
|
+
from .errors import NoMatchingVaultKeyError, NoVaultKeysError, VaultKeyMatchError
|
14
|
+
|
15
|
+
class VaultKey():
|
16
|
+
'''
|
17
|
+
Represents a single Ansible vault secret and allows to en- and decrypt vault data with it.
|
18
|
+
Can be initialized directly from an Ansible secrets tuple `(vault_id | None, VaultSecret)` through `VaultKey.from_ansible_secret`.
|
19
|
+
'''
|
20
|
+
|
21
|
+
def __init__(self, secret: str | VaultSecret, vault_id: str | None = None) -> None:
|
22
|
+
'''
|
23
|
+
Create a new VaultKey from an Ansible VaultSecret or directly from a passphrase.
|
24
|
+
Takes an optional vault ID which should be identical to the vault ID used to encrypt any data you wish to decrypt.
|
25
|
+
If no ID is supplied, Ansible's default vault identity value is used, which should match all vault IDs.
|
26
|
+
Note that IDs are not necessarily unique. For example, the default identity may be used for multiple `VaultKey`s.
|
27
|
+
'''
|
28
|
+
# Use default vault ID if none is supplied
|
29
|
+
self.id: str = Ansible.DEFAULT_VAULT_IDENTITY if vault_id is None else vault_id # type: ignore
|
30
|
+
# Convert passphrase to VaultSecret if necessary
|
31
|
+
self.secret: VaultSecret = VaultSecret(secret.encode('utf-8')) if type(secret) is str else secret # type: ignore
|
32
|
+
# Pass secret to VaultLib (we need an individual instance because `VaultLib.decrypt` doesn't take an explicit secret)
|
33
|
+
self._vaultlib: VaultLib = VaultLib([ self.to_ansible_secret() ])
|
34
|
+
|
35
|
+
@classmethod
|
36
|
+
def from_ansible_secret(VaultKey: Type['VaultKey'], secret: tuple[str | None, VaultSecret]) -> 'VaultKey':
|
37
|
+
'''Converts an Ansible secrets tuple `(vault_id | None, VaultSecret)` to a `VaultKey` instance.'''
|
38
|
+
return VaultKey(secret[1], vault_id=secret[0])
|
39
|
+
|
40
|
+
def to_ansible_secret(self) -> tuple[str, VaultSecret]:
|
41
|
+
'''Converts this `VaultKey` into an Ansible secrets tuple `(vault_id | None, VaultSecret)`.'''
|
42
|
+
return ( self.id, self.secret )
|
43
|
+
|
44
|
+
@property
|
45
|
+
def passphrase(self) -> str | None:
|
46
|
+
'''Returns the passphrase associated with the key's secret, or None if it cannot be decoded.'''
|
47
|
+
return self.secret.bytes.decode('utf-8') if type(self.secret.bytes) is bytes else None
|
48
|
+
|
49
|
+
@staticmethod
|
50
|
+
def is_encrypted(test_me: str) -> bool:
|
51
|
+
'''
|
52
|
+
Tests if a string is an encrypted Ansible vault string.
|
53
|
+
Expects a string with optional YAML tag preamble (`!vault | $ANSIBLE_VAULT;<options>\\n<cipher>`).
|
54
|
+
'''
|
55
|
+
return VaultLib.is_encrypted(VaultKey._strip_vault_tag(test_me))
|
56
|
+
|
57
|
+
def encrypt(self, plain: str) -> str:
|
58
|
+
'''Encrypts a string using this `VaultKey`'s secret.'''
|
59
|
+
# Pass our secret directly to the encrypt call to skip expensive secret matching
|
60
|
+
# Beware: the encrypt function takes a `secret`, but means just the VaultSecret and not a tuple of (vault_id, VaultSecret)
|
61
|
+
# In other calls, `secret` or `secrets` may refer to the tuple(s)
|
62
|
+
return self._vaultlib.encrypt(plain, secret=self.secret, vault_id=self.id).decode('utf-8').strip()
|
63
|
+
|
64
|
+
def decrypt(self, vault_cipher: str) -> str:
|
65
|
+
'''
|
66
|
+
Tries to decrypt a string using this `VaultKey`'s secret.
|
67
|
+
Expects a cipher with optional YAML tag preamble (`!vault | $ANSIBLE_VAULT;<options>\\n<cipher>`).
|
68
|
+
If the secret does not match the cipher, a `VaultKeyMatchError` will be raised.
|
69
|
+
'''
|
70
|
+
vault_cipher = VaultKey._strip_vault_tag(vault_cipher)
|
71
|
+
try:
|
72
|
+
decrypted: bytes = self._vaultlib.decrypt(vault_cipher)
|
73
|
+
return decrypted.decode('utf-8').strip()
|
74
|
+
except AnsibleVaultError as e:
|
75
|
+
if e.message.startswith('Decryption failed (no vault secrets were found that could decrypt)'):
|
76
|
+
raise VaultKeyMatchError(f"Could not match cipher with { self }")
|
77
|
+
raise e
|
78
|
+
|
79
|
+
@staticmethod
|
80
|
+
def _strip_vault_tag(vault_cipher: str) -> str:
|
81
|
+
'''Strips extra whitespace and any YAML vault tag preamble from the cipher.'''
|
82
|
+
EXTRACTION_PATTERN: str = r'^(?:\s*!vault\s*[\|>]?\-?\s*)?(.*)$'
|
83
|
+
match_result: re.Match[str] | None = re.search(EXTRACTION_PATTERN, vault_cipher.strip(), re.DOTALL)
|
84
|
+
return vault_cipher.strip() if match_result is None else match_result.group(1).strip()
|
85
|
+
|
86
|
+
def __repr__(self) -> str:
|
87
|
+
return f"VaultKey({ self.id })"
|
88
|
+
|
89
|
+
class VaultKeyring():
|
90
|
+
'''
|
91
|
+
A collection of Ansible vault secrets to be used for en- and decrypting vault data.
|
92
|
+
Tries to infer available secrets from the caller's present working directory, if it is an Ansible home.
|
93
|
+
'''
|
94
|
+
|
95
|
+
def __init__(
|
96
|
+
self,
|
97
|
+
keys: list[VaultKey] | None = None,
|
98
|
+
default_encryption_key: VaultKey | None = None,
|
99
|
+
detect_available_keys: bool = True
|
100
|
+
) -> None:
|
101
|
+
'''
|
102
|
+
Create a new keyring of `VaultKey`s and optionally populate it with the given `keys`.
|
103
|
+
Tries to infer available Ansible vault secrets from the caller's present working directory, if it is an Ansible home.
|
104
|
+
This is done using the Ansible CLI module. You can disable key inferral by setting `detect_available_keys` to False.
|
105
|
+
When decrypting vault data, inferred keys are tried after the explicitly supplied ones.
|
106
|
+
Note that the inferral process may cause TTY prompts or other unwanted in- and output.
|
107
|
+
|
108
|
+
When encrypting data, you can specify an explicit `VaultKey` to use. If none is specified, `default_encryption_key` is used.
|
109
|
+
If no explicit or default keys are available, the first key of the `keys` parameter is used.
|
110
|
+
'''
|
111
|
+
self.keys: list[VaultKey] = keys or []
|
112
|
+
self.default_encryption_key: VaultKey | None = default_encryption_key
|
113
|
+
if detect_available_keys:
|
114
|
+
self.keys.extend(VaultKeyring.load_cli_secrets())
|
115
|
+
|
116
|
+
@property
|
117
|
+
def encryption_key(self) -> VaultKey:
|
118
|
+
'''
|
119
|
+
Get the key used for encryption or raise a `NoVaultKeysError` if none are available.
|
120
|
+
If no `default_encryption_key` is set for this instance, the first key of the `keys` array is used.
|
121
|
+
Note that you can override this behavior by passing an explicit key to the `encrypt` method.
|
122
|
+
'''
|
123
|
+
if not (self.default_encryption_key or self.keys):
|
124
|
+
raise NoVaultKeysError('No vault keys available for encryption')
|
125
|
+
return self.default_encryption_key or self.keys[0]
|
126
|
+
|
127
|
+
def encrypt(self, plain: str, key: VaultKey | None = None) -> str:
|
128
|
+
'''
|
129
|
+
Encrypts the given vault data using the supplied `VaultKey`.
|
130
|
+
If no key is supplied, the `VaultKeyring`'s `default_encryption_key` is used.
|
131
|
+
If that key is also unset, the first key of the `VaultKeyring`'s `keys` is used.
|
132
|
+
'''
|
133
|
+
# If no key is provided, use the default encryption key or first key in `keys`
|
134
|
+
if not key:
|
135
|
+
key = self.encryption_key
|
136
|
+
return key.encrypt(plain)
|
137
|
+
|
138
|
+
def decrypt(self, vault_cipher: str, key: VaultKey | None = None) -> str:
|
139
|
+
'''
|
140
|
+
Tries to decrypt the given vault data using the supplied `VaultKey`.
|
141
|
+
If no key is supplied, all of the `VaultKeyring`'s `keys` are tried in order (by default, inferred keys are last).
|
142
|
+
If no key matches the cipher, a `NoMatchingVaultKeyError` will be raised.
|
143
|
+
|
144
|
+
Expects a cipher with optional YAML tag preamble (`!vault | $ANSIBLE_VAULT;<options>\\n<cipher>`).
|
145
|
+
'''
|
146
|
+
if not key and not self.keys:
|
147
|
+
raise NoVaultKeysError('No vault keys available for decryption')
|
148
|
+
if key:
|
149
|
+
return key.decrypt(vault_cipher)
|
150
|
+
# Search for matching key
|
151
|
+
for key in self.keys:
|
152
|
+
try: return key.decrypt(vault_cipher)
|
153
|
+
except VaultKeyMatchError: pass
|
154
|
+
# No keys matched
|
155
|
+
raise NoMatchingVaultKeyError(f"Found no matching vault key to decrypt cipher in { self }")
|
156
|
+
|
157
|
+
def key_by_id(self, id: str) -> VaultKey:
|
158
|
+
'''
|
159
|
+
Gets a loaded key by its ID or raises `NoMatchingVaultKeyError` if no key matches the ID.
|
160
|
+
If multiple keys share the ID, the first in `keys` is returned.
|
161
|
+
'''
|
162
|
+
for key in self.keys:
|
163
|
+
if key.id == id:
|
164
|
+
return key
|
165
|
+
raise NoMatchingVaultKeyError(f"No matching key found for ID '{ id }' in { self }")
|
166
|
+
|
167
|
+
@staticmethod
|
168
|
+
def load_cli_secrets() -> list[VaultKey]:
|
169
|
+
'''
|
170
|
+
Tries to infer available Ansible vault secrets from the caller's present working directory, if it is an Ansible home.
|
171
|
+
Inferred secrets are converted into `VaultKey`s and returned.
|
172
|
+
Note that the inferral process may cause TTY prompts or other unwanted in- and output.
|
173
|
+
'''
|
174
|
+
# Ansible.DEFAULT_VAULT_IDENTITY_LIST is a list populated with vault IDs inferred from the PWD
|
175
|
+
# (i.e. has to be run in ANSIBLE_HOME, else the value is [])
|
176
|
+
secrets: list[tuple[str | None, VaultSecret]] = \
|
177
|
+
CLI.setup_vault_secrets(DataLoader(), Ansible.DEFAULT_VAULT_IDENTITY_LIST, auto_prompt=False) # type: ignore
|
178
|
+
return list(map(VaultKey.from_ansible_secret, secrets))
|
179
|
+
|
180
|
+
def __repr__(self) -> str:
|
181
|
+
return f"VaultKeyring({ ', '.join(map(lambda key: key.id, self.keys)) or 'no keys' })"
|
@@ -0,0 +1,254 @@
|
|
1
|
+
Metadata-Version: 2.4
|
2
|
+
Name: ansible-vars
|
3
|
+
Version: 1.0.0
|
4
|
+
Summary: Manage vaults and variable files for Ansible
|
5
|
+
Project-URL: Homepage, https://github.com/xorwow/ansible-vars
|
6
|
+
Project-URL: Issues, https://github.com/xorwow/ansible-vars/issues
|
7
|
+
Author-email: xorwow <pip@xorwow.de>
|
8
|
+
License-File: LICENSE
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
10
|
+
Classifier: Operating System :: OS Independent
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
12
|
+
Requires-Python: >=3.9
|
13
|
+
Requires-Dist: ansible
|
14
|
+
Requires-Dist: argcomplete
|
15
|
+
Requires-Dist: pygments
|
16
|
+
Requires-Dist: pyyaml
|
17
|
+
Requires-Dist: ruamel-yaml
|
18
|
+
Requires-Dist: ruamel-yaml-jinja2
|
19
|
+
Requires-Dist: termcolor
|
20
|
+
Requires-Dist: watchdog
|
21
|
+
Description-Content-Type: text/markdown
|
22
|
+
|
23
|
+
# ansible-vars
|
24
|
+
|
25
|
+
*Manage vaults and variable files for Ansible.*
|
26
|
+
|
27
|
+
## Introduction
|
28
|
+
|
29
|
+
This project was motivated by a need to have Ansible vaults readable to humans and external programs like `grep` without a manual decryption step, while keeping secret values in these vaults secure from prying eyes. Ansible actually supports keeping vaults plain-text and only encrypting individual string variables, but this feature is not widely known or used and editing such files is not supported by the `ansible-vault` tool.
|
30
|
+
|
31
|
+
`ansible-vars` allows you to do the same things `ansible-vault` does (and more!), but not just for fully encrypted vaults, but also plain variable files and vaults with hybrid encryption (encrypted variables and/or full encryption). This is significantly more complex, as `ansible-vault` can be agnostic to the contents of the file it en-/decrypts, while hybrid encryption requires full round-trip parsing of a vault's Jinja2 YAML code.
|
32
|
+
|
33
|
+
The main features are:
|
34
|
+
- Create and edit vaults and variable files with hybrid encryption support.
|
35
|
+
- Continuously sync a decrypted copy of your vault file(s) to a specified directory.
|
36
|
+
- Programatically change a vault's variables from Python or from your shell (experimental).
|
37
|
+
- Compare different versions of a vault and optionally log changes.
|
38
|
+
|
39
|
+
Many convenience features have been implemented, such as:
|
40
|
+
- Convert any of your old fully encrypted vaults to a hybrid vault with just one command.
|
41
|
+
- Automatically load vault secrets from the Ansible configuration.
|
42
|
+
- Smart search paths for vault files and directories.
|
43
|
+
- Full bash/zsh completion.
|
44
|
+
|
45
|
+
The extensive help function (`ansible-vars [command] -h`) will explain each feature in detail.
|
46
|
+
|
47
|
+
## Installation
|
48
|
+
|
49
|
+
You need to have a current version of Python installed. The CLI and library have been tested in Python 3.12, but will likely work with earlier versions as well.
|
50
|
+
|
51
|
+
### Using a [virtual environment](https://docs.python.org/3/library/venv.html)
|
52
|
+
|
53
|
+
```sh
|
54
|
+
# Create and activate virtual environment
|
55
|
+
python -m venv venv
|
56
|
+
source venv/bin/activate
|
57
|
+
# Install pip package
|
58
|
+
pip install ansible-vars
|
59
|
+
```
|
60
|
+
|
61
|
+
Note that the virtual environment must be active when using the command.
|
62
|
+
|
63
|
+
### Using [pipx](https://github.com/pypa/pipx)
|
64
|
+
|
65
|
+
```sh
|
66
|
+
# Install pipx
|
67
|
+
pip install pipx
|
68
|
+
# Install pip package globally using pipx
|
69
|
+
pipx install ansible-vars
|
70
|
+
```
|
71
|
+
|
72
|
+
### Shell completion
|
73
|
+
|
74
|
+
For `bash` and `zsh` users, shell completion for `ansible-vars` can be activated by adding this command to your shell RC file:
|
75
|
+
|
76
|
+
```sh
|
77
|
+
# Add to .bashrc/.zshrc in your user's home
|
78
|
+
# If you installed ansible-vars to a venv, the venv must be active when this command runs
|
79
|
+
eval "$(register-python-argcomplete ansible-vars)"
|
80
|
+
```
|
81
|
+
|
82
|
+
Alternatively, you can install the `argcomplete` completion system [globally](https://github.com/kislyuk/argcomplete#global-completion).
|
83
|
+
|
84
|
+
## Usage
|
85
|
+
|
86
|
+
The functions of `ansible-vars` are accessed by specifying a command as the first argument. You can quickly get relevant information by using the help function (`ansible-vars [command] -h`), optionally specifying the command you want more details about.
|
87
|
+
|
88
|
+
### Variable encryption
|
89
|
+
|
90
|
+
When editing a vault or variable file using `ansible-vars`, you can prefix any string value using the `!enc` tag to have it encrypted automatically:
|
91
|
+
|
92
|
+
```yaml
|
93
|
+
my_message: !enc this is a super secret message
|
94
|
+
```
|
95
|
+
|
96
|
+
Encrypted variables will be displayed using this tag when editing, making it easy to view, modify, or decrypt their value.
|
97
|
+
|
98
|
+
### Vault secrets
|
99
|
+
|
100
|
+
**TL;DR:** Run `ansible-vars` from your Ansible home to auto-detect configured secrets. Add a custom secret using `-k <identifier> <passphrase>`.
|
101
|
+
|
102
|
+
To use any functions of `ansible-vars` that require encrypting or decrypting data, you need to provide one or multiple vault secret(s). If you're in an Ansible home directory when running the command, it tries to auto-detect configured vault secrets by calling the Ansible CLI API. You can also specify your own secrets as pairs of identifier and passphrase using `--add-key|-k <identifier> <passphrase>`. The identifier can be anything you want, although it should ideally be unique. Consider using an environment variable or in-line command to retrieve the passphrase from a secure location.
|
103
|
+
|
104
|
+
By default, the first loaded key is used for all encryption tasks. Note that auto-detected keys are inserted into the application's keyring *after* your explicitly added ones, so the first key you add will usually be the encryption key. If you want to make sure a certain key is used, reference its identifier using `--encryption-key|-K <identifier>`.
|
105
|
+
|
106
|
+
You can disable automatic key detection by flagging `--no-encrypt-keys|-D`. Use `ansible-vars keyring` to view all available keys.
|
107
|
+
|
108
|
+
### Diff logging
|
109
|
+
|
110
|
+
**TL;DR:** You can use `-l <log directory>` to log changes to edited vaults to a vault-encrypted log file.
|
111
|
+
|
112
|
+
You can automatically log any changes performed to a vault by the commands `create`, `edit`, `convert`, `set`, and `del` using the `--log|-l <log path>` or `--log-plain|-L <log path>` flags. The changes will be saved as a YAML-compatible diff with some additional metadata. When using `--log`, the entire log file is encrypted as a vault using your encryption key. This is important as the diffs contain the plain values of encrypted variables. When using `--log-plain` to skip encryption, make sure you're only editing fully plaintext variable files to avoid leaking secrets.
|
113
|
+
|
114
|
+
As you cannot mix different encryption keys and/or plain logging in the same log file, consider either using a dedicated logging key (`--logging-key <identifier>`) or specifying a directory as the log path (in which case `ansible-vars` automatically chooses a filename based on the used encryption key's identifier) if you frequently switch between keys.
|
115
|
+
|
116
|
+
### Examples
|
117
|
+
|
118
|
+
```sh
|
119
|
+
# Create the variable file `./host_vars/my_host/main.yml` without full encryption and open it for editing
|
120
|
+
ansible-vars create --make-parents --plain host_vars/my_host/main.yml
|
121
|
+
# Short version (see `Tips` section to learn about vault search paths)
|
122
|
+
ansible-vars create -mp h:my_host
|
123
|
+
|
124
|
+
# Decrypt the vault file `./config/logging.yml` in-place
|
125
|
+
ansible-vars decrypt file config/logging.yml
|
126
|
+
|
127
|
+
# Recursively search vaults and variable files in `./host_vars` for left-over TODOs
|
128
|
+
ansible-vars grep '# TODO' h:
|
129
|
+
|
130
|
+
# Print a tree structure showing the differences between two versions of the vault `./vars/passwords.yml`
|
131
|
+
ansible-vars changes v:passwords.yml.old v:passwords.yml
|
132
|
+
|
133
|
+
# Create decrypted mirrors of the directories `./host_vars`, `./group_vars`, and `./vars` in `/tmp/decrypted`
|
134
|
+
ansible-vars file-daemon /tmp/decrypted
|
135
|
+
|
136
|
+
# Get the decrypted value of `<vault root>['my_key'][4]['133']` in `./group_vars/database_hosts/main.yml` as JSON
|
137
|
+
ansible-vars get --json g:database_hosts 'my_key' '[4]' '133'
|
138
|
+
```
|
139
|
+
|
140
|
+
### Tips
|
141
|
+
|
142
|
+
- When a command supports a `--json` flag, the command's help (`ansible-vars <command> -h`) will define the returned structure.
|
143
|
+
- The directories `host_vars`, `group_vars`, and `vars` are common vault locations. When in their parent directory, you can use the prefixes `h:`, `g:`, and `v:` in any vault path you specify, followed by a path relative to them. Wherever a directory is not expected as a path, supplying a directory path will also append a `main.yml` to the path automatically. In summary, this lets you type `h:my_host` when you actually mean `./host_vars/my_host/main.yml`. Shell completion for these prefixed paths is provided.
|
144
|
+
- These three directories are also default sources for the `file-daemon` command.
|
145
|
+
- When referencing vault traversal keys, you can specify numbers to access lists and number-indexed dictionaries. However, just specifying `2` as a key segment will resolve into the string `'2'`. Instead, you should write `[2]` to mark it as a number index. If you need to specify the string `'[2]'` for some reason, you can escape it by adding another set of brackets (and so on).
|
146
|
+
|
147
|
+
### Commands
|
148
|
+
|
149
|
+
A brief overview of the available commands. You can change a lot of the default behavior described here using command flags. Use the command help to get additional information and available flags (`ansible-vars <command> -h`).
|
150
|
+
|
151
|
+
#### keyring
|
152
|
+
|
153
|
+
Displays the loaded vault secrets, including any auto-detected ones, along with their passphrase. Supports JSON output.
|
154
|
+
|
155
|
+
#### create
|
156
|
+
|
157
|
+
Creates a new vault or variable file. By default, full encryption is enabled to avoid accidentally leaking secrets. Use the `--plain|-p` flag to create a file with hybrid encryption, i.e. completely plain or with individually encrypted variables. After creating the file, it will open in edit mode (see `edit` command below).
|
158
|
+
|
159
|
+
#### edit
|
160
|
+
|
161
|
+
Opens a vault or variable file in the configured editor (`EDITOR` environment variable or passed using the `--edit-command|-e` flag) in decrypted form. Here, you can en-/decrypt, add, remove, and change variables. After saving the file and closing the editor, the file will be re-parsed and re-encrypted.
|
162
|
+
|
163
|
+
*Note: When choosing a custom edit command, make sure the command exits after the file is saved, as `ansible-vars` will read a finished command as the cue to start re-parsing the file contents.*
|
164
|
+
|
165
|
+
#### view
|
166
|
+
|
167
|
+
Prints the contents of a vault or variable file to the terminal, fully decrypted without any encryption markers. Supports JSON output.
|
168
|
+
|
169
|
+
#### info
|
170
|
+
|
171
|
+
Shows the amounts of encrypted and decrypted variables in a vault file. Supports JSON output.
|
172
|
+
|
173
|
+
#### encrypt, decrypt, is-encrypted
|
174
|
+
|
175
|
+
En-/Decrypts or checks the encryption status of a file or string value. No loaded secrets are required for `is-encrypted`.
|
176
|
+
|
177
|
+
#### convert
|
178
|
+
|
179
|
+
Convenience function to convert between fully encrypted vaults and hybrid vaults. Useful if you wish to convert your "legacy" fully encrypted vaults to plain files with all string values individually encrypted. Works both ways.
|
180
|
+
|
181
|
+
#### grep
|
182
|
+
|
183
|
+
Searches one or multiple vault(s) for matches on a pattern, either in their full text or limited to keys/values. Supports recursively searching directories and JSON output. Note that non-variable/-vault files are not included in the search.
|
184
|
+
|
185
|
+
#### diff
|
186
|
+
|
187
|
+
Compares two vaults or variable files and prints the line diff.
|
188
|
+
|
189
|
+
#### changes
|
190
|
+
|
191
|
+
Compares two vaults or variable files and prints a tree structure showing differences between their variables. Supports JSON output.
|
192
|
+
|
193
|
+
#### file-daemon
|
194
|
+
|
195
|
+
Starts a daemon which mirrors the decrypted contents of one or multiple vault or variable file(s)/directories to a target directory. By default, this includes the directories `./host_vars`, `./group_vars`, and `./vars`. Changes to the source files are reflected in the decrypted targets. Changes to the target files are ignored.
|
196
|
+
|
197
|
+
#### get
|
198
|
+
|
199
|
+
Displays the (decrypted) value of a specified key in a vault or variable file. Supports dictionary and list traversal, and JSON output.
|
200
|
+
|
201
|
+
#### set, del (experimental)
|
202
|
+
|
203
|
+
Creates, updates, or deletes a key-value pair from a vault or variable file. When setting a value, you may provide a YAML string which will be parsed into the corresponding objects. Note that these are experimental features, as the current parser has difficulty preserving the metadata for programmatic variable changes. Comments and Jinja2 blocks between the affected key and the next key in the file may be lost.
|
204
|
+
|
205
|
+
### Python library
|
206
|
+
|
207
|
+
When using `ansible-vars` as a library, these are the relevant modules.
|
208
|
+
|
209
|
+
#### vault module
|
210
|
+
|
211
|
+
Contains the classes `Vault` and `VaultFile`. A `Vault` is initialized using the contents of a vault or variable file, while `VaultFile` wraps around a `Vault` instance and manages reading from and writing to a file directly. These are the main classes you'll likely use, as they contain the means of loading, manipulating and exporting vault and variable data. Both can also be initialized using an 'editable' (the output of `<vault>.as_editable()`, contains encryption markers and an optional explanatory comment header). A `vault_crypt.VaultKeyring` is required for en-/decryption operations.
|
212
|
+
|
213
|
+
`EncryptedVar` represents an encrypted value. The stored cipher can be decrypted using a `vault_crypt.VaultKey(ring)`. Will be dumped as `!vault`-tagged values on exporting.
|
214
|
+
|
215
|
+
`ProtoEncryptedVar` is used for parsing, as the `!enc` tag parses into such a proto-var and is then converted to an `EncryptedVar`, and vice-versa for exporting.
|
216
|
+
|
217
|
+
#### vault_crypt module
|
218
|
+
|
219
|
+
The `VaultKey` class represents a single vault secret, comprised of an identifier and an `ansible.parsing.vault.VaultSecret`. Can be initialized using a plain passphrase instead of a `VaultSecret` as well.
|
220
|
+
|
221
|
+
The `VaultKeyring` combines a collection of `VaultKey`s. It supports auto-detection of any secrets available in the present working directory using the `ansible.cli` module, appending them to the `<keyring>.keys` collection. While all keys are tried in order for decryption operations, only one key can be used for encrypting data. This key is usually the first key in the `<keyring>.keys` collection, unless explicitly specified otherwise using `<keyring>.default_encryption_key` or passing a key to the `<keyring>.encrypt()` method.
|
222
|
+
|
223
|
+
#### util module
|
224
|
+
|
225
|
+
The `DiffLogger` with its wrapper `DiffFileLogger` generate log entries for changes to a vault and can save them to an encrypted or plain log file. A method decorator (`@<file logger>.log_changes(<vault used in wrapped method>)`) is available for your convenience.
|
226
|
+
|
227
|
+
The `VaultDaemon` syncs changes from a source file or directory to a target using the `watchdog` library, decrypting any vaults encountered on the way.
|
228
|
+
|
229
|
+
#### constants & errors modules
|
230
|
+
|
231
|
+
Custom types and exceptions, and static values. Mostly useful for type hints.
|
232
|
+
|
233
|
+
## Known issues and limitations
|
234
|
+
|
235
|
+
- YAML round-trip parser
|
236
|
+
- Trailing comments and Jinja2 blocks may be misaligned and a trailing newline may be inserted/removed when switching between folded (`|`, `>`) and non-foldes values.
|
237
|
+
- The `set` and `del` commands may remove trailing comments and Jinja2 blocks.
|
238
|
+
- Explicit start/end markers (`---`, `...`) are not preserved.
|
239
|
+
- Supports lists, dictionaries, and scalar values.
|
240
|
+
- Does not support custom YAML tags (`!tag`).
|
241
|
+
- Ansible
|
242
|
+
- Ansible only directly supports encrypted string values (although you can work around this with the `from_yaml` filter).
|
243
|
+
- Ansible-encrypted strings must include a newline between the envelope and the cipher.
|
244
|
+
- Ansible vault and variable file roots must be a dictionary.
|
245
|
+
- `grep` command
|
246
|
+
- Will ignore files which cannot be parsed as an Ansible YAML file.
|
247
|
+
- `file-daemon` command
|
248
|
+
- Changes to file metadata (permissions, ...) are not mirrored.
|
249
|
+
|
250
|
+
## Extension plans
|
251
|
+
|
252
|
+
- I'm debating creating my own Jinja2 YAML round-trip parser to alleviate the metadata preservation issues of the current parser.
|
253
|
+
- I may add an Ansible action plugin for updating vault variables directly from an Ansible task (useful e.g. for automatically storing passwords that are set to a random value by Ansible). I am currently using a small script for this task.
|
254
|
+
- I want to create `ansible-vars` system packages for common repositories.
|
@@ -0,0 +1,12 @@
|
|
1
|
+
ansible_vars/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
ansible_vars/cli.py,sha256=-Ig1_oidQRmPMPb3FrALWILxhSS8SOaGy9mZm-0PFEI,55338
|
3
|
+
ansible_vars/constants.py,sha256=Nd3sIuSoOvyfUfHfnsnJBDGMW7eNzbMm1NAvEQio9hE,1624
|
4
|
+
ansible_vars/errors.py,sha256=VnViEBMR3rIeioMj460DgdBA5S5FYiDObaDDhG2FMBs,857
|
5
|
+
ansible_vars/util.py,sha256=BzS7n3UzaKqVZ3W78HVkJtdVCYofprqQDtU8wYH1d0Q,21325
|
6
|
+
ansible_vars/vault.py,sha256=_dp1K5_UAFGgcg6iO4on4-L_BaJO2cHP6My3EU6enA4,45593
|
7
|
+
ansible_vars/vault_crypt.py,sha256=JcFc6dTZ6EqhKXv_C5ofggTpBK8hWG3ZwrBrDNYcEIg,9501
|
8
|
+
ansible_vars-1.0.0.dist-info/METADATA,sha256=kHUaYax79kV2Di2_FNy0890sV_m3UeZq7HRknaI8o58,15689
|
9
|
+
ansible_vars-1.0.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
+
ansible_vars-1.0.0.dist-info/entry_points.txt,sha256=RrhkEH0MbfRzflguVrfYfthsFC5V2fkFnizUG3uHMtQ,55
|
11
|
+
ansible_vars-1.0.0.dist-info/licenses/LICENSE,sha256=ocyJHLG5wD12qB4uam2pqWTHIJmzloiyNyTex6Q2DKo,1062
|
12
|
+
ansible_vars-1.0.0.dist-info/RECORD,,
|
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2025 xorwow
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|