akeyless-agentcore-runtime 0.2.0__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.
- akeyless_agentcore/__init__.py +17 -0
- akeyless_agentcore/auth.py +134 -0
- akeyless_agentcore/cache.py +40 -0
- akeyless_agentcore/client.py +363 -0
- akeyless_agentcore/config.py +168 -0
- akeyless_agentcore/paths.py +79 -0
- akeyless_agentcore/tools/__init__.py +31 -0
- akeyless_agentcore/tools/gateway.py +103 -0
- akeyless_agentcore/tools/mcp.py +70 -0
- akeyless_agentcore/tools/service.py +144 -0
- akeyless_agentcore/tools/strands.py +44 -0
- akeyless_agentcore_runtime-0.2.0.dist-info/METADATA +257 -0
- akeyless_agentcore_runtime-0.2.0.dist-info/RECORD +16 -0
- akeyless_agentcore_runtime-0.2.0.dist-info/WHEEL +4 -0
- akeyless_agentcore_runtime-0.2.0.dist-info/entry_points.txt +2 -0
- akeyless_agentcore_runtime-0.2.0.dist-info/licenses/LICENSE +19 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Fetch Akeyless secrets at runtime on AWS Bedrock AgentCore."""
|
|
2
|
+
|
|
3
|
+
from akeyless_agentcore.client import (
|
|
4
|
+
AkeylessRuntimeClient,
|
|
5
|
+
get_default_client,
|
|
6
|
+
get_secret,
|
|
7
|
+
get_secret_sync,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"AkeylessRuntimeClient",
|
|
12
|
+
"get_default_client",
|
|
13
|
+
"get_secret",
|
|
14
|
+
"get_secret_sync",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
__version__ = "0.2.0"
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
"""Authenticate to Akeyless using cloud identity or other configured methods."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from datetime import datetime, timezone
|
|
8
|
+
|
|
9
|
+
import akeyless
|
|
10
|
+
from akeyless_cloud_id import CloudId
|
|
11
|
+
|
|
12
|
+
from akeyless_agentcore.config import AkeylessRuntimeConfig
|
|
13
|
+
|
|
14
|
+
ACCESS_KEY_DEFAULT_TTL_SECONDS = 14 * 60
|
|
15
|
+
ENV_TOKEN_TTL_SECONDS = 50 * 60
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class AuthSession:
|
|
20
|
+
token: str
|
|
21
|
+
expires_at: float
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _expiry_from_auth_output(auth_out: akeyless.AuthOutput, margin_seconds: float) -> float:
|
|
25
|
+
now = time.monotonic()
|
|
26
|
+
expiration = getattr(auth_out, "expiration", None)
|
|
27
|
+
if not expiration:
|
|
28
|
+
return now + ACCESS_KEY_DEFAULT_TTL_SECONDS - margin_seconds
|
|
29
|
+
|
|
30
|
+
raw = str(expiration).strip()
|
|
31
|
+
if not raw:
|
|
32
|
+
return now + ACCESS_KEY_DEFAULT_TTL_SECONDS - margin_seconds
|
|
33
|
+
|
|
34
|
+
try:
|
|
35
|
+
as_num = float(raw)
|
|
36
|
+
if as_num > 1e12:
|
|
37
|
+
# Epoch milliseconds
|
|
38
|
+
return (as_num / 1000.0) - time.time() + now - margin_seconds
|
|
39
|
+
except ValueError:
|
|
40
|
+
pass
|
|
41
|
+
|
|
42
|
+
try:
|
|
43
|
+
as_date = datetime.fromisoformat(raw.replace("Z", "+00:00"))
|
|
44
|
+
if as_date.tzinfo is None:
|
|
45
|
+
as_date = as_date.replace(tzinfo=timezone.utc)
|
|
46
|
+
return as_date.timestamp() - time.time() + now - margin_seconds
|
|
47
|
+
except ValueError:
|
|
48
|
+
pass
|
|
49
|
+
|
|
50
|
+
return now + ACCESS_KEY_DEFAULT_TTL_SECONDS - margin_seconds
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def _get_cloud_id(config: AkeylessRuntimeConfig) -> str:
|
|
54
|
+
if config.cloud_id and config.cloud_id.strip():
|
|
55
|
+
return config.cloud_id.strip()
|
|
56
|
+
|
|
57
|
+
provider = config.cloud_provider or config.access_type
|
|
58
|
+
if provider not in ("aws_iam", "azure_ad", "gcp"):
|
|
59
|
+
raise ValueError(f"Cannot generate cloud ID for access type {provider}")
|
|
60
|
+
|
|
61
|
+
cloud_id = CloudId().generate()
|
|
62
|
+
if not cloud_id or not str(cloud_id).strip():
|
|
63
|
+
raise ValueError(
|
|
64
|
+
f"akeyless-cloud-id returned an empty cloud ID for provider {provider!r}. "
|
|
65
|
+
"On AgentCore Runtime, ensure the execution role has ambient AWS credentials "
|
|
66
|
+
"or pass cloud_id explicitly."
|
|
67
|
+
)
|
|
68
|
+
return str(cloud_id).strip()
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def _sdk_host(gateway_url: str) -> str:
|
|
72
|
+
host = gateway_url.rstrip("/")
|
|
73
|
+
if host.endswith("/api/v2"):
|
|
74
|
+
if "api.akeyless.io" in host:
|
|
75
|
+
return host[: -len("/api/v2")]
|
|
76
|
+
return host
|
|
77
|
+
if host.endswith("/v2"):
|
|
78
|
+
return host
|
|
79
|
+
if "api.akeyless.io" in host:
|
|
80
|
+
return host
|
|
81
|
+
return f"{host}/api/v2"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def create_v2_api(gateway_url: str) -> akeyless.V2Api:
|
|
85
|
+
configuration = akeyless.Configuration(host=_sdk_host(gateway_url))
|
|
86
|
+
return akeyless.V2Api(akeyless.ApiClient(configuration))
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def authenticate(api: akeyless.V2Api, config: AkeylessRuntimeConfig) -> AuthSession:
|
|
90
|
+
now = time.monotonic()
|
|
91
|
+
|
|
92
|
+
if config.token and config.token.strip():
|
|
93
|
+
return AuthSession(
|
|
94
|
+
token=config.token.strip(),
|
|
95
|
+
expires_at=now + ENV_TOKEN_TTL_SECONDS,
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
access_type = config.access_type
|
|
99
|
+
|
|
100
|
+
if access_type in ("access_key", "api_key"):
|
|
101
|
+
body = akeyless.Auth(
|
|
102
|
+
access_id=config.access_id,
|
|
103
|
+
access_type=access_type,
|
|
104
|
+
access_key=config.access_key,
|
|
105
|
+
)
|
|
106
|
+
elif access_type == "universal_identity":
|
|
107
|
+
body = akeyless.Auth(
|
|
108
|
+
access_type="universal_identity",
|
|
109
|
+
uid_token=config.uid_token,
|
|
110
|
+
)
|
|
111
|
+
elif access_type == "jwt":
|
|
112
|
+
body = akeyless.Auth(
|
|
113
|
+
access_id=config.access_id,
|
|
114
|
+
access_type="jwt",
|
|
115
|
+
jwt=config.jwt,
|
|
116
|
+
)
|
|
117
|
+
elif access_type in ("aws_iam", "azure_ad", "gcp"):
|
|
118
|
+
body = akeyless.Auth(
|
|
119
|
+
access_id=config.access_id,
|
|
120
|
+
access_type=access_type,
|
|
121
|
+
cloud_id=_get_cloud_id(config),
|
|
122
|
+
)
|
|
123
|
+
else:
|
|
124
|
+
raise ValueError(f"Unsupported access type: {access_type}")
|
|
125
|
+
|
|
126
|
+
auth_out = api.auth(body)
|
|
127
|
+
token = getattr(auth_out, "token", None)
|
|
128
|
+
if not token or not str(token).strip():
|
|
129
|
+
raise ValueError("Akeyless authentication did not return a token")
|
|
130
|
+
|
|
131
|
+
return AuthSession(
|
|
132
|
+
token=str(token).strip(),
|
|
133
|
+
expires_at=_expiry_from_auth_output(auth_out, config.token_expiry_margin_seconds),
|
|
134
|
+
)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""In-memory TTL cache for secret values and auth tokens."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import time
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Generic, TypeVar
|
|
8
|
+
|
|
9
|
+
T = TypeVar("T")
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
@dataclass
|
|
13
|
+
class _CacheEntry(Generic[T]):
|
|
14
|
+
value: T
|
|
15
|
+
expires_at: float
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TtlCache(Generic[T]):
|
|
19
|
+
def __init__(self) -> None:
|
|
20
|
+
self._entries: dict[str, _CacheEntry[T]] = {}
|
|
21
|
+
|
|
22
|
+
def get(self, key: str, now: float | None = None) -> T | None:
|
|
23
|
+
now = time.monotonic() if now is None else now
|
|
24
|
+
entry = self._entries.get(key)
|
|
25
|
+
if entry is None:
|
|
26
|
+
return None
|
|
27
|
+
if now >= entry.expires_at:
|
|
28
|
+
del self._entries[key]
|
|
29
|
+
return None
|
|
30
|
+
return entry.value
|
|
31
|
+
|
|
32
|
+
def set(self, key: str, value: T, ttl_seconds: float, now: float | None = None) -> None:
|
|
33
|
+
now = time.monotonic() if now is None else now
|
|
34
|
+
self._entries[key] = _CacheEntry(value=value, expires_at=now + ttl_seconds)
|
|
35
|
+
|
|
36
|
+
def delete(self, key: str) -> None:
|
|
37
|
+
self._entries.pop(key, None)
|
|
38
|
+
|
|
39
|
+
def clear(self) -> None:
|
|
40
|
+
self._entries.clear()
|
|
@@ -0,0 +1,363 @@
|
|
|
1
|
+
"""Akeyless runtime client for Bedrock AgentCore agents."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import time
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
import akeyless
|
|
11
|
+
|
|
12
|
+
from akeyless_agentcore.auth import AuthSession, authenticate, create_v2_api
|
|
13
|
+
from akeyless_agentcore.cache import TtlCache
|
|
14
|
+
from akeyless_agentcore.config import AkeylessRuntimeConfig, config_from_env, validate_config
|
|
15
|
+
from akeyless_agentcore.paths import (
|
|
16
|
+
format_structured_response,
|
|
17
|
+
join_secret_path,
|
|
18
|
+
normalize_path,
|
|
19
|
+
pick_secret_from_response,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _is_not_found_error(error: Exception) -> bool:
|
|
24
|
+
status = getattr(error, "status", None)
|
|
25
|
+
if status == 404:
|
|
26
|
+
return True
|
|
27
|
+
message = str(error).lower()
|
|
28
|
+
return "not found" in message or "404" in message
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class GetSecretOptions:
|
|
33
|
+
path: str | None = None
|
|
34
|
+
version: int | None = None
|
|
35
|
+
json: bool = False
|
|
36
|
+
ignore_cache: bool = False
|
|
37
|
+
allow_dynamic_fallback: bool = False
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
@dataclass
|
|
41
|
+
class DynamicSecretOptions:
|
|
42
|
+
path: str | None = None
|
|
43
|
+
timeout: int | None = None
|
|
44
|
+
args: list[str] | None = None
|
|
45
|
+
host: str | None = None
|
|
46
|
+
dbname: str | None = None
|
|
47
|
+
target: str | None = None
|
|
48
|
+
json: bool = False
|
|
49
|
+
ignore_cache: bool = False
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
@dataclass
|
|
53
|
+
class RotatedSecretOptions:
|
|
54
|
+
path: str | None = None
|
|
55
|
+
host: str | None = None
|
|
56
|
+
version: int | None = None
|
|
57
|
+
json: bool = False
|
|
58
|
+
ignore_cache: bool = False
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class AkeylessRuntimeClient:
|
|
62
|
+
def __init__(self, **config_overrides: Any) -> None:
|
|
63
|
+
self._config = config_from_env(**config_overrides)
|
|
64
|
+
validate_config(self._config)
|
|
65
|
+
self._api = create_v2_api(self._config.gateway_url)
|
|
66
|
+
self._session: AuthSession | None = None
|
|
67
|
+
self._secret_cache: TtlCache[str] = TtlCache()
|
|
68
|
+
|
|
69
|
+
@classmethod
|
|
70
|
+
def from_env(cls, **overrides: Any) -> AkeylessRuntimeClient:
|
|
71
|
+
return cls(**overrides)
|
|
72
|
+
|
|
73
|
+
@property
|
|
74
|
+
def config(self) -> AkeylessRuntimeConfig:
|
|
75
|
+
return self._config
|
|
76
|
+
|
|
77
|
+
def resolve_path(self, name_or_path: str) -> str:
|
|
78
|
+
trimmed = name_or_path.strip()
|
|
79
|
+
if trimmed.startswith("/"):
|
|
80
|
+
return normalize_path(trimmed)
|
|
81
|
+
return join_secret_path(self._config.secret_prefix, trimmed)
|
|
82
|
+
|
|
83
|
+
def clear_cache(self) -> None:
|
|
84
|
+
self._secret_cache.clear()
|
|
85
|
+
self._session = None
|
|
86
|
+
|
|
87
|
+
def _cache_key(self, path: str, kind: str, extra: str = "") -> str:
|
|
88
|
+
return f"{kind}:{path}:{extra}"
|
|
89
|
+
|
|
90
|
+
def _read_cached(self, path: str, kind: str, ignore_cache: bool) -> str | None:
|
|
91
|
+
if ignore_cache:
|
|
92
|
+
return None
|
|
93
|
+
return self._secret_cache.get(self._cache_key(path, kind))
|
|
94
|
+
|
|
95
|
+
def _write_cached(self, path: str, kind: str, value: str) -> None:
|
|
96
|
+
self._secret_cache.set(
|
|
97
|
+
self._cache_key(path, kind),
|
|
98
|
+
value,
|
|
99
|
+
self._config.secret_cache_ttl_seconds,
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
def _get_token(self) -> str:
|
|
103
|
+
now = time.monotonic()
|
|
104
|
+
if self._session and now < self._session.expires_at:
|
|
105
|
+
return self._session.token
|
|
106
|
+
|
|
107
|
+
self._session = authenticate(self._api, self._config)
|
|
108
|
+
return self._session.token
|
|
109
|
+
|
|
110
|
+
async def get_secret_at_path(
|
|
111
|
+
self,
|
|
112
|
+
path: str,
|
|
113
|
+
*,
|
|
114
|
+
version: int | None = None,
|
|
115
|
+
json: bool = False,
|
|
116
|
+
ignore_cache: bool = False,
|
|
117
|
+
allow_dynamic_fallback: bool = False,
|
|
118
|
+
) -> str:
|
|
119
|
+
return self.get_secret_at_path_sync(
|
|
120
|
+
path,
|
|
121
|
+
version=version,
|
|
122
|
+
json=json,
|
|
123
|
+
ignore_cache=ignore_cache,
|
|
124
|
+
allow_dynamic_fallback=allow_dynamic_fallback,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
def get_secret_at_path_sync(
|
|
128
|
+
self,
|
|
129
|
+
path: str,
|
|
130
|
+
*,
|
|
131
|
+
version: int | None = None,
|
|
132
|
+
json: bool = False,
|
|
133
|
+
ignore_cache: bool = False,
|
|
134
|
+
allow_dynamic_fallback: bool = False,
|
|
135
|
+
) -> str:
|
|
136
|
+
resolved = normalize_path(path)
|
|
137
|
+
cached = self._read_cached(resolved, "static", ignore_cache)
|
|
138
|
+
if cached is not None:
|
|
139
|
+
return cached
|
|
140
|
+
|
|
141
|
+
token = self._get_token()
|
|
142
|
+
body_kwargs: dict[str, Any] = {"names": [resolved], "token": token}
|
|
143
|
+
if version is not None:
|
|
144
|
+
body_kwargs["version"] = version
|
|
145
|
+
if json:
|
|
146
|
+
body_kwargs["json"] = True
|
|
147
|
+
if ignore_cache:
|
|
148
|
+
body_kwargs["ignore_cache"] = "true"
|
|
149
|
+
|
|
150
|
+
try:
|
|
151
|
+
raw = self._api.get_secret_value(akeyless.GetSecretValue(**body_kwargs))
|
|
152
|
+
if isinstance(raw, dict):
|
|
153
|
+
value = pick_secret_from_response(resolved, raw)
|
|
154
|
+
else:
|
|
155
|
+
value = format_structured_response(raw)
|
|
156
|
+
self._write_cached(resolved, "static", value)
|
|
157
|
+
return value
|
|
158
|
+
except Exception as error:
|
|
159
|
+
if allow_dynamic_fallback and _is_not_found_error(error):
|
|
160
|
+
return self.get_dynamic_secret_at_path_sync(
|
|
161
|
+
resolved,
|
|
162
|
+
ignore_cache=ignore_cache,
|
|
163
|
+
)
|
|
164
|
+
raise
|
|
165
|
+
|
|
166
|
+
async def get_secret(
|
|
167
|
+
self,
|
|
168
|
+
name: str,
|
|
169
|
+
options: GetSecretOptions | None = None,
|
|
170
|
+
) -> str:
|
|
171
|
+
return self.get_secret_sync(name, options)
|
|
172
|
+
|
|
173
|
+
def get_secret_sync(
|
|
174
|
+
self,
|
|
175
|
+
name: str,
|
|
176
|
+
options: GetSecretOptions | None = None,
|
|
177
|
+
) -> str:
|
|
178
|
+
opts = options or GetSecretOptions()
|
|
179
|
+
path = opts.path or self.resolve_path(name)
|
|
180
|
+
return self.get_secret_at_path_sync(
|
|
181
|
+
path,
|
|
182
|
+
version=opts.version,
|
|
183
|
+
json=opts.json,
|
|
184
|
+
ignore_cache=opts.ignore_cache,
|
|
185
|
+
allow_dynamic_fallback=opts.allow_dynamic_fallback,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
def get_secret_json_sync(
|
|
189
|
+
self,
|
|
190
|
+
name: str,
|
|
191
|
+
options: GetSecretOptions | None = None,
|
|
192
|
+
) -> dict[str, Any]:
|
|
193
|
+
raw = self.get_secret_sync(name, options)
|
|
194
|
+
data = json.loads(raw)
|
|
195
|
+
if not isinstance(data, dict):
|
|
196
|
+
raise ValueError(f"Secret {name!r} is not a JSON object")
|
|
197
|
+
return data
|
|
198
|
+
|
|
199
|
+
async def get_dynamic_secret_at_path(
|
|
200
|
+
self,
|
|
201
|
+
path: str,
|
|
202
|
+
options: DynamicSecretOptions | None = None,
|
|
203
|
+
) -> str:
|
|
204
|
+
return self.get_dynamic_secret_at_path_sync(path, options)
|
|
205
|
+
|
|
206
|
+
def get_dynamic_secret_at_path_sync(
|
|
207
|
+
self,
|
|
208
|
+
path: str,
|
|
209
|
+
options: DynamicSecretOptions | None = None,
|
|
210
|
+
) -> str:
|
|
211
|
+
opts = options or DynamicSecretOptions()
|
|
212
|
+
resolved = normalize_path(path)
|
|
213
|
+
should_cache = not any(
|
|
214
|
+
[
|
|
215
|
+
opts.args,
|
|
216
|
+
opts.timeout is not None,
|
|
217
|
+
opts.host,
|
|
218
|
+
opts.dbname,
|
|
219
|
+
opts.target,
|
|
220
|
+
]
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if should_cache:
|
|
224
|
+
cached = self._read_cached(resolved, "dynamic", opts.ignore_cache)
|
|
225
|
+
if cached is not None:
|
|
226
|
+
return cached
|
|
227
|
+
|
|
228
|
+
token = self._get_token()
|
|
229
|
+
body_kwargs: dict[str, Any] = {"name": resolved, "token": token}
|
|
230
|
+
if opts.timeout is not None:
|
|
231
|
+
body_kwargs["timeout"] = opts.timeout
|
|
232
|
+
if opts.args:
|
|
233
|
+
body_kwargs["args"] = opts.args
|
|
234
|
+
if opts.host:
|
|
235
|
+
body_kwargs["host"] = opts.host
|
|
236
|
+
if opts.dbname:
|
|
237
|
+
body_kwargs["dbname"] = opts.dbname
|
|
238
|
+
if opts.target:
|
|
239
|
+
body_kwargs["target"] = opts.target
|
|
240
|
+
if opts.json:
|
|
241
|
+
body_kwargs["json"] = True
|
|
242
|
+
if opts.ignore_cache:
|
|
243
|
+
body_kwargs["ignore_cache"] = "true"
|
|
244
|
+
|
|
245
|
+
raw = self._api.get_dynamic_secret_value(akeyless.GetDynamicSecretValue(**body_kwargs))
|
|
246
|
+
value = format_structured_response(raw)
|
|
247
|
+
|
|
248
|
+
if should_cache:
|
|
249
|
+
self._write_cached(resolved, "dynamic", value)
|
|
250
|
+
return value
|
|
251
|
+
|
|
252
|
+
async def get_dynamic_secret(
|
|
253
|
+
self,
|
|
254
|
+
name: str,
|
|
255
|
+
options: DynamicSecretOptions | None = None,
|
|
256
|
+
) -> str:
|
|
257
|
+
return self.get_dynamic_secret_sync(name, options)
|
|
258
|
+
|
|
259
|
+
def get_dynamic_secret_sync(
|
|
260
|
+
self,
|
|
261
|
+
name: str,
|
|
262
|
+
options: DynamicSecretOptions | None = None,
|
|
263
|
+
) -> str:
|
|
264
|
+
opts = options or DynamicSecretOptions()
|
|
265
|
+
path = opts.path or self.resolve_path(name)
|
|
266
|
+
return self.get_dynamic_secret_at_path_sync(path, opts)
|
|
267
|
+
|
|
268
|
+
async def get_rotated_secret_at_path(
|
|
269
|
+
self,
|
|
270
|
+
path: str,
|
|
271
|
+
options: RotatedSecretOptions | None = None,
|
|
272
|
+
) -> str:
|
|
273
|
+
return self.get_rotated_secret_at_path_sync(path, options)
|
|
274
|
+
|
|
275
|
+
def get_rotated_secret_at_path_sync(
|
|
276
|
+
self,
|
|
277
|
+
path: str,
|
|
278
|
+
options: RotatedSecretOptions | None = None,
|
|
279
|
+
) -> str:
|
|
280
|
+
opts = options or RotatedSecretOptions()
|
|
281
|
+
resolved = normalize_path(path)
|
|
282
|
+
cached = self._read_cached(resolved, "rotated", opts.ignore_cache)
|
|
283
|
+
if cached is not None:
|
|
284
|
+
return cached
|
|
285
|
+
|
|
286
|
+
token = self._get_token()
|
|
287
|
+
body_kwargs: dict[str, Any] = {"names": resolved, "token": token}
|
|
288
|
+
if opts.host:
|
|
289
|
+
body_kwargs["host"] = opts.host
|
|
290
|
+
if opts.version is not None:
|
|
291
|
+
body_kwargs["version"] = opts.version
|
|
292
|
+
if opts.json:
|
|
293
|
+
body_kwargs["json"] = True
|
|
294
|
+
if opts.ignore_cache:
|
|
295
|
+
body_kwargs["ignore_cache"] = "true"
|
|
296
|
+
|
|
297
|
+
raw = self._api.get_rotated_secret_value(akeyless.GetRotatedSecretValue(**body_kwargs))
|
|
298
|
+
value = format_structured_response(raw)
|
|
299
|
+
self._write_cached(resolved, "rotated", value)
|
|
300
|
+
return value
|
|
301
|
+
|
|
302
|
+
async def get_rotated_secret(
|
|
303
|
+
self,
|
|
304
|
+
name: str,
|
|
305
|
+
options: RotatedSecretOptions | None = None,
|
|
306
|
+
) -> str:
|
|
307
|
+
return self.get_rotated_secret_sync(name, options)
|
|
308
|
+
|
|
309
|
+
def get_rotated_secret_sync(
|
|
310
|
+
self,
|
|
311
|
+
name: str,
|
|
312
|
+
options: RotatedSecretOptions | None = None,
|
|
313
|
+
) -> str:
|
|
314
|
+
opts = options or RotatedSecretOptions()
|
|
315
|
+
path = opts.path or self.resolve_path(name)
|
|
316
|
+
return self.get_rotated_secret_at_path_sync(path, opts)
|
|
317
|
+
|
|
318
|
+
def list_secrets_sync(self, prefix: str | None = None) -> list[str]:
|
|
319
|
+
"""List secret names under a prefix. Returns paths only, never values."""
|
|
320
|
+
resolved = normalize_path(prefix or self._config.secret_prefix)
|
|
321
|
+
token = self._get_token()
|
|
322
|
+
result = self._api.list_items(
|
|
323
|
+
akeyless.ListItems(
|
|
324
|
+
path=resolved,
|
|
325
|
+
token=token,
|
|
326
|
+
minimal_view=True,
|
|
327
|
+
auto_pagination="enabled",
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
|
|
331
|
+
items = getattr(result, "items", None) or []
|
|
332
|
+
names: list[str] = []
|
|
333
|
+
for item in items:
|
|
334
|
+
if isinstance(item, dict):
|
|
335
|
+
name = item.get("item_name")
|
|
336
|
+
else:
|
|
337
|
+
name = getattr(item, "item_name", None)
|
|
338
|
+
if name:
|
|
339
|
+
names.append(str(name))
|
|
340
|
+
return sorted(names)
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
_default_client: AkeylessRuntimeClient | None = None
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
def get_default_client(**overrides: Any) -> AkeylessRuntimeClient:
|
|
347
|
+
global _default_client
|
|
348
|
+
if _default_client is None:
|
|
349
|
+
_default_client = AkeylessRuntimeClient(**overrides)
|
|
350
|
+
return _default_client
|
|
351
|
+
|
|
352
|
+
|
|
353
|
+
def reset_default_client() -> None:
|
|
354
|
+
global _default_client
|
|
355
|
+
_default_client = None
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
async def get_secret(name: str, options: GetSecretOptions | None = None) -> str:
|
|
359
|
+
return await get_default_client().get_secret(name, options)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def get_secret_sync(name: str, options: GetSecretOptions | None = None) -> str:
|
|
363
|
+
return get_default_client().get_secret_sync(name, options)
|