envstack 1.0.0__tar.gz → 1.0.2__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.
- {envstack-1.0.0/lib/envstack.egg-info → envstack-1.0.2}/PKG-INFO +1 -1
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/__init__.py +1 -1
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/encrypt.py +34 -7
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/envshell.py +1 -1
- {envstack-1.0.0 → envstack-1.0.2/lib/envstack.egg-info}/PKG-INFO +1 -1
- {envstack-1.0.0 → envstack-1.0.2}/pyproject.toml +1 -1
- {envstack-1.0.0 → envstack-1.0.2}/LICENSE +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/README.md +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/dist.json +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/cli.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/config.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/env.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/exceptions.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/logger.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/node.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/path.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/util.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack/wrapper.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack.egg-info/SOURCES.txt +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack.egg-info/dependency_links.txt +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack.egg-info/entry_points.txt +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack.egg-info/not-zip-safe +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack.egg-info/requires.txt +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/lib/envstack.egg-info/top_level.txt +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/setup.cfg +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/tests/test_cmds.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/tests/test_encrypt.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/tests/test_env.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/tests/test_node.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/tests/test_path.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/tests/test_util.py +0 -0
- {envstack-1.0.0 → envstack-1.0.2}/tests/test_wrapper.py +0 -0
|
@@ -34,7 +34,7 @@ Stacked environment variable management system.
|
|
|
34
34
|
"""
|
|
35
35
|
|
|
36
36
|
__prog__ = "envstack"
|
|
37
|
-
__version__ = "1.0.
|
|
37
|
+
__version__ = "1.0.2"
|
|
38
38
|
|
|
39
39
|
from envstack.env import clear, init, revert, save # noqa: F401
|
|
40
40
|
from envstack.env import load_environ, resolve_environ # noqa: F401
|
|
@@ -38,21 +38,42 @@ import binascii
|
|
|
38
38
|
import os
|
|
39
39
|
import secrets
|
|
40
40
|
from base64 import b64decode, b64encode
|
|
41
|
+
from functools import wraps
|
|
41
42
|
|
|
42
43
|
from envstack.logger import log
|
|
43
44
|
|
|
44
45
|
# cryptography and _rust dependency may not be available everywhere
|
|
45
46
|
# ImportError: DLL load failed while importing _rust: Module not found.
|
|
47
|
+
CRYPTOGRAPHY_AVAILABLE = False
|
|
46
48
|
Fernet = None
|
|
49
|
+
InvalidToken = type("InvalidToken", (Exception,), {})
|
|
50
|
+
InvalidTag = type("InvalidTag", (Exception,), {})
|
|
51
|
+
padding = None
|
|
52
|
+
Cipher = None
|
|
53
|
+
algorithms = None
|
|
54
|
+
modes = None
|
|
47
55
|
try:
|
|
48
|
-
import cryptography.exceptions
|
|
49
56
|
from cryptography.fernet import Fernet, InvalidToken
|
|
57
|
+
from cryptography.exceptions import InvalidTag
|
|
50
58
|
from cryptography.hazmat.primitives import padding
|
|
51
59
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
|
|
60
|
+
CRYPTOGRAPHY_AVAILABLE = True
|
|
52
61
|
except ImportError as err:
|
|
53
62
|
log.debug("cryptography module not available: %s", err)
|
|
54
63
|
|
|
55
64
|
|
|
65
|
+
def require_cryptography(func):
|
|
66
|
+
"""Guard crypto-backed functions when cryptography is unavailable."""
|
|
67
|
+
|
|
68
|
+
@wraps(func)
|
|
69
|
+
def wrapper(*args, **kwargs):
|
|
70
|
+
if not CRYPTOGRAPHY_AVAILABLE:
|
|
71
|
+
raise RuntimeError("cryptography support is not available")
|
|
72
|
+
return func(*args, **kwargs)
|
|
73
|
+
|
|
74
|
+
return wrapper
|
|
75
|
+
|
|
76
|
+
|
|
56
77
|
class Base64Encryptor(object):
|
|
57
78
|
"""Encrypt and decrypt secrets using base64 encoding."""
|
|
58
79
|
|
|
@@ -84,6 +105,7 @@ class FernetEncryptor(object):
|
|
|
84
105
|
self.key = self.get_key(env)
|
|
85
106
|
|
|
86
107
|
@classmethod
|
|
108
|
+
@require_cryptography
|
|
87
109
|
def generate_key(csl):
|
|
88
110
|
"""Generate a new 256-bit encryption key."""
|
|
89
111
|
if Fernet:
|
|
@@ -104,6 +126,7 @@ class FernetEncryptor(object):
|
|
|
104
126
|
return Fernet(key)
|
|
105
127
|
return key
|
|
106
128
|
|
|
129
|
+
@require_cryptography
|
|
107
130
|
def encrypt(self, data: str):
|
|
108
131
|
"""Encrypt a secret using Fernet.
|
|
109
132
|
|
|
@@ -121,9 +144,9 @@ class FernetEncryptor(object):
|
|
|
121
144
|
log.error("invalid value: %s", e)
|
|
122
145
|
except Exception as e:
|
|
123
146
|
log.error("unhandled error: %s", e)
|
|
124
|
-
|
|
125
|
-
return results
|
|
147
|
+
return results
|
|
126
148
|
|
|
149
|
+
@require_cryptography
|
|
127
150
|
def decrypt(self, data: str):
|
|
128
151
|
"""Decrypt a secret using Fernet.
|
|
129
152
|
|
|
@@ -153,6 +176,7 @@ class AESGCMEncryptor(object):
|
|
|
153
176
|
self.key = self.get_key(env)
|
|
154
177
|
|
|
155
178
|
@classmethod
|
|
179
|
+
@require_cryptography
|
|
156
180
|
def generate_key(csl):
|
|
157
181
|
"""Generate a new 256-bit encryption key."""
|
|
158
182
|
key = secrets.token_bytes(32)
|
|
@@ -171,6 +195,7 @@ class AESGCMEncryptor(object):
|
|
|
171
195
|
raise ValueError("invalid base64 encoding: %s" % e)
|
|
172
196
|
return key
|
|
173
197
|
|
|
198
|
+
@require_cryptography
|
|
174
199
|
def encrypt_data(self, secret: str):
|
|
175
200
|
"""Encrypt a secret using AES-GCM.
|
|
176
201
|
|
|
@@ -189,6 +214,7 @@ class AESGCMEncryptor(object):
|
|
|
189
214
|
"tag": b64encode(encryptor.tag).decode(),
|
|
190
215
|
}
|
|
191
216
|
|
|
217
|
+
@require_cryptography
|
|
192
218
|
def decrypt_data(self, encrypted_data: dict):
|
|
193
219
|
"""Decrypt a secret using AES-GCM.
|
|
194
220
|
|
|
@@ -218,14 +244,13 @@ class AESGCMEncryptor(object):
|
|
|
218
244
|
results = compact_store(encrypted_data)
|
|
219
245
|
except binascii.Error as e:
|
|
220
246
|
log.error("invalid base64 encoding: %s", e)
|
|
221
|
-
except
|
|
247
|
+
except InvalidTag:
|
|
222
248
|
log.error("invalid encryption key")
|
|
223
249
|
except ValueError as e:
|
|
224
250
|
log.error("invalid value: %s", e)
|
|
225
251
|
except Exception as e:
|
|
226
252
|
log.error("unhandled error: %s", e)
|
|
227
|
-
|
|
228
|
-
return results
|
|
253
|
+
return results
|
|
229
254
|
|
|
230
255
|
def decrypt(self, data: str):
|
|
231
256
|
"""Convenience function to decrypt a secret using AES-GCM.
|
|
@@ -239,7 +264,7 @@ class AESGCMEncryptor(object):
|
|
|
239
264
|
return decrypted.decode()
|
|
240
265
|
except binascii.Error as e:
|
|
241
266
|
log.debug("invalid base64 encoding: %s", e)
|
|
242
|
-
except
|
|
267
|
+
except InvalidTag:
|
|
243
268
|
log.debug("invalid encryption key")
|
|
244
269
|
except ValueError as e:
|
|
245
270
|
log.debug("invalid value: %s", e)
|
|
@@ -248,6 +273,7 @@ class AESGCMEncryptor(object):
|
|
|
248
273
|
return data
|
|
249
274
|
|
|
250
275
|
|
|
276
|
+
@require_cryptography
|
|
251
277
|
def pad_data(data: str):
|
|
252
278
|
"""Pad data to be block-aligned for AES encryption.
|
|
253
279
|
|
|
@@ -258,6 +284,7 @@ def pad_data(data: str):
|
|
|
258
284
|
return padder.update(str(data).encode()) + padder.finalize()
|
|
259
285
|
|
|
260
286
|
|
|
287
|
+
@require_cryptography
|
|
261
288
|
def unpad_data(data: dict):
|
|
262
289
|
"""Unpad data after decryption.
|
|
263
290
|
|
|
@@ -131,7 +131,7 @@ class EnvshellWrapper(Wrapper):
|
|
|
131
131
|
if os.name == "nt":
|
|
132
132
|
return ("PROMPT", "$E[32m(${ENV:=${STACK}})$E[0m $P$G ")
|
|
133
133
|
else:
|
|
134
|
-
return ("PS1", "\[\e[32m\](${ENV:=${STACK}})\[\e[0m\] \w\$ ")
|
|
134
|
+
return ("PS1", r"\[\e[32m\](${ENV:=${STACK}})\[\e[0m\] \w\$ ")
|
|
135
135
|
|
|
136
136
|
def get_subprocess_env(self):
|
|
137
137
|
"""
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "envstack"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.2"
|
|
8
8
|
description = "Environment variable composition layer for tools and processes."
|
|
9
9
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
10
10
|
requires-python = ">=3.6"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|