ansible-vars 1.0.7__py3-none-any.whl → 1.0.8__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/cli.py +12 -2
- ansible_vars/util.py +2 -2
- ansible_vars/vault_crypt.py +12 -8
- {ansible_vars-1.0.7.dist-info → ansible_vars-1.0.8.dist-info}/METADATA +9 -2
- ansible_vars-1.0.8.dist-info/RECORD +12 -0
- ansible_vars-1.0.7.dist-info/RECORD +0 -12
- {ansible_vars-1.0.7.dist-info → ansible_vars-1.0.8.dist-info}/WHEEL +0 -0
- {ansible_vars-1.0.7.dist-info → ansible_vars-1.0.8.dist-info}/entry_points.txt +0 -0
- {ansible_vars-1.0.7.dist-info → ansible_vars-1.0.8.dist-info}/licenses/LICENSE +0 -0
ansible_vars/cli.py
CHANGED
@@ -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.
|
@@ -198,6 +202,7 @@ DEFAULT_EDITOR: str = os.environ.get('EDITOR', 'notepad.exe' if os.name == 'nt'
|
|
198
202
|
DEFAULT_COLOR_MODE: str = os.environ.get('AV_COLOR_MODE', '256' if sys.stdout.isatty() else 'none')
|
199
203
|
DEFAULT_TEMP_DIR: str = os.environ.get('AV_TEMP_DIR', gettempdir())
|
200
204
|
DEFAULT_CREATE_PLAIN: bool = os.environ.get('AV_CREATE_PLAIN', 'no').lower() in [ 'yes', 'y', 'true', 't', '1' ]
|
205
|
+
DEFAULT_SALT: str | None = os.environ.get('AV_SALT', None)
|
201
206
|
|
202
207
|
args: ArgumentParser = ArgumentParser(
|
203
208
|
prog = 'ansible-vars',
|
@@ -243,6 +248,10 @@ key_args.add_argument(
|
|
243
248
|
)
|
244
249
|
key_args.add_argument('--no-detect-keys', '-D', action='store_false', dest='detect_keys', help='disable automatic key detection')
|
245
250
|
key_args.add_argument('--encryption-key', '-K', type=str, metavar='<identifier>', help='which of the loaded keys to use for encryption')
|
251
|
+
key_args.add_argument(
|
252
|
+
'--fixed-salt', '-S', type=str, metavar='<salt>', default=DEFAULT_SALT,
|
253
|
+
help='a fixed salt to use for encryption (should be 32+ chars!)'
|
254
|
+
)
|
246
255
|
|
247
256
|
log_args = args.add_argument_group('logging vault changes', description=HELP['log_args'])
|
248
257
|
log_mutex = log_args.add_mutually_exclusive_group()
|
@@ -565,7 +574,7 @@ sys.excepthook = _exc_hook
|
|
565
574
|
# Load vault keys
|
566
575
|
|
567
576
|
_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)
|
577
|
+
keyring = VaultKeyring(_explicit_keys.copy(), detect_available_keys=config.detect_keys, default_salt=config.fixed_salt)
|
569
578
|
|
570
579
|
if config.encryption_key:
|
571
580
|
keyring.default_encryption_key = keyring.key_by_id(config.encryption_key)
|
@@ -575,6 +584,8 @@ try:
|
|
575
584
|
debug(f"Encryption key: { keyring.encryption_key.id }")
|
576
585
|
except:
|
577
586
|
debug('Encryption key: unavailable')
|
587
|
+
if config.fixed_salt:
|
588
|
+
debug(f"Using fixed encryption salt: { config.fixed_salt }")
|
578
589
|
|
579
590
|
# Set up logging
|
580
591
|
|
@@ -690,7 +701,6 @@ if config.command in [ 'create', 'edit' ]:
|
|
690
701
|
def _find_new_plain_vars(path: tuple[Hashable, ...], value: Any) -> Any:
|
691
702
|
if path != ( SENTINEL_KEY, ) and type(value) is not EncryptedVar:
|
692
703
|
if (old_value := vault.get(path, default=Unset)) != value:
|
693
|
-
std_print(path[-1], type(old_value), type(value))
|
694
704
|
if type(old_value) is EncryptedVar:
|
695
705
|
decrypted_vars.append(path)
|
696
706
|
else:
|
ansible_vars/util.py
CHANGED
@@ -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)
|
ansible_vars/vault_crypt.py
CHANGED
@@ -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)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: ansible-vars
|
3
|
-
Version: 1.0.
|
3
|
+
Version: 1.0.8
|
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.
|
@@ -220,6 +224,10 @@ Set the tempfile/staging root as you would with `-T <path>`.
|
|
220
224
|
|
221
225
|
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
226
|
|
227
|
+
#### AV_SALT
|
228
|
+
|
229
|
+
Set a fixed salt as you would with `-S <salt>`.
|
230
|
+
|
223
231
|
### Python library
|
224
232
|
|
225
233
|
When using `ansible-vars` as a library, import any of these modules from the `ansible_vars` module.
|
@@ -270,7 +278,6 @@ When editing a file or creating a daemon, decrypted vaults are written to disk t
|
|
270
278
|
- Changes to file metadata (permissions, ...) are not mirrored.
|
271
279
|
- `ansible-vars` does not support files which are not (Jinja2) YAML dictionaries, except for limited support in these commands:
|
272
280
|
- `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
281
|
|
275
282
|
## Extension plans
|
276
283
|
|
@@ -0,0 +1,12 @@
|
|
1
|
+
ansible_vars/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
ansible_vars/cli.py,sha256=TFPSuUmOTWjX6FF5aAd2PnJkyftSur9DfccYwzjQe6A,63959
|
3
|
+
ansible_vars/constants.py,sha256=Nd3sIuSoOvyfUfHfnsnJBDGMW7eNzbMm1NAvEQio9hE,1624
|
4
|
+
ansible_vars/errors.py,sha256=6dzyksPKWira9O2-Ir3MIOwr4XjN9MSBiRp5e6siY6Q,1256
|
5
|
+
ansible_vars/util.py,sha256=UwGPBT19pee7lBpWuBzLPAvcrHUBAn6i1MrJvzM9OQ4,21265
|
6
|
+
ansible_vars/vault.py,sha256=cMvFdtc3bw6yf-aChUEP34k2yafWS2UuubFO84De_rA,46383
|
7
|
+
ansible_vars/vault_crypt.py,sha256=nh2k686nTI3yERIp-qzx5iDE1kZKg10YG019QeZDnLM,10019
|
8
|
+
ansible_vars-1.0.8.dist-info/METADATA,sha256=KSO8y8E4DZeGzYjXKrnuj-aW8ni3TEAh1upcwGPdlL0,17967
|
9
|
+
ansible_vars-1.0.8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
+
ansible_vars-1.0.8.dist-info/entry_points.txt,sha256=RrhkEH0MbfRzflguVrfYfthsFC5V2fkFnizUG3uHMtQ,55
|
11
|
+
ansible_vars-1.0.8.dist-info/licenses/LICENSE,sha256=ocyJHLG5wD12qB4uam2pqWTHIJmzloiyNyTex6Q2DKo,1062
|
12
|
+
ansible_vars-1.0.8.dist-info/RECORD,,
|
@@ -1,12 +0,0 @@
|
|
1
|
-
ansible_vars/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
ansible_vars/cli.py,sha256=OAIXPLMTinS48e9Wn9YEhdHt6eaBHuU9Px1qZ5peCKY,63345
|
3
|
-
ansible_vars/constants.py,sha256=Nd3sIuSoOvyfUfHfnsnJBDGMW7eNzbMm1NAvEQio9hE,1624
|
4
|
-
ansible_vars/errors.py,sha256=6dzyksPKWira9O2-Ir3MIOwr4XjN9MSBiRp5e6siY6Q,1256
|
5
|
-
ansible_vars/util.py,sha256=j6Ay_TXEyCm2OUBV7VRXlGjipKSAuklIHXSXIxbrzj0,21305
|
6
|
-
ansible_vars/vault.py,sha256=cMvFdtc3bw6yf-aChUEP34k2yafWS2UuubFO84De_rA,46383
|
7
|
-
ansible_vars/vault_crypt.py,sha256=Eotf1JVJcvrdae2gv2KMVor-x1D1IMREkONKgKdtT2A,9493
|
8
|
-
ansible_vars-1.0.7.dist-info/METADATA,sha256=otjXXgLP1_XT-Xykt9_V8nyIi65aoRAKM2ukGxdu5pU,17464
|
9
|
-
ansible_vars-1.0.7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
10
|
-
ansible_vars-1.0.7.dist-info/entry_points.txt,sha256=RrhkEH0MbfRzflguVrfYfthsFC5V2fkFnizUG3uHMtQ,55
|
11
|
-
ansible_vars-1.0.7.dist-info/licenses/LICENSE,sha256=ocyJHLG5wD12qB4uam2pqWTHIJmzloiyNyTex6Q2DKo,1062
|
12
|
-
ansible_vars-1.0.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|