ansible-vars 1.0.12__py3-none-any.whl → 1.0.13__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 CHANGED
@@ -29,11 +29,6 @@ from pygments.lexers.templates import YamlJinjaLexer
29
29
  from pygments.formatter import Formatter
30
30
  from pygments.formatters import TerminalFormatter, Terminal256Formatter, TerminalTrueColorFormatter
31
31
 
32
- # If we need to change the working directory, we have to do it before loading the Ansible vault library
33
- # Else, keys will not be detected correctly
34
- ANSIBLE_HOME: str = os.environ.get('ANSIBLE_HOME', os.getcwd())
35
- os.chdir(ANSIBLE_HOME)
36
-
37
32
  # Internal module imports
38
33
  from .vault import VaultFile, EncryptedVar, ProtoEncryptedVar
39
34
  from .vault_crypt import VaultKey, VaultKeyring
@@ -203,11 +198,18 @@ Deletes a node from a vault if it exists.
203
198
  '''
204
199
  }
205
200
 
206
- DEFAULT_EDITOR: str = os.environ.get('EDITOR', 'notepad.exe' if os.name == 'nt' else 'vi')
207
- DEFAULT_COLOR_MODE: str = os.environ.get('AV_COLOR_MODE', '256' if sys.stdout.isatty() else 'none')
208
- DEFAULT_TEMP_DIR: str = os.environ.get('AV_TEMP_DIR', gettempdir())
209
- DEFAULT_CREATE_PLAIN: bool = os.environ.get('AV_CREATE_PLAIN', 'no').lower() in [ 'yes', 'y', 'true', 't', '1' ]
210
- DEFAULT_SALT: str | None = os.environ.get('AV_SALT', None)
201
+ # Not using defaults directly because we also want to ignore empty values
202
+ DEFAULT_EDITOR: str = os.environ.get('EDITOR', None) or ('notepad.exe' if os.name == 'nt' else 'vi')
203
+ DEFAULT_COLOR_MODE: str = os.environ.get('AV_COLOR_MODE', None) or ('256' if sys.stdout.isatty() else 'none')
204
+ DEFAULT_TEMP_DIR: str = os.environ.get('AV_TEMP_DIR', None) or gettempdir()
205
+ DEFAULT_CREATE_PLAIN: bool = (os.environ.get('AV_CREATE_PLAIN', None) or 'no').lower() in [ 'yes', 'y', 'true', 't', '1' ]
206
+ DEFAULT_SALT: str | None = os.environ.get('AV_SALT', None) or None
207
+ DEFAULT_SECRETS_ROOT: str | None = os.environ.get('AV_SECRETS_ROOT', None) or os.environ.get('ANSIBLE_HOME', None) or None
208
+ DEFAULT_RESOLVER_ROOT: str | None = os.environ.get('AV_RESOLVER_ROOT', None) or None
209
+
210
+ # Resolve all paths as if the caller is in the resolver root
211
+ if DEFAULT_RESOLVER_ROOT:
212
+ os.chdir(DEFAULT_RESOLVER_ROOT)
211
213
 
212
214
  args: ArgumentParser = ArgumentParser(
213
215
  prog = 'ansible-vars',
@@ -251,7 +253,12 @@ key_args = args.add_argument_group('vault key management', description=HELP['key
251
253
  key_args.add_argument(
252
254
  '--add-key', '-k', type=str, nargs=2, action='append', dest='keys', default=[], metavar=('<identifier>', '<passphrase>'), help='add a vault key'
253
255
  )
254
- key_args.add_argument('--no-detect-keys', '-D', action='store_false', dest='detect_keys', help='disable automatic key detection')
256
+ key_mutex = key_args.add_mutually_exclusive_group()
257
+ key_mutex.add_argument('--no-detect-keys', '-D', action='store_false', dest='detect_keys', help='disable automatic key detection')
258
+ key_mutex.add_argument(
259
+ '--detection-source', type=str, metavar='<secrets root>', default=DEFAULT_SECRETS_ROOT,
260
+ help=f"use this directory or config file to detect keys (default: { DEFAULT_SECRETS_ROOT or 'CWD' })"
261
+ )
255
262
  key_args.add_argument('--encryption-key', '-K', type=str, metavar='<identifier>', help='which of the loaded keys to use for encryption')
256
263
  key_args.add_argument(
257
264
  '--fixed-salt', '-S', type=str, metavar='<salt>', default=DEFAULT_SALT,
@@ -587,7 +594,10 @@ sys.excepthook = _exc_hook
587
594
  # Load vault keys
588
595
 
589
596
  _explicit_keys: list[VaultKey] = [ VaultKey(passphrase, vault_id=id) for id, passphrase in config.keys ]
590
- keyring = VaultKeyring(_explicit_keys.copy(), detect_available_keys=config.detect_keys, default_salt=config.fixed_salt)
597
+ keyring: VaultKeyring = VaultKeyring(
598
+ _explicit_keys.copy(), default_salt=config.fixed_salt,
599
+ detect_available_keys=config.detect_keys, detection_source=config.detection_source
600
+ )
591
601
 
592
602
  if config.encryption_key:
593
603
  keyring.default_encryption_key = keyring.key_by_id(config.encryption_key)
ansible_vars/vault.py CHANGED
@@ -239,7 +239,7 @@ class Vault():
239
239
  return (-1, default) if with_index else default
240
240
 
241
241
  def set(
242
- self, path: DictPath, value: Any, overwrite: bool | Type[ThrowError] = True,
242
+ self, path: DictPath, value: Any, overwrite: bool | Type[ThrowError] = True, # type: ignore
243
243
  create_parents: bool | Type[ThrowError] = True, encrypt: bool = False
244
244
  ) -> bool:
245
245
  '''
@@ -263,7 +263,7 @@ class Vault():
263
263
  - `True`: Attempt to copy and convert the value('s leaf values) into an `EncryptedVar` before storing it
264
264
  - `False`: Store the value as-is
265
265
  '''
266
- path: tuple[Hashable, ...] = Vault._to_path(path)
266
+ path: tuple[Hashable, ...] = Vault._to_path(path) # XXX typer complains if not explicitly re-typed
267
267
  # Encrypt value if necessary
268
268
  if encrypt:
269
269
  value = Vault._copy_data(value)
@@ -1,13 +1,14 @@
1
1
  # Vault secret loading and management for ansible-vars
2
2
 
3
3
  # Standard library imports
4
- import re
5
- from typing import Type
4
+ import os, re
5
+ from typing import Type, cast
6
6
 
7
7
  # External library imports
8
8
  import ansible.constants as Ansible
9
9
  from ansible.parsing.vault import VaultLib, VaultSecret, AnsibleVaultError
10
10
  from ansible.cli import DataLoader, CLI
11
+ from ansible.config.manager import ConfigManager
11
12
 
12
13
  # Internal module imports
13
14
  from .errors import NoMatchingVaultKeyError, NoVaultKeysError, VaultKeyMatchError
@@ -89,7 +90,7 @@ class VaultKey():
89
90
  class VaultKeyring():
90
91
  '''
91
92
  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
+ Tries to infer available secrets from the caller's present working directory or a custom source.
93
94
  '''
94
95
 
95
96
  def __init__(
@@ -97,12 +98,14 @@ class VaultKeyring():
97
98
  keys: list[VaultKey] | None = None,
98
99
  default_encryption_key: VaultKey | None = None,
99
100
  default_salt: str | None = None,
100
- detect_available_keys: bool = True
101
+ detect_available_keys: bool = True,
102
+ detection_source: str | list[str] | None = None
101
103
  ) -> None:
102
104
  '''
103
105
  Create a new keyring of `VaultKey`s and optionally populate it with the given `keys`.
104
- Tries to infer available Ansible vault secrets from the caller's present working directory, if it is an Ansible home.
106
+ Tries to infer available Ansible vault secrets from the caller's present working directory or a custom `detection_source`.
105
107
  This is done using the Ansible CLI module. You can disable key inferral by setting `detect_available_keys` to False.
108
+ Check the docstring of `VaultKeyring.load_cli_secrets` for details on alternative key detection sources.
106
109
  When decrypting vault data, inferred keys are tried after the explicitly supplied ones.
107
110
  Note that the inferral process may cause TTY prompts or other unwanted in- and output.
108
111
 
@@ -116,7 +119,7 @@ class VaultKeyring():
116
119
  self.default_encryption_key: VaultKey | None = default_encryption_key
117
120
  self.default_salt: str | None = default_salt
118
121
  if detect_available_keys:
119
- self.keys.extend(VaultKeyring.load_cli_secrets())
122
+ self.keys.extend(VaultKeyring.load_cli_secrets(detection_source))
120
123
 
121
124
  @property
122
125
  def encryption_key(self) -> VaultKey:
@@ -169,17 +172,46 @@ class VaultKeyring():
169
172
  raise NoMatchingVaultKeyError(f"No matching key found for ID '{ id }' in { self }")
170
173
 
171
174
  @staticmethod
172
- def load_cli_secrets() -> list[VaultKey]:
175
+ def load_cli_secrets(source: str | list[str] | None = None) -> list[VaultKey]:
173
176
  '''
174
- Tries to infer available Ansible vault secrets from the caller's present working directory, if it is an Ansible home.
177
+ Tries to infer available Ansible vault secrets from the given configuration source's `vault_identity_list`.
178
+ - If `source` is None, check the environment and CWD for a configuration file path and check the file.
179
+ - If `source` is a file, check the file.
180
+ - If `source` is a directory, check `<source>/ansible.cfg`.
181
+ - If `source` is a list, it is used directly as the `vault_identity_list`.
182
+
183
+ Due to limitations in the Ansible interface, setting the environment variable `ANSIBLE_VAULT_IDENTITY_LIST`
184
+ will always override the result of the `source` check, except if `source` is a list of vault IDs.
185
+
175
186
  Inferred secrets are converted into `VaultKey`s and returned.
176
187
  Note that the inferral process may cause TTY prompts or other unwanted in- and output.
177
- '''
178
- # Ansible.DEFAULT_VAULT_IDENTITY_LIST is a list populated with vault IDs inferred from the PWD
179
- # (i.e. has to be run in ANSIBLE_HOME, else the value is [])
180
- secrets: list[tuple[str | None, VaultSecret]] = \
181
- CLI.setup_vault_secrets(DataLoader(), Ansible.DEFAULT_VAULT_IDENTITY_LIST, auto_prompt=False) # type: ignore
182
- return list(map(VaultKey.from_ansible_secret, secrets))
188
+ Not entirely thread-safe, as setting a specific `source` path will temporarily change the global CWD.
189
+ '''
190
+ pardir: str = os.getcwd()
191
+ # Set or discover available vault IDs
192
+ if isinstance(source, list | tuple):
193
+ vault_ids: list[str] = source
194
+ else:
195
+ if source:
196
+ if not os.path.exists(source):
197
+ raise FileNotFoundError(f"Vault key detection path `{ source }` could not be resolved.")
198
+ if os.path.isdir(source):
199
+ source = os.path.join(source, 'ansible.cfg')
200
+ pardir: str = os.path.dirname(cast(str, source))
201
+ vault_ids: list[str] = ConfigManager(conf_file=source).get_config_value('DEFAULT_VAULT_IDENTITY_LIST')
202
+ # Load secrets for discovered vault IDs
203
+ if not vault_ids:
204
+ return []
205
+ prev_dir: str = os.getcwd()
206
+ try:
207
+ # XXX Hacky, but loading a vault ID like 'vaultid@get-password.sh' will be resolved from CWD
208
+ # Could possibly be avoided by splitting the detected vault IDs and transforming any right-hand paths
209
+ os.chdir(pardir)
210
+ secrets: list[tuple[str | None, VaultSecret]] = \
211
+ CLI.setup_vault_secrets(DataLoader(), vault_ids, auto_prompt=False) # type: ignore
212
+ return list(map(VaultKey.from_ansible_secret, secrets))
213
+ finally:
214
+ os.chdir(prev_dir)
183
215
 
184
216
  def __repr__(self) -> str:
185
217
  return f"VaultKeyring({ ', '.join(map(lambda key: key.id, self.keys)) or 'no keys' })"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ansible-vars
3
- Version: 1.0.12
3
+ Version: 1.0.13
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
@@ -24,11 +24,15 @@ Description-Content-Type: text/markdown
24
24
 
25
25
  *Manage vaults and variable files for Ansible.*
26
26
 
27
+ ## TL;DR
28
+
29
+ Replaces the `ansible-vault` command with `ansible-vars`, which supports encrypting individual variables, not just entire files. Also provides a CLI and Python interface for querying and modifying Ansible variable files and vaults.
30
+
27
31
  ## Introduction
28
32
 
29
33
  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
34
 
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.
35
+ `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 (individually (un)encrypted variables). 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
36
 
33
37
  The main features are:
34
38
  - Create and edit vaults and variable files with hybrid encryption support.
@@ -63,8 +67,8 @@ Note that the virtual environment must be active when using the command.
63
67
  ### Using [pipx](https://github.com/pypa/pipx)
64
68
 
65
69
  ```sh
66
- # Install pipx
67
- pip install pipx
70
+ # Install pipx, e.g. like this on Debian
71
+ sudo apt install pipx
68
72
  # Install pip package globally using pipx
69
73
  pipx install ansible-vars
70
74
  ```
@@ -93,21 +97,31 @@ When editing a vault or variable file using `ansible-vars`, you can prefix any s
93
97
  my_message: !enc this is a super secret message
94
98
  ```
95
99
 
96
- Encrypted variables will be displayed using this tag when editing, making it easy to view, modify, or decrypt their value.
100
+ The variable will turn into an Ansible encryption string when saving the file:
101
+
102
+ ```yaml
103
+ my_message: !vault |-
104
+ $ANSIBLE_VAULT;1.2;AES256;someid
105
+ 333533...
106
+ ```
107
+
108
+ Encrypted variables will be displayed using this tag when editing, making it easy to view, modify, or decrypt their value on the fly.
97
109
 
98
110
  ### Vault secrets
99
111
 
100
112
  **TL;DR:** Run `ansible-vars` from your Ansible home to auto-detect configured secrets. Add a custom secret using `-k <identifier> <passphrase>`.
101
113
 
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.
114
+ 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, which looks for a `vault_identity_list` in the Ansible configuration. 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
115
 
104
116
  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
117
 
106
118
  You can disable automatic key detection by flagging `--no-detect-keys|-D`. Use `ansible-vars keyring` to view all available keys.
107
119
 
120
+ You can customize the configuration file used for key detection via `--detection-source` or the environment. When a configuration file path is given, the file will be searched for a `vault_identity_list` and the corresponding secrets get loaded. When a directory path is given, `ansible-vars` will look for an `ansible.cfg` in that directory and perform the same loading procedure on that file. When left unset, Ansible checks if a configuration file path is set via its standard environment variable, and otherwise uses the current working directory for auto-detection. Note that setting Ansible's `DEFAULT_VAULT_IDENTITY_LIST` environment variable will override this behavior.
121
+
108
122
  #### Encryption salts
109
123
 
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>` or the environment. 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.
124
+ Each time you edit a vault or otherwise encrypt a value, a randomly generated salt is used to avoid identical plain values resulting in identical ciphers. One side-effect of this is that each time a single encrypted variable is edited, all other ciphers in the vault will change as well, possibly making changelogs (e.g. from git) less useful. You can avoid this by passing a fixed salt via `--fixed-salt|-S <salt>` or the environment. 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
125
 
112
126
  ### Diff logging
113
127
 
@@ -213,9 +227,15 @@ Creates, updates, or deletes a key-value pair from a vault or variable file. Whe
213
227
 
214
228
  ### Environment variables
215
229
 
216
- #### ANSIBLE_HOME
230
+ #### AV_SECRETS_ROOT (or ANSIBLE_HOME)
231
+
232
+ If this variable is set to a file or directory path, the program will use its value to auto-detect configured vault secrets if detection is not disabled via `--no-detect-keys|-D`. When a configuration file path is given, the file will be searched for a `vault_identity_list` and the corresponding secrets get loaded. When a directory path is given, `ansible-vars` will look for an `ansible.cfg` in that directory and perform the same loading procedure on that file. Note that setting Ansible's `DEFAULT_VAULT_IDENTITY_LIST` environment variable will skip this step entirely.
233
+
234
+ When running the script from somewhere else, this way vault secrets will be resolved as if you were in this directory or using this configuration file. When left unset, Ansible checks if a configuration file path is set via its standard environment variable, and otherwise uses the current working directory for auto-detection. It is equivalent to setting the `--detection-source` flag.
235
+
236
+ #### AV_RESOLVER_ROOT
217
237
 
218
- If this variable is set, the program will use its value as the working directory. When running the script from somewhere else, this way keys will be detected and paths will be resolved as if you were in your Ansible root directory.
238
+ If this variable is set, the program will use its value as the working directory. When running the script from somewhere else, this way paths will be resolved as if you were in this directory.
219
239
 
220
240
  #### AV_COLOR_MODE
221
241
 
@@ -0,0 +1,12 @@
1
+ ansible_vars/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ ansible_vars/cli.py,sha256=0RScnMrG__L5mXu_OmEgxB3x4lTJ1lAyowlOJvQNcrE,66476
3
+ ansible_vars/constants.py,sha256=VNr78qbzdJ0Vn0psbzjjQngNG3VbHhk6NXTz30VNUno,1622
4
+ ansible_vars/errors.py,sha256=6dzyksPKWira9O2-Ir3MIOwr4XjN9MSBiRp5e6siY6Q,1256
5
+ ansible_vars/util.py,sha256=UwGPBT19pee7lBpWuBzLPAvcrHUBAn6i1MrJvzM9OQ4,21265
6
+ ansible_vars/vault.py,sha256=aGTU_GmJLw0QGjBQiNlRE_E-rRMEVVbwdr8IV2U2duk,47011
7
+ ansible_vars/vault_crypt.py,sha256=EVK1-EENC_IUaSqDC1jGhh8vXHka8DQknMjPX4zvmKE,11844
8
+ ansible_vars-1.0.13.dist-info/METADATA,sha256=gBEjhS3-pVRnOKaX3ncPrVaw8IMcqkxwKr_5nXLzZfc,21050
9
+ ansible_vars-1.0.13.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
+ ansible_vars-1.0.13.dist-info/entry_points.txt,sha256=RrhkEH0MbfRzflguVrfYfthsFC5V2fkFnizUG3uHMtQ,55
11
+ ansible_vars-1.0.13.dist-info/licenses/LICENSE,sha256=ocyJHLG5wD12qB4uam2pqWTHIJmzloiyNyTex6Q2DKo,1062
12
+ ansible_vars-1.0.13.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- ansible_vars/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- ansible_vars/cli.py,sha256=u9kuZN304BQ5NGd8-dWnogmxXlnWl0_eD9iLcQEvbdQ,65917
3
- ansible_vars/constants.py,sha256=VNr78qbzdJ0Vn0psbzjjQngNG3VbHhk6NXTz30VNUno,1622
4
- ansible_vars/errors.py,sha256=6dzyksPKWira9O2-Ir3MIOwr4XjN9MSBiRp5e6siY6Q,1256
5
- ansible_vars/util.py,sha256=UwGPBT19pee7lBpWuBzLPAvcrHUBAn6i1MrJvzM9OQ4,21265
6
- ansible_vars/vault.py,sha256=ZrlUIjSBKQPjQg5NqbpfTzkhca7x-tvMrH9gFn1g2xE,46947
7
- ansible_vars/vault_crypt.py,sha256=nh2k686nTI3yERIp-qzx5iDE1kZKg10YG019QeZDnLM,10019
8
- ansible_vars-1.0.12.dist-info/METADATA,sha256=6Qy4OTNpY28l6B4AaUok0Fv_SSzvImMcVmyy1ScjyGU,18830
9
- ansible_vars-1.0.12.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
10
- ansible_vars-1.0.12.dist-info/entry_points.txt,sha256=RrhkEH0MbfRzflguVrfYfthsFC5V2fkFnizUG3uHMtQ,55
11
- ansible_vars-1.0.12.dist-info/licenses/LICENSE,sha256=ocyJHLG5wD12qB4uam2pqWTHIJmzloiyNyTex6Q2DKo,1062
12
- ansible_vars-1.0.12.dist-info/RECORD,,