ansible-vars 1.0.7__tar.gz → 1.0.9__tar.gz
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-1.0.7 → ansible_vars-1.0.9}/PKG-INFO +13 -2
- {ansible_vars-1.0.7 → ansible_vars-1.0.9}/README.md +12 -1
- {ansible_vars-1.0.7 → ansible_vars-1.0.9}/pyproject.toml +1 -1
- {ansible_vars-1.0.7 → ansible_vars-1.0.9}/src/ansible_vars/cli.py +38 -2
- {ansible_vars-1.0.7 → ansible_vars-1.0.9}/src/ansible_vars/util.py +2 -2
- {ansible_vars-1.0.7 → ansible_vars-1.0.9}/src/ansible_vars/vault_crypt.py +12 -8
- {ansible_vars-1.0.7 → ansible_vars-1.0.9}/.gitignore +0 -0
- {ansible_vars-1.0.7 → ansible_vars-1.0.9}/LICENSE +0 -0
- {ansible_vars-1.0.7 → ansible_vars-1.0.9}/src/ansible_vars/__init__.py +0 -0
- {ansible_vars-1.0.7 → ansible_vars-1.0.9}/src/ansible_vars/constants.py +0 -0
- {ansible_vars-1.0.7 → ansible_vars-1.0.9}/src/ansible_vars/errors.py +0 -0
- {ansible_vars-1.0.7 → ansible_vars-1.0.9}/src/ansible_vars/vault.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ansible-vars
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.9
|
4
4
|
Summary: Manage vaults and variable files for Ansible
|
5
5
|
Project-URL: Homepage, https://github.com/xorwow/ansible-vars
|
6
6
|
Project-URL: Issues, https://github.com/xorwow/ansible-vars/issues
|
@@ -105,6 +105,10 @@ By default, the first loaded key is used for all encryption tasks. Note that aut
|
|
105
105
|
|
106
106
|
You can disable automatic key detection by flagging `--no-encrypt-keys|-D`. Use `ansible-vars keyring` to view all available keys.
|
107
107
|
|
108
|
+
#### Encryption salts
|
109
|
+
|
110
|
+
Each time you edit a vault or otherwise encrypt a value, a randomly generated salt is used so even identical plain values will result in unique ciphers. However, this also means that each time a single encrypted variable is edited, all other ciphers in the vault will change as well. You can avoid this by passing a fixed salt via `--fixed-salt|-S <salt>`. Note that it should be at least 32 characters long and sufficiently random for Ansible's AES-256 encryption, and that you won't benefit from unique ciphers for identical plaintexts anymore.
|
111
|
+
|
108
112
|
### Diff logging
|
109
113
|
|
110
114
|
**TL;DR:** You can use `-l <log directory>` to log changes to edited vaults to a vault-encrypted log file.
|
@@ -174,6 +178,10 @@ Shows the amounts of encrypted and decrypted variables in a vault file. Supports
|
|
174
178
|
|
175
179
|
En-/Decrypts or checks the encryption status of a file or string value. Note that only full file encryption is considered in file mode, a hybrid vault with individually encrypted variables will be counted as plain.
|
176
180
|
|
181
|
+
### rekey
|
182
|
+
|
183
|
+
Re-encrypts a vault file with a different encryption key and/or salt. The key specified in the global `--encryption-key|-K <identifier>` flag is used for encryption, along with an optional fixed salt set via the global `--fixed-salt|-S <salt>` flag.
|
184
|
+
|
177
185
|
#### convert
|
178
186
|
|
179
187
|
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.
|
@@ -220,6 +228,10 @@ Set the tempfile/staging root as you would with `-T <path>`.
|
|
220
228
|
|
221
229
|
Invert the default creation mode for files: If unset or `no`, files are created with full encryption unless specified otherwise via the `--plain|-p` flag. This behavior mirrors that of `ansible-vault`. When set to `yes`, the behavior and flag are inverted as files are created without encryption by default unless specified otherwise via the `--no-plain|-P` flag.
|
222
230
|
|
231
|
+
#### AV_SALT
|
232
|
+
|
233
|
+
Set a fixed salt as you would with `-S <salt>`.
|
234
|
+
|
223
235
|
### Python library
|
224
236
|
|
225
237
|
When using `ansible-vars` as a library, import any of these modules from the `ansible_vars` module.
|
@@ -270,7 +282,6 @@ When editing a file or creating a daemon, decrypted vaults are written to disk t
|
|
270
282
|
- Changes to file metadata (permissions, ...) are not mirrored.
|
271
283
|
- `ansible-vars` does not support files which are not (Jinja2) YAML dictionaries, except for limited support in these commands:
|
272
284
|
- `edit`, `view` (without `--json` support), `encrypt`, `decrypt`, `is-encrypted`
|
273
|
-
- When a vault is modified, all of its ciphers will change due to new salts, even if only one encrypted variable has been changed.
|
274
285
|
|
275
286
|
## Extension plans
|
276
287
|
|
@@ -83,6 +83,10 @@ By default, the first loaded key is used for all encryption tasks. Note that aut
|
|
83
83
|
|
84
84
|
You can disable automatic key detection by flagging `--no-encrypt-keys|-D`. Use `ansible-vars keyring` to view all available keys.
|
85
85
|
|
86
|
+
#### Encryption salts
|
87
|
+
|
88
|
+
Each time you edit a vault or otherwise encrypt a value, a randomly generated salt is used so even identical plain values will result in unique ciphers. However, this also means that each time a single encrypted variable is edited, all other ciphers in the vault will change as well. You can avoid this by passing a fixed salt via `--fixed-salt|-S <salt>`. Note that it should be at least 32 characters long and sufficiently random for Ansible's AES-256 encryption, and that you won't benefit from unique ciphers for identical plaintexts anymore.
|
89
|
+
|
86
90
|
### Diff logging
|
87
91
|
|
88
92
|
**TL;DR:** You can use `-l <log directory>` to log changes to edited vaults to a vault-encrypted log file.
|
@@ -152,6 +156,10 @@ Shows the amounts of encrypted and decrypted variables in a vault file. Supports
|
|
152
156
|
|
153
157
|
En-/Decrypts or checks the encryption status of a file or string value. Note that only full file encryption is considered in file mode, a hybrid vault with individually encrypted variables will be counted as plain.
|
154
158
|
|
159
|
+
### rekey
|
160
|
+
|
161
|
+
Re-encrypts a vault file with a different encryption key and/or salt. The key specified in the global `--encryption-key|-K <identifier>` flag is used for encryption, along with an optional fixed salt set via the global `--fixed-salt|-S <salt>` flag.
|
162
|
+
|
155
163
|
#### convert
|
156
164
|
|
157
165
|
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.
|
@@ -198,6 +206,10 @@ Set the tempfile/staging root as you would with `-T <path>`.
|
|
198
206
|
|
199
207
|
Invert the default creation mode for files: If unset or `no`, files are created with full encryption unless specified otherwise via the `--plain|-p` flag. This behavior mirrors that of `ansible-vault`. When set to `yes`, the behavior and flag are inverted as files are created without encryption by default unless specified otherwise via the `--no-plain|-P` flag.
|
200
208
|
|
209
|
+
#### AV_SALT
|
210
|
+
|
211
|
+
Set a fixed salt as you would with `-S <salt>`.
|
212
|
+
|
201
213
|
### Python library
|
202
214
|
|
203
215
|
When using `ansible-vars` as a library, import any of these modules from the `ansible_vars` module.
|
@@ -248,7 +260,6 @@ When editing a file or creating a daemon, decrypted vaults are written to disk t
|
|
248
260
|
- Changes to file metadata (permissions, ...) are not mirrored.
|
249
261
|
- `ansible-vars` does not support files which are not (Jinja2) YAML dictionaries, except for limited support in these commands:
|
250
262
|
- `edit`, `view` (without `--json` support), `encrypt`, `decrypt`, `is-encrypted`
|
251
|
-
- When a vault is modified, all of its ciphers will change due to new salts, even if only one encrypted variable has been changed.
|
252
263
|
|
253
264
|
## Extension plans
|
254
265
|
|
@@ -84,6 +84,10 @@ Specify vault keys to load for en-/decryption. Not required for vars files with
|
|
84
84
|
A key is a combination of an identifier (can be a vault ID or anything else, ideally unique) and a passphrase.
|
85
85
|
By default, available keys are auto-detected if your current directory contains an Ansible config and appended to the ones you supplied.
|
86
86
|
If no explicit encryption key is specified, the first supplied/available key is used.
|
87
|
+
|
88
|
+
[!] When editing a hybrid vault file, changing even a single variable will change all variables' ciphers, as salts are generated randomly.
|
89
|
+
You can prevent this by passing a fixed salt via `-S <salt>`, which will result in any plaintext always resolving to the same cipher.
|
90
|
+
For Ansible's AES-256, a length of at least 32 characters is recommended.
|
87
91
|
''',
|
88
92
|
'log_args': '''
|
89
93
|
Log a diff of any vault changes performed with this program to an encrypted or plain logfile, creating it if necessary.
|
@@ -130,6 +134,10 @@ Decrypt a string and return it or fully decrypt a file in-place. Uses the first
|
|
130
134
|
''',
|
131
135
|
'cmd_is_enc': '''
|
132
136
|
Check if a string or file is (fully) vault-encrypted.
|
137
|
+
''',
|
138
|
+
'cmd_rekey': '''
|
139
|
+
Update a vault's ciphers with a new encryption key and/or salt.
|
140
|
+
The key referenced by `--encryption-key|-K <identifier>` and/or the salt set by `--fixed-salt|-S <salt>` are used for re-encryption.
|
133
141
|
''',
|
134
142
|
'cmd_convert': '''
|
135
143
|
Switch a file between full outer and full inner encryption for convenient migrating between encryption schemes.
|
@@ -198,6 +206,7 @@ DEFAULT_EDITOR: str = os.environ.get('EDITOR', 'notepad.exe' if os.name == 'nt'
|
|
198
206
|
DEFAULT_COLOR_MODE: str = os.environ.get('AV_COLOR_MODE', '256' if sys.stdout.isatty() else 'none')
|
199
207
|
DEFAULT_TEMP_DIR: str = os.environ.get('AV_TEMP_DIR', gettempdir())
|
200
208
|
DEFAULT_CREATE_PLAIN: bool = os.environ.get('AV_CREATE_PLAIN', 'no').lower() in [ 'yes', 'y', 'true', 't', '1' ]
|
209
|
+
DEFAULT_SALT: str | None = os.environ.get('AV_SALT', None)
|
201
210
|
|
202
211
|
args: ArgumentParser = ArgumentParser(
|
203
212
|
prog = 'ansible-vars',
|
@@ -243,6 +252,10 @@ key_args.add_argument(
|
|
243
252
|
)
|
244
253
|
key_args.add_argument('--no-detect-keys', '-D', action='store_false', dest='detect_keys', help='disable automatic key detection')
|
245
254
|
key_args.add_argument('--encryption-key', '-K', type=str, metavar='<identifier>', help='which of the loaded keys to use for encryption')
|
255
|
+
key_args.add_argument(
|
256
|
+
'--fixed-salt', '-S', type=str, metavar='<salt>', default=DEFAULT_SALT,
|
257
|
+
help='a fixed salt to use for encryption (should be 32+ chars!)'
|
258
|
+
)
|
246
259
|
|
247
260
|
log_args = args.add_argument_group('logging vault changes', description=HELP['log_args'])
|
248
261
|
log_mutex = log_args.add_mutually_exclusive_group()
|
@@ -329,6 +342,13 @@ cmd_is_enc.add_argument('target', type=str, metavar='<vault path | string>', hel
|
|
329
342
|
.completer = _prefixed_path_completer # type: ignore
|
330
343
|
cmd_is_enc.add_argument('--quiet', '-q', action='store_true', help='no output, only set the rc to 0 if encrypted or 100 if unencrypted')
|
331
344
|
|
345
|
+
cmd_rekey = commands.add_parser(
|
346
|
+
'rekey', help='update a vault\'s encryption key (from -K) and/or salt (from -S)', description=HELP['cmd_rekey'],
|
347
|
+
formatter_class=RawDescriptionHelpFormatter
|
348
|
+
)
|
349
|
+
cmd_rekey.add_argument('vault_path', type=str, metavar='<vault path>', help='path of vault to rekey') \
|
350
|
+
.completer = _prefixed_path_completer # type: ignore
|
351
|
+
|
332
352
|
cmd_convert = commands.add_parser(
|
333
353
|
'convert', help='switch vault between outer (file) and inner (vars) encryption', description=HELP['cmd_convert'],
|
334
354
|
formatter_class=RawDescriptionHelpFormatter
|
@@ -565,7 +585,7 @@ sys.excepthook = _exc_hook
|
|
565
585
|
# Load vault keys
|
566
586
|
|
567
587
|
_explicit_keys: list[VaultKey] = [ VaultKey(passphrase, vault_id=id) for id, passphrase in config.keys ]
|
568
|
-
keyring = VaultKeyring(_explicit_keys.copy(), detect_available_keys=config.detect_keys)
|
588
|
+
keyring = VaultKeyring(_explicit_keys.copy(), detect_available_keys=config.detect_keys, default_salt=config.fixed_salt)
|
569
589
|
|
570
590
|
if config.encryption_key:
|
571
591
|
keyring.default_encryption_key = keyring.key_by_id(config.encryption_key)
|
@@ -575,6 +595,8 @@ try:
|
|
575
595
|
debug(f"Encryption key: { keyring.encryption_key.id }")
|
576
596
|
except:
|
577
597
|
debug('Encryption key: unavailable')
|
598
|
+
if config.fixed_salt:
|
599
|
+
debug(f"Using fixed encryption salt: { config.fixed_salt }")
|
578
600
|
|
579
601
|
# Set up logging
|
580
602
|
|
@@ -690,7 +712,6 @@ if config.command in [ 'create', 'edit' ]:
|
|
690
712
|
def _find_new_plain_vars(path: tuple[Hashable, ...], value: Any) -> Any:
|
691
713
|
if path != ( SENTINEL_KEY, ) and type(value) is not EncryptedVar:
|
692
714
|
if (old_value := vault.get(path, default=Unset)) != value:
|
693
|
-
std_print(path[-1], type(old_value), type(value))
|
694
715
|
if type(old_value) is EncryptedVar:
|
695
716
|
decrypted_vars.append(path)
|
696
717
|
else:
|
@@ -822,6 +843,21 @@ if config.command in [ 'encrypt', 'decrypt', 'is-encrypted' ]:
|
|
822
843
|
else:
|
823
844
|
print(f"Value is { 'encrypted' if is_encrypted else 'plain' }.", Color.GOOD if is_encrypted else Color.MEH)
|
824
845
|
|
846
|
+
# Rekey command
|
847
|
+
|
848
|
+
if config.command == 'rekey':
|
849
|
+
vault_path: str = resolve_vault_path(config.vault_path)
|
850
|
+
if not config.encryption_key:
|
851
|
+
print(f"No explicit encryption key specified, falling back to '{ keyring.encryption_key.id }'", Color.MEH)
|
852
|
+
# Since ciphers are usually not changed from load to save, we force re-encryption by loading from an editable
|
853
|
+
vault = VaultFile(vault_path, keyring=keyring)
|
854
|
+
vault = VaultFile.from_editable(vault, vault.as_editable())
|
855
|
+
vault.save()
|
856
|
+
print(
|
857
|
+
f"Re-encrypted vault with key '{ keyring.encryption_key.id }' and a { 'fixed' if config.fixed_salt else 'random' } salt",
|
858
|
+
Color.GOOD
|
859
|
+
)
|
860
|
+
|
825
861
|
# Convert command
|
826
862
|
|
827
863
|
if config.command == 'convert':
|
@@ -338,7 +338,7 @@ class VaultDaemon(FileSystemEventHandler):
|
|
338
338
|
content: str = src.read()
|
339
339
|
with open(self.target_file, 'w') as tgt:
|
340
340
|
try:
|
341
|
-
tgt.write(Vault(content, keyring=self.keyring).
|
341
|
+
tgt.write(Vault(content, keyring=self.keyring).as_plain())
|
342
342
|
self.debug(f"Created/Updated file { self.target_file } from vault contents")
|
343
343
|
except:
|
344
344
|
tgt.write(content)
|
@@ -373,7 +373,7 @@ class VaultDaemon(FileSystemEventHandler):
|
|
373
373
|
content: str = src.read()
|
374
374
|
with open(target_path, 'w') as tgt:
|
375
375
|
try:
|
376
|
-
tgt.write(Vault(content, keyring=self.keyring).
|
376
|
+
tgt.write(Vault(content, keyring=self.keyring).as_plain())
|
377
377
|
self.debug(f"Created/Updated file { target_path } from vault contents")
|
378
378
|
except:
|
379
379
|
tgt.write(content)
|
@@ -54,12 +54,12 @@ class VaultKey():
|
|
54
54
|
'''
|
55
55
|
return VaultLib.is_encrypted(VaultKey._strip_vault_tag(test_me))
|
56
56
|
|
57
|
-
def encrypt(self, plain: str) -> str:
|
58
|
-
'''Encrypts a string using this `VaultKey`'s secret.'''
|
57
|
+
def encrypt(self, plain: str, salt: str | None = None) -> str:
|
58
|
+
'''Encrypts a string using this `VaultKey`'s secret. You can specify an optional fixed salt (ideally 32+ chars).'''
|
59
59
|
# Pass our secret directly to the encrypt call to skip expensive secret matching
|
60
60
|
# Beware: the encrypt function takes a `secret`, but means just the VaultSecret and not a tuple of (vault_id, VaultSecret)
|
61
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()
|
62
|
+
return self._vaultlib.encrypt(plain, secret=self.secret, vault_id=self.id, salt=salt).decode('utf-8').strip()
|
63
63
|
|
64
64
|
def decrypt(self, vault_cipher: str) -> str:
|
65
65
|
'''
|
@@ -96,6 +96,7 @@ class VaultKeyring():
|
|
96
96
|
self,
|
97
97
|
keys: list[VaultKey] | None = None,
|
98
98
|
default_encryption_key: VaultKey | None = None,
|
99
|
+
default_salt: str | None = None,
|
99
100
|
detect_available_keys: bool = True
|
100
101
|
) -> None:
|
101
102
|
'''
|
@@ -107,9 +108,13 @@ class VaultKeyring():
|
|
107
108
|
|
108
109
|
When encrypting data, you can specify an explicit `VaultKey` to use. If none is specified, `default_encryption_key` is used.
|
109
110
|
If no explicit or default keys are available, the first key of the `keys` parameter is used.
|
111
|
+
|
112
|
+
You can also set a fixed salt to use for encryption tasks, either passing it to the `encrypt` function directly
|
113
|
+
or as a `default_salt` here. A length of at least 32 chars is recommended for Ansible's AES-256.
|
110
114
|
'''
|
111
115
|
self.keys: list[VaultKey] = keys or []
|
112
116
|
self.default_encryption_key: VaultKey | None = default_encryption_key
|
117
|
+
self.default_salt: str | None = default_salt
|
113
118
|
if detect_available_keys:
|
114
119
|
self.keys.extend(VaultKeyring.load_cli_secrets())
|
115
120
|
|
@@ -124,16 +129,15 @@ class VaultKeyring():
|
|
124
129
|
raise NoVaultKeysError('No vault keys available for encryption')
|
125
130
|
return self.default_encryption_key or self.keys[0]
|
126
131
|
|
127
|
-
def encrypt(self, plain: str, key: VaultKey | None = None) -> str:
|
132
|
+
def encrypt(self, plain: str, key: VaultKey | None = None, salt: str | None = None) -> str:
|
128
133
|
'''
|
129
134
|
Encrypts the given vault data using the supplied `VaultKey`.
|
130
135
|
If no key is supplied, the `VaultKeyring`'s `default_encryption_key` is used.
|
131
136
|
If that key is also unset, the first key of the `VaultKeyring`'s `keys` is used.
|
137
|
+
You can specify an optional fixed salt for encrypting (ideally 32+ chars).
|
132
138
|
'''
|
133
139
|
# If no key is provided, use the default encryption key or first key in `keys`
|
134
|
-
|
135
|
-
key = self.encryption_key
|
136
|
-
return key.encrypt(plain)
|
140
|
+
return (key or self.encryption_key).encrypt(plain, salt=(salt or self.default_salt))
|
137
141
|
|
138
142
|
def decrypt(self, vault_cipher: str, key: VaultKey | None = None) -> str:
|
139
143
|
'''
|
@@ -143,7 +147,7 @@ class VaultKeyring():
|
|
143
147
|
|
144
148
|
Expects a cipher with optional YAML tag preamble (`!vault | $ANSIBLE_VAULT;<options>\\n<cipher>`).
|
145
149
|
'''
|
146
|
-
if not key
|
150
|
+
if not (key or self.keys):
|
147
151
|
raise NoVaultKeysError('No vault keys available for decryption')
|
148
152
|
if key:
|
149
153
|
return key.decrypt(vault_cipher)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|