aixtools 0.1.6__py3-none-any.whl → 0.1.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.
- aixtools/_version.py +2 -2
- aixtools/vault/vault.py +43 -16
- {aixtools-0.1.6.dist-info → aixtools-0.1.8.dist-info}/METADATA +1 -1
- {aixtools-0.1.6.dist-info → aixtools-0.1.8.dist-info}/RECORD +9 -9
- docker/mcp-base/Dockerfile +1 -1
- tests/unit/vault/test_vault.py +95 -38
- {aixtools-0.1.6.dist-info → aixtools-0.1.8.dist-info}/WHEEL +0 -0
- {aixtools-0.1.6.dist-info → aixtools-0.1.8.dist-info}/entry_points.txt +0 -0
- {aixtools-0.1.6.dist-info → aixtools-0.1.8.dist-info}/top_level.txt +0 -0
aixtools/_version.py
CHANGED
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.8'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 8)
|
|
33
33
|
|
|
34
34
|
__commit_id__ = commit_id = None
|
aixtools/vault/vault.py
CHANGED
|
@@ -4,7 +4,7 @@ Provides a Vault client for storing and retrieving user service api keys.
|
|
|
4
4
|
"""
|
|
5
5
|
|
|
6
6
|
import logging
|
|
7
|
-
from typing import Optional
|
|
7
|
+
from typing import Dict, Optional
|
|
8
8
|
|
|
9
9
|
import hvac
|
|
10
10
|
from hvac.exceptions import InvalidPath
|
|
@@ -27,47 +27,74 @@ class VaultClient:
|
|
|
27
27
|
if not self.client.is_authenticated():
|
|
28
28
|
raise VaultAuthError("Vault client authentication failed. Check vault_token.")
|
|
29
29
|
|
|
30
|
+
def _get_secret_path(self, user_id: str, service_name: str) -> str:
|
|
31
|
+
"""Generate the vault secret path for a user and service."""
|
|
32
|
+
return f"{config.VAULT_PATH_PREFIX}/{config.VAULT_ENV}/{user_id}/{service_name}"
|
|
33
|
+
|
|
30
34
|
def store_user_service_api_key(self, *, user_id: str, service_name: str, user_api_key: str):
|
|
31
35
|
"""
|
|
32
36
|
Store user's service api key in the Vault at the specified vault mount
|
|
33
37
|
point, where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
38
|
+
|
|
39
|
+
This is a convenience method for storing a single API key.
|
|
40
|
+
For storing multiple secrets, use store_user_service_secret().
|
|
41
|
+
"""
|
|
42
|
+
secret_dict = {"user-api-key": user_api_key}
|
|
43
|
+
self.store_user_service_secret(user_id=user_id, service_name=service_name, secret_data=secret_dict)
|
|
44
|
+
|
|
45
|
+
def read_user_service_api_key(self, *, user_id: str, service_name: str) -> Optional[str]:
|
|
46
|
+
"""
|
|
47
|
+
Read user's service api key in from vault at the specified mount point,
|
|
48
|
+
where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
49
|
+
|
|
50
|
+
This is a convenience method for reading a single API key.
|
|
51
|
+
For reading multiple secrets, use read_user_service_secret().
|
|
52
|
+
"""
|
|
53
|
+
secret_data = self.read_user_service_secret(user_id=user_id, service_name=service_name)
|
|
54
|
+
if secret_data is None:
|
|
55
|
+
return None
|
|
56
|
+
return secret_data.get("user-api-key")
|
|
57
|
+
|
|
58
|
+
def store_user_service_secret(self, *, user_id: str, service_name: str, secret_data: Dict[str, str]):
|
|
59
|
+
"""
|
|
60
|
+
Store complete user service secret with multiple key-value pairs in the Vault
|
|
61
|
+
at the specified vault mount point, where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
34
62
|
"""
|
|
35
63
|
secret_path = None
|
|
36
64
|
try:
|
|
37
|
-
secret_path =
|
|
38
|
-
|
|
39
|
-
secret_dict = {"user-api-key": user_api_key}
|
|
65
|
+
secret_path = self._get_secret_path(user_id, service_name)
|
|
66
|
+
logger.info("Writing complete secret to path %s", secret_path)
|
|
40
67
|
self.client.secrets.kv.v2.create_or_update_secret(
|
|
41
|
-
secret_path, secret=
|
|
68
|
+
secret_path, secret=secret_data, mount_point=config.VAULT_MOUNT_POINT
|
|
42
69
|
)
|
|
43
70
|
|
|
44
|
-
logger.info("
|
|
71
|
+
logger.info("Complete secret written to path %s", secret_path)
|
|
45
72
|
except Exception as e:
|
|
46
|
-
logger.error("Failed to write secret to path %s: %s", secret_path, str(e))
|
|
73
|
+
logger.error("Failed to write complete secret to path %s: %s", secret_path, str(e))
|
|
47
74
|
raise VaultAuthError(e) from e
|
|
48
75
|
|
|
49
|
-
def
|
|
76
|
+
def read_user_service_secret(self, *, user_id: str, service_name: str) -> Optional[Dict[str, str]]:
|
|
50
77
|
"""
|
|
51
|
-
Read user
|
|
78
|
+
Read complete user service secret from vault at the specified mount point,
|
|
52
79
|
where the path is <path_prefix>/<env>/<user_id>/<service_name>.
|
|
80
|
+
Returns all key-value pairs in the secret or None if the secret doesn't exist.
|
|
53
81
|
"""
|
|
54
82
|
secret_path = None
|
|
55
83
|
|
|
56
84
|
try:
|
|
57
|
-
secret_path =
|
|
58
|
-
logger.info("Reading secret from path %s", secret_path)
|
|
85
|
+
secret_path = self._get_secret_path(user_id, service_name)
|
|
86
|
+
logger.info("Reading complete secret from path %s", secret_path)
|
|
59
87
|
response = self.client.secrets.kv.v2.read_secret_version(
|
|
60
88
|
secret_path, mount_point=config.VAULT_MOUNT_POINT, raise_on_deleted_version=True
|
|
61
89
|
)
|
|
62
90
|
secret_data = response["data"]["data"]
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return user_api_key
|
|
91
|
+
logger.info("Complete secret read from path %s", secret_path)
|
|
92
|
+
return secret_data
|
|
66
93
|
except InvalidPath:
|
|
67
94
|
# Secret path does not exist
|
|
68
|
-
logger.warning("Secret path does not exist %s
|
|
95
|
+
logger.warning("Secret path does not exist %s", secret_path)
|
|
69
96
|
return None
|
|
70
97
|
|
|
71
98
|
except Exception as e:
|
|
72
|
-
logger.error("Failed to read secret from path %s: %s", secret_path, str(e))
|
|
99
|
+
logger.error("Failed to read complete secret from path %s: %s", secret_path, str(e))
|
|
73
100
|
raise VaultAuthError(e) from e
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
aixtools/__init__.py,sha256=9NGHm7LjsQmsvjTZvw6QFJexSvAU4bCoN_KBk9SCa00,260
|
|
2
|
-
aixtools/_version.py,sha256=
|
|
2
|
+
aixtools/_version.py,sha256=Zaz3s9gl_rzsS46-ymJOALojMxviW77EJq_agE8knLk,704
|
|
3
3
|
aixtools/app.py,sha256=JzQ0nrv_bjDQokllIlGHOV0HEb-V8N6k_nGQH-TEsVU,5227
|
|
4
4
|
aixtools/chainlit.md,sha256=yC37Ly57vjKyiIvK4oUvf4DYxZCwH7iocTlx7bLeGLU,761
|
|
5
5
|
aixtools/context.py,sha256=I_MD40ZnvRm5WPKAKqBUAdXIf8YaurkYUUHSVVy-QvU,598
|
|
@@ -77,8 +77,8 @@ aixtools/utils/utils.py,sha256=5911Ej1ES2NU_FKIWA3CWKhKnwgjvi1aDR2aiD6Xv3E,4880
|
|
|
77
77
|
aixtools/utils/chainlit/cl_agent_show.py,sha256=vaRuowp4BRvhxEr5hw0zHEJ7iaSF_5bo_9BH7pGPPpw,4398
|
|
78
78
|
aixtools/utils/chainlit/cl_utils.py,sha256=fxaxdkcZg6uHdM8uztxdPowg3a2f7VR7B26VPY4t-3c,5738
|
|
79
79
|
aixtools/vault/__init__.py,sha256=fsr_NuX3GZ9WZ7dGfe0gp_5-z3URxAfwVRXw7Xyc0dU,141
|
|
80
|
-
aixtools/vault/vault.py,sha256=
|
|
81
|
-
docker/mcp-base/Dockerfile,sha256=
|
|
80
|
+
aixtools/vault/vault.py,sha256=WkzBTEYM-Vqjyoa5x5imbEDi0ePBklMjD_aAdvIK-34,4293
|
|
81
|
+
docker/mcp-base/Dockerfile,sha256=UuAA1ltJpusKwow2fd_wS0M4DexbxyCzQGNVOK1_lzs,1547
|
|
82
82
|
notebooks/example_faulty_mcp_server.ipynb,sha256=b2Cy3GXfj-gOBZ7SoUzj25F1rxp5u-32EWPHWQ-sxn8,1729
|
|
83
83
|
notebooks/example_mcp_server_stdio.ipynb,sha256=ya4dRKNFU2vQxob-uIhKHGAzINXGQ6MehgKVmSCpHLk,1634
|
|
84
84
|
notebooks/example_raw_mcp_client.ipynb,sha256=uchaG-LuuwEpE2oIkmhZ2s1EDb19AgT1fUv2Jxtjgu8,1795
|
|
@@ -112,9 +112,9 @@ tests/unit/server/test_utils.py,sha256=kvhzdgNfsJl5tqcRBWg2yTR5GPpyrFCOmEIOuHb39
|
|
|
112
112
|
tests/unit/utils/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
113
113
|
tests/unit/utils/test_files.py,sha256=AKFmXQqXstyKd2PreE4EmQyhQYeqOmu1Sp80MwHrf_Q,5782
|
|
114
114
|
tests/unit/vault/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
115
|
-
tests/unit/vault/test_vault.py,sha256=
|
|
116
|
-
aixtools-0.1.
|
|
117
|
-
aixtools-0.1.
|
|
118
|
-
aixtools-0.1.
|
|
119
|
-
aixtools-0.1.
|
|
120
|
-
aixtools-0.1.
|
|
115
|
+
tests/unit/vault/test_vault.py,sha256=R_RTDsralvE0JhuIrHQTb85gU9ipDJD7aoYIMdcg0o4,7264
|
|
116
|
+
aixtools-0.1.8.dist-info/METADATA,sha256=aVW6zyI3cqprsWoTDOJCwXc-2tD2pMHb2bR6fc4Mo2w,18569
|
|
117
|
+
aixtools-0.1.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
118
|
+
aixtools-0.1.8.dist-info/entry_points.txt,sha256=dHoutULEZx7xXSqJrZdViSVjfInJibfLibi2nRXL3SE,56
|
|
119
|
+
aixtools-0.1.8.dist-info/top_level.txt,sha256=ee4eF-0pqu45zCUVml0mWIhnXQgqMQper2-49BBVHLY,40
|
|
120
|
+
aixtools-0.1.8.dist-info/RECORD,,
|
docker/mcp-base/Dockerfile
CHANGED
tests/unit/vault/test_vault.py
CHANGED
|
@@ -3,7 +3,7 @@ from unittest.mock import MagicMock, patch
|
|
|
3
3
|
import pytest
|
|
4
4
|
from hvac.exceptions import InvalidPath
|
|
5
5
|
|
|
6
|
-
from aixtools.vault.vault import
|
|
6
|
+
from aixtools.vault.vault import VaultAuthError, VaultClient
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
@pytest.fixture
|
|
@@ -25,24 +25,27 @@ def valid_params():
|
|
|
25
25
|
"env": "dev",
|
|
26
26
|
"user_id": "test-user",
|
|
27
27
|
"service_name": "test-service",
|
|
28
|
-
"user_api_key": "test-api-key"
|
|
28
|
+
"user_api_key": "test-api-key",
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
def test_store_user_service_api_key_success(patched_vault_client, valid_params):
|
|
33
33
|
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.return_value = {}
|
|
34
34
|
|
|
35
|
-
patched_vault_client.store_user_service_api_key(
|
|
36
|
-
|
|
37
|
-
|
|
35
|
+
patched_vault_client.store_user_service_api_key(
|
|
36
|
+
user_id=valid_params["user_id"],
|
|
37
|
+
service_name=valid_params["service_name"],
|
|
38
|
+
user_api_key=valid_params["user_api_key"],
|
|
39
|
+
)
|
|
38
40
|
|
|
39
|
-
secret_path =
|
|
41
|
+
secret_path = (
|
|
42
|
+
f"{valid_params['path_prefix']}/{valid_params['env']}/{valid_params['user_id']}/{valid_params['service_name']}"
|
|
43
|
+
)
|
|
40
44
|
|
|
41
|
-
print("expected secret path", secret_path)
|
|
42
45
|
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.assert_called_once_with(
|
|
43
46
|
secret_path,
|
|
44
47
|
secret={"user-api-key": valid_params["user_api_key"]},
|
|
45
|
-
mount_point=valid_params["vault_mount_point"]
|
|
48
|
+
mount_point=valid_params["vault_mount_point"],
|
|
46
49
|
)
|
|
47
50
|
|
|
48
51
|
|
|
@@ -50,33 +53,24 @@ def test_store_user_service_api_key_invalid_path(patched_vault_client, valid_par
|
|
|
50
53
|
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.side_effect = Exception("Invalid Path")
|
|
51
54
|
|
|
52
55
|
with pytest.raises(VaultAuthError, match="Invalid Path"):
|
|
53
|
-
patched_vault_client.store_user_service_api_key(
|
|
54
|
-
|
|
55
|
-
|
|
56
|
+
patched_vault_client.store_user_service_api_key(
|
|
57
|
+
user_id=valid_params["user_id"],
|
|
58
|
+
service_name=valid_params["service_name"],
|
|
59
|
+
user_api_key=valid_params["user_api_key"],
|
|
60
|
+
)
|
|
56
61
|
patched_vault_client.client.assert_not_called()
|
|
57
62
|
|
|
58
63
|
|
|
59
64
|
def test_read_user_service_api_key_success(patched_vault_client):
|
|
60
65
|
"""Test successful read of user service API key."""
|
|
61
|
-
mock_response = {
|
|
62
|
-
"data": {
|
|
63
|
-
"data": {
|
|
64
|
-
"user-api-key": "test-api-key"
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
}
|
|
66
|
+
mock_response = {"data": {"data": {"user-api-key": "test-api-key"}}}
|
|
68
67
|
patched_vault_client.client.secrets.kv.v2.read_secret_version.return_value = mock_response
|
|
69
68
|
|
|
70
|
-
result = patched_vault_client.read_user_service_api_key(
|
|
71
|
-
user_id="test-user",
|
|
72
|
-
service_name="test-service"
|
|
73
|
-
)
|
|
69
|
+
result = patched_vault_client.read_user_service_api_key(user_id="test-user", service_name="test-service")
|
|
74
70
|
|
|
75
71
|
assert result == "test-api-key"
|
|
76
72
|
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
77
|
-
"path/dev/test-user/test-service",
|
|
78
|
-
mount_point='secret',
|
|
79
|
-
raise_on_deleted_version=True
|
|
73
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
80
74
|
)
|
|
81
75
|
|
|
82
76
|
|
|
@@ -84,16 +78,11 @@ def test_read_user_service_api_key_secret_not_found(patched_vault_client):
|
|
|
84
78
|
"""Test read_user_service_api_key when the secret path does not exist."""
|
|
85
79
|
patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = InvalidPath
|
|
86
80
|
|
|
87
|
-
result = patched_vault_client.read_user_service_api_key(
|
|
88
|
-
user_id="test-user",
|
|
89
|
-
service_name="test-service"
|
|
90
|
-
)
|
|
81
|
+
result = patched_vault_client.read_user_service_api_key(user_id="test-user", service_name="test-service")
|
|
91
82
|
|
|
92
83
|
assert result is None
|
|
93
84
|
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
94
|
-
"path/dev/test-user/test-service",
|
|
95
|
-
mount_point="secret",
|
|
96
|
-
raise_on_deleted_version=True
|
|
85
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
97
86
|
)
|
|
98
87
|
|
|
99
88
|
|
|
@@ -102,13 +91,81 @@ def test_read_user_service_api_key_unexpected_error(patched_vault_client):
|
|
|
102
91
|
patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = Exception("Unexpected error")
|
|
103
92
|
|
|
104
93
|
with pytest.raises(VaultAuthError, match="Unexpected error"):
|
|
105
|
-
patched_vault_client.read_user_service_api_key(
|
|
106
|
-
|
|
107
|
-
|
|
94
|
+
patched_vault_client.read_user_service_api_key(user_id="test-user", service_name="test-service")
|
|
95
|
+
|
|
96
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
97
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_store_user_service_secret_success(patched_vault_client, valid_params):
|
|
102
|
+
"""Test successful storage of complete user service secret."""
|
|
103
|
+
secret_data = {"api_key": "test-api-key", "token": "test-token", "endpoint": "https://api.example.com"}
|
|
104
|
+
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.return_value = {}
|
|
105
|
+
|
|
106
|
+
patched_vault_client.store_user_service_secret(
|
|
107
|
+
user_id=valid_params["user_id"],
|
|
108
|
+
service_name=valid_params["service_name"],
|
|
109
|
+
secret_data=secret_data,
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
secret_path = (
|
|
113
|
+
f"{valid_params['path_prefix']}/{valid_params['env']}/{valid_params['user_id']}/{valid_params['service_name']}"
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.assert_called_once_with(
|
|
117
|
+
secret_path,
|
|
118
|
+
secret=secret_data,
|
|
119
|
+
mount_point=valid_params["vault_mount_point"],
|
|
120
|
+
)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def test_store_user_service_secret_error(patched_vault_client, valid_params):
|
|
124
|
+
"""Test store_user_service_secret when an error occurs."""
|
|
125
|
+
secret_data = {"api_key": "test-api-key", "token": "test-token"}
|
|
126
|
+
patched_vault_client.client.secrets.kv.v2.create_or_update_secret.side_effect = Exception("Storage error")
|
|
127
|
+
|
|
128
|
+
with pytest.raises(VaultAuthError, match="Storage error"):
|
|
129
|
+
patched_vault_client.store_user_service_secret(
|
|
130
|
+
user_id=valid_params["user_id"],
|
|
131
|
+
service_name=valid_params["service_name"],
|
|
132
|
+
secret_data=secret_data,
|
|
108
133
|
)
|
|
109
134
|
|
|
135
|
+
|
|
136
|
+
def test_read_user_service_secret_success(patched_vault_client):
|
|
137
|
+
"""Test successful read of complete user service secret."""
|
|
138
|
+
secret_data = {"api_key": "test-api-key", "token": "test-token", "endpoint": "https://api.example.com"}
|
|
139
|
+
mock_response = {"data": {"data": secret_data}}
|
|
140
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.return_value = mock_response
|
|
141
|
+
|
|
142
|
+
result = patched_vault_client.read_user_service_secret(user_id="test-user", service_name="test-service")
|
|
143
|
+
|
|
144
|
+
assert result == secret_data
|
|
145
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
146
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def test_read_user_service_secret_not_found(patched_vault_client):
|
|
151
|
+
"""Test read_user_service_secret when the secret path does not exist."""
|
|
152
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = InvalidPath
|
|
153
|
+
|
|
154
|
+
result = patched_vault_client.read_user_service_secret(user_id="test-user", service_name="test-service")
|
|
155
|
+
|
|
156
|
+
assert result is None
|
|
157
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
158
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def test_read_user_service_secret_error(patched_vault_client):
|
|
163
|
+
"""Test read_user_service_secret when an unexpected exception occurs."""
|
|
164
|
+
patched_vault_client.client.secrets.kv.v2.read_secret_version.side_effect = Exception("Read error")
|
|
165
|
+
|
|
166
|
+
with pytest.raises(VaultAuthError, match="Read error"):
|
|
167
|
+
patched_vault_client.read_user_service_secret(user_id="test-user", service_name="test-service")
|
|
168
|
+
|
|
110
169
|
patched_vault_client.client.secrets.kv.v2.read_secret_version.assert_called_once_with(
|
|
111
|
-
"path/dev/test-user/test-service",
|
|
112
|
-
mount_point="secret",
|
|
113
|
-
raise_on_deleted_version=True
|
|
170
|
+
"path/dev/test-user/test-service", mount_point="secret", raise_on_deleted_version=True
|
|
114
171
|
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|