toru-vault 0.1.4__tar.gz → 0.3.0__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.
- {toru_vault-0.1.4 → toru_vault-0.3.0}/PKG-INFO +19 -9
- {toru_vault-0.1.4 → toru_vault-0.3.0}/README.md +18 -8
- {toru_vault-0.1.4 → toru_vault-0.3.0}/pyproject.toml +1 -1
- {toru_vault-0.1.4 → toru_vault-0.3.0}/setup.py +2 -2
- toru_vault-0.3.0/tests/test_env_load.py +101 -0
- toru_vault-0.3.0/tests/test_vault_encryption.py +124 -0
- toru_vault-0.3.0/tests/test_vault_jit.py +79 -0
- {toru_vault-0.1.4 → toru_vault-0.3.0}/toru_vault/__init__.py +1 -0
- {toru_vault-0.1.4 → toru_vault-0.3.0}/toru_vault/__main__.py +29 -6
- toru_vault-0.3.0/toru_vault/in_env.py +173 -0
- toru_vault-0.3.0/toru_vault/in_memory.py +379 -0
- {toru_vault-0.1.4 → toru_vault-0.3.0}/toru_vault/lazy_dict.py +10 -15
- toru_vault-0.3.0/toru_vault/vault.py +261 -0
- {toru_vault-0.1.4 → toru_vault-0.3.0}/toru_vault.egg-info/PKG-INFO +19 -9
- {toru_vault-0.1.4 → toru_vault-0.3.0}/toru_vault.egg-info/SOURCES.txt +5 -3
- toru_vault-0.1.4/tests/test_cli.py +0 -96
- toru_vault-0.1.4/tests/test_lazy_dict.py +0 -125
- toru_vault-0.1.4/tests/test_vault.py +0 -196
- toru_vault-0.1.4/toru_vault/vault.py +0 -575
- {toru_vault-0.1.4 → toru_vault-0.3.0}/LICENSE +0 -0
- {toru_vault-0.1.4 → toru_vault-0.3.0}/setup.cfg +0 -0
- {toru_vault-0.1.4 → toru_vault-0.3.0}/toru_vault/py.typed +0 -0
- {toru_vault-0.1.4 → toru_vault-0.3.0}/toru_vault.egg-info/dependency_links.txt +0 -0
- {toru_vault-0.1.4 → toru_vault-0.3.0}/toru_vault.egg-info/entry_points.txt +0 -0
- {toru_vault-0.1.4 → toru_vault-0.3.0}/toru_vault.egg-info/requires.txt +0 -0
- {toru_vault-0.1.4 → toru_vault-0.3.0}/toru_vault.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: toru-vault
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0
|
4
4
|
Summary: ToruVault: A simple Python package for managing Bitwarden secrets
|
5
5
|
Author: Toru AI
|
6
6
|
Author-email: ToruAI <mpaszynski@toruai.com>
|
@@ -28,7 +28,7 @@ Dynamic: requires-python
|
|
28
28
|
A simple Python package for managing Bitwarden secrets with enhanced security.
|
29
29
|
|
30
30
|
|
31
|
-

|
32
32
|

|
33
33
|

|
34
34
|
|
@@ -37,8 +37,8 @@ A simple Python package for managing Bitwarden secrets with enhanced security.
|
|
37
37
|
- Load secrets from Bitwarden Secret Manager into environment variables
|
38
38
|
- Get secrets as a Python dictionary
|
39
39
|
- Filter secrets by project ID
|
40
|
-
-
|
41
|
-
-
|
40
|
+
- JIT decryption of individual secrets
|
41
|
+
- No persistent caching of decrypted values
|
42
42
|
- Secure file permissions for state storage
|
43
43
|
- Machine-specific secret protection
|
44
44
|
- Secure credential storage using OS keyring
|
@@ -118,6 +118,7 @@ Alternatively, you can set the following environment variables:
|
|
118
118
|
- `BWS_TOKEN`: Your Bitwarden access token
|
119
119
|
- `ORGANIZATION_ID`: Your Bitwarden organization ID
|
120
120
|
- `STATE_FILE`: Path to the state file (must be in an existing directory)
|
121
|
+
- `PROJECT_ID` (optional): Your Bitwarden project ID to filter secrets
|
121
122
|
- `API_URL` (optional): Defaults to "https://api.bitwarden.com"
|
122
123
|
- `IDENTITY_URL` (optional): Defaults to "https://identity.bitwarden.com"
|
123
124
|
|
@@ -159,6 +160,11 @@ print(os.environ.get("SECRET_NAME"))
|
|
159
160
|
# Load secrets for a specific project
|
160
161
|
vault.env_load(project_id="your-project-id")
|
161
162
|
|
163
|
+
# Alternatively, set PROJECT_ID environment variable and call without parameter
|
164
|
+
# export PROJECT_ID="your-project-id" # Linux/macOS
|
165
|
+
# set PROJECT_ID=your-project-id # Windows
|
166
|
+
vault.env_load() # Will use PROJECT_ID from environment
|
167
|
+
|
162
168
|
# Override existing environment variables (default: False)
|
163
169
|
vault.env_load(override=True)
|
164
170
|
```
|
@@ -178,6 +184,11 @@ secrets = vault.get(refresh=True)
|
|
178
184
|
# Get secrets for a specific project
|
179
185
|
secrets = vault.get(project_id="your-project-id")
|
180
186
|
|
187
|
+
# Alternatively, set PROJECT_ID environment variable and call without parameter
|
188
|
+
# export PROJECT_ID="your-project-id" # Linux/macOS
|
189
|
+
# set PROJECT_ID=your-project-id # Windows
|
190
|
+
secrets = vault.get() # Will use PROJECT_ID from environment
|
191
|
+
|
181
192
|
# Use in-memory encryption instead of system keyring
|
182
193
|
secrets = vault.get(use_keyring=False)
|
183
194
|
```
|
@@ -199,11 +210,10 @@ vault.env_load_all(override=True)
|
|
199
210
|
The vault package includes several security enhancements:
|
200
211
|
|
201
212
|
1. **OS Keyring Integration**: Securely stores BWS_TOKEN, ORGANIZATION_ID, and STATE_FILE in your OS keyring
|
202
|
-
2. **Memory Protection**: Secrets are encrypted in memory using Fernet encryption (AES-128)
|
203
|
-
3. **
|
204
|
-
4. **
|
205
|
-
5. **
|
206
|
-
6. **Machine-Specific Encryption**: Uses machine-specific identifiers for encryption keys
|
213
|
+
2. **Memory Protection**: Secrets are individually encrypted in memory using Fernet encryption (AES-128)
|
214
|
+
3. **JIT Decryption**: Secrets are only decrypted when explicitly accessed and never stored in decrypted form
|
215
|
+
4. **Secure File Permissions**: Sets secure permissions on state files
|
216
|
+
5. **Machine-Specific Encryption**: Uses machine-specific identifiers for encryption keys
|
207
217
|
7. **Cache Clearing**: Automatically clears secret cache on program exit
|
208
218
|
8. **Environment Variable Protection**: Doesn't override existing environment variables by default
|
209
219
|
9. **Secure Key Derivation**: Uses PBKDF2 with SHA-256 for key derivation
|
@@ -5,7 +5,7 @@
|
|
5
5
|
A simple Python package for managing Bitwarden secrets with enhanced security.
|
6
6
|
|
7
7
|
|
8
|
-

|
9
9
|

|
10
10
|

|
11
11
|
|
@@ -14,8 +14,8 @@ A simple Python package for managing Bitwarden secrets with enhanced security.
|
|
14
14
|
- Load secrets from Bitwarden Secret Manager into environment variables
|
15
15
|
- Get secrets as a Python dictionary
|
16
16
|
- Filter secrets by project ID
|
17
|
-
-
|
18
|
-
-
|
17
|
+
- JIT decryption of individual secrets
|
18
|
+
- No persistent caching of decrypted values
|
19
19
|
- Secure file permissions for state storage
|
20
20
|
- Machine-specific secret protection
|
21
21
|
- Secure credential storage using OS keyring
|
@@ -95,6 +95,7 @@ Alternatively, you can set the following environment variables:
|
|
95
95
|
- `BWS_TOKEN`: Your Bitwarden access token
|
96
96
|
- `ORGANIZATION_ID`: Your Bitwarden organization ID
|
97
97
|
- `STATE_FILE`: Path to the state file (must be in an existing directory)
|
98
|
+
- `PROJECT_ID` (optional): Your Bitwarden project ID to filter secrets
|
98
99
|
- `API_URL` (optional): Defaults to "https://api.bitwarden.com"
|
99
100
|
- `IDENTITY_URL` (optional): Defaults to "https://identity.bitwarden.com"
|
100
101
|
|
@@ -136,6 +137,11 @@ print(os.environ.get("SECRET_NAME"))
|
|
136
137
|
# Load secrets for a specific project
|
137
138
|
vault.env_load(project_id="your-project-id")
|
138
139
|
|
140
|
+
# Alternatively, set PROJECT_ID environment variable and call without parameter
|
141
|
+
# export PROJECT_ID="your-project-id" # Linux/macOS
|
142
|
+
# set PROJECT_ID=your-project-id # Windows
|
143
|
+
vault.env_load() # Will use PROJECT_ID from environment
|
144
|
+
|
139
145
|
# Override existing environment variables (default: False)
|
140
146
|
vault.env_load(override=True)
|
141
147
|
```
|
@@ -155,6 +161,11 @@ secrets = vault.get(refresh=True)
|
|
155
161
|
# Get secrets for a specific project
|
156
162
|
secrets = vault.get(project_id="your-project-id")
|
157
163
|
|
164
|
+
# Alternatively, set PROJECT_ID environment variable and call without parameter
|
165
|
+
# export PROJECT_ID="your-project-id" # Linux/macOS
|
166
|
+
# set PROJECT_ID=your-project-id # Windows
|
167
|
+
secrets = vault.get() # Will use PROJECT_ID from environment
|
168
|
+
|
158
169
|
# Use in-memory encryption instead of system keyring
|
159
170
|
secrets = vault.get(use_keyring=False)
|
160
171
|
```
|
@@ -176,11 +187,10 @@ vault.env_load_all(override=True)
|
|
176
187
|
The vault package includes several security enhancements:
|
177
188
|
|
178
189
|
1. **OS Keyring Integration**: Securely stores BWS_TOKEN, ORGANIZATION_ID, and STATE_FILE in your OS keyring
|
179
|
-
2. **Memory Protection**: Secrets are encrypted in memory using Fernet encryption (AES-128)
|
180
|
-
3. **
|
181
|
-
4. **
|
182
|
-
5. **
|
183
|
-
6. **Machine-Specific Encryption**: Uses machine-specific identifiers for encryption keys
|
190
|
+
2. **Memory Protection**: Secrets are individually encrypted in memory using Fernet encryption (AES-128)
|
191
|
+
3. **JIT Decryption**: Secrets are only decrypted when explicitly accessed and never stored in decrypted form
|
192
|
+
4. **Secure File Permissions**: Sets secure permissions on state files
|
193
|
+
5. **Machine-Specific Encryption**: Uses machine-specific identifiers for encryption keys
|
184
194
|
7. **Cache Clearing**: Automatically clears secret cache on program exit
|
185
195
|
8. **Environment Variable Protection**: Doesn't override existing environment variables by default
|
186
196
|
9. **Secure Key Derivation**: Uses PBKDF2 with SHA-256 for key derivation
|
@@ -0,0 +1,101 @@
|
|
1
|
+
import os
|
2
|
+
import pytest
|
3
|
+
from unittest.mock import patch, MagicMock
|
4
|
+
|
5
|
+
from toru_vault.vault import env_load, env_load_all
|
6
|
+
|
7
|
+
class TestEnvLoad:
|
8
|
+
|
9
|
+
def test_env_load_single_project(self, mock_env_vars, mock_initialize_client, mock_bitwarden_client):
|
10
|
+
"""Test loading secrets from a single project into environment variables"""
|
11
|
+
# Clear any existing test environment variables
|
12
|
+
if "TEST_SECRET1" in os.environ:
|
13
|
+
del os.environ["TEST_SECRET1"]
|
14
|
+
if "TEST_SECRET2" in os.environ:
|
15
|
+
del os.environ["TEST_SECRET2"]
|
16
|
+
|
17
|
+
# Call the function with a specific project
|
18
|
+
env_load(project_id="project1")
|
19
|
+
|
20
|
+
# Verify Bitwarden client was initialized
|
21
|
+
mock_initialize_client.assert_called_once()
|
22
|
+
|
23
|
+
# Verify secrets synchronization was performed
|
24
|
+
mock_bitwarden_client.secrets().sync.assert_called_once()
|
25
|
+
|
26
|
+
# Verify environment variables were set
|
27
|
+
assert os.environ["TEST_SECRET1"] == "test_value1"
|
28
|
+
assert os.environ["TEST_SECRET2"] == "test_value2"
|
29
|
+
|
30
|
+
# Test overriding
|
31
|
+
os.environ["TEST_SECRET1"] = "existing_value"
|
32
|
+
|
33
|
+
# Call with override=False, shouldn't change existing value
|
34
|
+
env_load(project_id="project1", override=False)
|
35
|
+
assert os.environ["TEST_SECRET1"] == "existing_value"
|
36
|
+
|
37
|
+
# Call with override=True, should change existing value
|
38
|
+
env_load(project_id="project1", override=True)
|
39
|
+
assert os.environ["TEST_SECRET1"] == "test_value1"
|
40
|
+
|
41
|
+
def test_env_load_all(self, mock_env_vars, mock_initialize_client, mock_bitwarden_client):
|
42
|
+
"""Test loading secrets from all projects into environment variables"""
|
43
|
+
# Clear any existing test environment variables
|
44
|
+
if "TEST_SECRET1" in os.environ:
|
45
|
+
del os.environ["TEST_SECRET1"]
|
46
|
+
if "TEST_SECRET2" in os.environ:
|
47
|
+
del os.environ["TEST_SECRET2"]
|
48
|
+
|
49
|
+
# Call the function to load all secrets
|
50
|
+
env_load_all()
|
51
|
+
|
52
|
+
# Verify Bitwarden client was initialized
|
53
|
+
mock_initialize_client.assert_called_once()
|
54
|
+
|
55
|
+
# Verify secrets synchronization was performed
|
56
|
+
mock_bitwarden_client.secrets().sync.assert_called_once()
|
57
|
+
|
58
|
+
# Verify secrets were fetched with list and get_by_ids
|
59
|
+
mock_bitwarden_client.secrets().list.assert_called_once()
|
60
|
+
mock_bitwarden_client.secrets().get_by_ids.assert_called_once()
|
61
|
+
|
62
|
+
# Verify environment variables were set
|
63
|
+
assert os.environ["TEST_SECRET1"] == "test_value1"
|
64
|
+
assert os.environ["TEST_SECRET2"] == "test_value2"
|
65
|
+
|
66
|
+
# Test overriding
|
67
|
+
os.environ["TEST_SECRET1"] = "existing_value"
|
68
|
+
|
69
|
+
# Call with override=False, shouldn't change existing value
|
70
|
+
env_load_all(override=False)
|
71
|
+
assert os.environ["TEST_SECRET1"] == "existing_value"
|
72
|
+
|
73
|
+
# Call with override=True, should change existing value
|
74
|
+
env_load_all(override=True)
|
75
|
+
assert os.environ["TEST_SECRET1"] == "test_value1"
|
76
|
+
|
77
|
+
def test_env_load_missing_organization_id(self, mock_env_vars):
|
78
|
+
"""Test env_load behavior when organization_id is missing"""
|
79
|
+
# Remove organization ID from environment
|
80
|
+
if "ORGANIZATION_ID" in os.environ:
|
81
|
+
del os.environ["ORGANIZATION_ID"]
|
82
|
+
|
83
|
+
# Should not raise exception, just return without doing anything
|
84
|
+
env_load(project_id="project1")
|
85
|
+
|
86
|
+
# Verify no environment variables were set
|
87
|
+
assert "TEST_SECRET1" not in os.environ
|
88
|
+
assert "TEST_SECRET2" not in os.environ
|
89
|
+
|
90
|
+
def test_env_load_all_missing_organization_id(self, mock_env_vars):
|
91
|
+
"""Test env_load_all behavior when organization_id is missing"""
|
92
|
+
# Remove organization ID from environment
|
93
|
+
if "ORGANIZATION_ID" in os.environ:
|
94
|
+
del os.environ["ORGANIZATION_ID"]
|
95
|
+
|
96
|
+
# Should not raise exception, just return without doing anything
|
97
|
+
env_load_all()
|
98
|
+
|
99
|
+
# Verify no environment variables were set
|
100
|
+
assert "TEST_SECRET1" not in os.environ
|
101
|
+
assert "TEST_SECRET2" not in os.environ
|
@@ -0,0 +1,124 @@
|
|
1
|
+
import pytest
|
2
|
+
import os
|
3
|
+
from unittest.mock import patch, MagicMock
|
4
|
+
|
5
|
+
from toru_vault.in_memory import (
|
6
|
+
_encrypt_secret,
|
7
|
+
_decrypt_secret,
|
8
|
+
_encrypt_secrets,
|
9
|
+
_decrypt_secrets,
|
10
|
+
create_secrets_dict
|
11
|
+
)
|
12
|
+
|
13
|
+
class TestEncryption:
|
14
|
+
"""Tests for encryption/decryption implementation"""
|
15
|
+
|
16
|
+
def test_single_secret_encryption_decryption(self):
|
17
|
+
"""Test individual secret encryption and decryption"""
|
18
|
+
secret_value = "super-sensitive-value"
|
19
|
+
|
20
|
+
# Encrypt the secret
|
21
|
+
encrypted = _encrypt_secret(secret_value)
|
22
|
+
assert encrypted is not None
|
23
|
+
assert ":" in encrypted # Should contain salt and encrypted data
|
24
|
+
|
25
|
+
# Should not be the same as original
|
26
|
+
assert encrypted != secret_value
|
27
|
+
|
28
|
+
# Decrypt the secret
|
29
|
+
decrypted = _decrypt_secret(encrypted)
|
30
|
+
assert decrypted == secret_value
|
31
|
+
|
32
|
+
def test_multiple_secrets_encryption_decryption(self):
|
33
|
+
"""Test encryption and decryption of multiple secrets"""
|
34
|
+
secrets = {
|
35
|
+
"API_KEY": "secret-api-key",
|
36
|
+
"PASSWORD": "secret-password",
|
37
|
+
"TOKEN": "secret-token"
|
38
|
+
}
|
39
|
+
|
40
|
+
# Encrypt dictionary of secrets
|
41
|
+
encrypted_secrets = _encrypt_secrets(secrets)
|
42
|
+
assert encrypted_secrets is not None
|
43
|
+
|
44
|
+
# Ensure each value is encrypted
|
45
|
+
for key, value in encrypted_secrets.items():
|
46
|
+
assert ":" in value # Should contain salt and encrypted data
|
47
|
+
assert value != secrets[key] # Should not match original value
|
48
|
+
|
49
|
+
# Decrypt all secrets
|
50
|
+
decrypted_secrets = _decrypt_secrets(encrypted_secrets)
|
51
|
+
assert decrypted_secrets is not None
|
52
|
+
|
53
|
+
# Ensure decrypted values match original
|
54
|
+
for key, value in secrets.items():
|
55
|
+
assert decrypted_secrets[key] == value
|
56
|
+
|
57
|
+
def test_decryption_in_memory(self):
|
58
|
+
"""Test decryption in non-keyring mode"""
|
59
|
+
# Create test secrets
|
60
|
+
test_secrets = {
|
61
|
+
"API_KEY": "test-api-key",
|
62
|
+
"DB_PASSWORD": "test-db-password"
|
63
|
+
}
|
64
|
+
|
65
|
+
# Create secret keys set
|
66
|
+
secret_keys = set(test_secrets.keys())
|
67
|
+
|
68
|
+
# Create LazySecretsDict with in-memory decryption
|
69
|
+
secrets_dict = create_secrets_dict(
|
70
|
+
secret_keys,
|
71
|
+
"test_org_id",
|
72
|
+
"test_project_id",
|
73
|
+
test_secrets, # Direct use of secrets
|
74
|
+
False # No keyring
|
75
|
+
)
|
76
|
+
|
77
|
+
# Access each secret and verify correct values
|
78
|
+
for key, expected_value in test_secrets.items():
|
79
|
+
assert secrets_dict[key] == expected_value
|
80
|
+
|
81
|
+
@patch("keyring.get_password")
|
82
|
+
@patch("keyring.set_password")
|
83
|
+
def test_decryption_with_keyring(self, mock_set_password, mock_get_password):
|
84
|
+
"""Test decryption with keyring enabled"""
|
85
|
+
# Create test secrets
|
86
|
+
test_secrets = {
|
87
|
+
"API_KEY": "test-api-key",
|
88
|
+
"DB_PASSWORD": "test-db-password"
|
89
|
+
}
|
90
|
+
|
91
|
+
# Mock keyring to return encrypted values
|
92
|
+
def mock_get_side_effect(service_name, key):
|
93
|
+
# Return encrypted version of the test secret
|
94
|
+
if key in test_secrets:
|
95
|
+
return _encrypt_secret(test_secrets[key])
|
96
|
+
return None
|
97
|
+
|
98
|
+
mock_get_password.side_effect = mock_get_side_effect
|
99
|
+
|
100
|
+
# Create secret keys set
|
101
|
+
secret_keys = set(test_secrets.keys())
|
102
|
+
organization_id = "test_org_id"
|
103
|
+
project_id = "test_project_id"
|
104
|
+
|
105
|
+
# Create LazySecretsDict with keyring decryption
|
106
|
+
secrets_dict = create_secrets_dict(
|
107
|
+
secret_keys,
|
108
|
+
organization_id,
|
109
|
+
project_id,
|
110
|
+
test_secrets, # Used to initialize keyring
|
111
|
+
True # Use keyring
|
112
|
+
)
|
113
|
+
|
114
|
+
# Verify secrets were encrypted and stored in keyring
|
115
|
+
assert mock_set_password.call_count == len(test_secrets)
|
116
|
+
|
117
|
+
# Access each secret and verify decryption
|
118
|
+
for key, expected_value in test_secrets.items():
|
119
|
+
# Value should be decrypted from keyring when accessed
|
120
|
+
assert secrets_dict[key] == expected_value
|
121
|
+
|
122
|
+
|
123
|
+
|
124
|
+
|
@@ -0,0 +1,79 @@
|
|
1
|
+
import pytest
|
2
|
+
import os
|
3
|
+
from unittest.mock import patch, MagicMock
|
4
|
+
|
5
|
+
class TestVaultDecryption:
|
6
|
+
"""Test encryption/decryption with vault API functions"""
|
7
|
+
|
8
|
+
def test_get_in_memory(self, mock_initialize_client, mock_env_vars):
|
9
|
+
"""Test retrieval with get() using in-memory mode"""
|
10
|
+
from toru_vault import vault
|
11
|
+
|
12
|
+
# Get secrets with in-memory storage (no keyring)
|
13
|
+
secrets = vault.get("project1", use_keyring=False)
|
14
|
+
|
15
|
+
# Verify we got the expected keys
|
16
|
+
assert "TEST_SECRET1" in secrets
|
17
|
+
assert "TEST_SECRET2" in secrets
|
18
|
+
|
19
|
+
# Access secrets and verify values
|
20
|
+
assert secrets["TEST_SECRET1"] == "test_value1"
|
21
|
+
assert secrets["TEST_SECRET2"] == "test_value2"
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
@patch("keyring.get_password")
|
26
|
+
@patch("keyring.set_password")
|
27
|
+
def test_get_with_keyring(self, mock_set, mock_get, mock_initialize_client, mock_env_vars):
|
28
|
+
"""Test decryption with get() using keyring"""
|
29
|
+
from toru_vault import vault
|
30
|
+
from toru_vault.in_memory import _encrypt_secret, _decrypt_secret
|
31
|
+
|
32
|
+
# Setup keyring mock to simulate stored encrypted values
|
33
|
+
def mock_get_side_effect(service_name, key):
|
34
|
+
if key == "TEST_SECRET1":
|
35
|
+
return _encrypt_secret("test_value1")
|
36
|
+
elif key == "TEST_SECRET2":
|
37
|
+
return _encrypt_secret("test_value2")
|
38
|
+
return None
|
39
|
+
|
40
|
+
mock_get.side_effect = mock_get_side_effect
|
41
|
+
|
42
|
+
# Get secrets using keyring
|
43
|
+
secrets = vault.get("project1", use_keyring=True)
|
44
|
+
|
45
|
+
# Access both secrets sequentially to test individual decryption
|
46
|
+
value1 = secrets["TEST_SECRET1"]
|
47
|
+
assert value1 == "test_value1"
|
48
|
+
|
49
|
+
value2 = secrets["TEST_SECRET2"]
|
50
|
+
assert value2 == "test_value2"
|
51
|
+
|
52
|
+
# Verify we called get_password at least twice, plus once for org_id lookup
|
53
|
+
# Expected number: 2 secret lookups + 1 for ORGANIZATION_ID
|
54
|
+
assert mock_get.call_count >= 3
|
55
|
+
|
56
|
+
# Tests for env_load and env_load_all removed as they already exist in TestEnvLoad
|
57
|
+
|
58
|
+
def test_get_all(self, mock_initialize_client, mock_env_vars):
|
59
|
+
"""Test decryption with get_all()"""
|
60
|
+
with patch("toru_vault.vault.get") as mock_get:
|
61
|
+
from toru_vault import vault
|
62
|
+
|
63
|
+
# Setup mock get to return test secrets
|
64
|
+
mock_get.return_value = {
|
65
|
+
"TEST_SECRET1": "test_value1",
|
66
|
+
"TEST_SECRET2": "test_value2"
|
67
|
+
}
|
68
|
+
|
69
|
+
# Call get_all
|
70
|
+
all_secrets = vault.get_all(use_keyring=False)
|
71
|
+
|
72
|
+
# Verify get was called for the project
|
73
|
+
mock_get.assert_called_once_with("project1", use_keyring=False)
|
74
|
+
|
75
|
+
# Verify expected secrets are in result
|
76
|
+
assert "TEST_SECRET1" in all_secrets
|
77
|
+
assert all_secrets["TEST_SECRET1"] == "test_value1"
|
78
|
+
assert "TEST_SECRET2" in all_secrets
|
79
|
+
assert all_secrets["TEST_SECRET2"] == "test_value2"
|
@@ -7,9 +7,32 @@ import os
|
|
7
7
|
import sys
|
8
8
|
import getpass
|
9
9
|
|
10
|
-
from .vault import (_initialize_client,
|
11
|
-
_KEYRING_BWS_TOKEN_KEY, _KEYRING_ORG_ID_KEY,
|
12
|
-
_KEYRING_AVAILABLE)
|
10
|
+
from .vault import (_initialize_client, _get_from_keyring_or_env,
|
11
|
+
_KEYRING_SERVICE_NAME, _KEYRING_BWS_TOKEN_KEY, _KEYRING_ORG_ID_KEY,
|
12
|
+
_KEYRING_STATE_FILE_KEY, _KEYRING_AVAILABLE)
|
13
|
+
|
14
|
+
|
15
|
+
def _set_to_keyring(key, value):
|
16
|
+
"""
|
17
|
+
Set a value to keyring
|
18
|
+
|
19
|
+
Args:
|
20
|
+
key (str): Key in keyring
|
21
|
+
value (str): Value to store
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
bool: True if successful, False otherwise
|
25
|
+
"""
|
26
|
+
if not _KEYRING_AVAILABLE:
|
27
|
+
return False
|
28
|
+
|
29
|
+
try:
|
30
|
+
import keyring
|
31
|
+
keyring.set_password(_KEYRING_SERVICE_NAME, key, value)
|
32
|
+
return True
|
33
|
+
except Exception as e:
|
34
|
+
print(f"Failed to set {key} to keyring: {e}")
|
35
|
+
return False
|
13
36
|
|
14
37
|
|
15
38
|
def list_projects(organization_id=None):
|
@@ -103,21 +126,21 @@ def init_vault():
|
|
103
126
|
# Store in keyring
|
104
127
|
if _KEYRING_AVAILABLE:
|
105
128
|
if existing_token != token or not existing_token:
|
106
|
-
if
|
129
|
+
if _set_to_keyring(_KEYRING_BWS_TOKEN_KEY, token):
|
107
130
|
print("BWS_TOKEN stored in keyring")
|
108
131
|
else:
|
109
132
|
print("Failed to store BWS_TOKEN in keyring")
|
110
133
|
return False
|
111
134
|
|
112
135
|
if existing_org_id != org_id or not existing_org_id:
|
113
|
-
if
|
136
|
+
if _set_to_keyring(_KEYRING_ORG_ID_KEY, org_id):
|
114
137
|
print("ORGANIZATION_ID stored in keyring")
|
115
138
|
else:
|
116
139
|
print("Failed to store ORGANIZATION_ID in keyring")
|
117
140
|
return False
|
118
141
|
|
119
142
|
if existing_state_file != state_file or not existing_state_file:
|
120
|
-
if
|
143
|
+
if _set_to_keyring(_KEYRING_STATE_FILE_KEY, state_file):
|
121
144
|
print("STATE_FILE stored in keyring")
|
122
145
|
else:
|
123
146
|
print("Failed to store STATE_FILE in keyring")
|