generated from coulomb/repo-seed
ARTIFACT-STORE-WP-0007 D7.4: STS temporary credential support (session token + refreshable file refs)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -46,16 +46,24 @@ def _build_backends(settings: Settings) -> dict[str, StorageBackend]:
|
||||
if "local" in configured:
|
||||
backends["local"] = LocalBackend(settings.storage_local_root, backend_id="local")
|
||||
if "s3" in configured:
|
||||
access_key = (
|
||||
resolve_secret_ref(settings.s3_access_key_ref)
|
||||
if settings.s3_access_key_ref
|
||||
else None
|
||||
)
|
||||
secret_key = (
|
||||
resolve_secret_ref(settings.s3_secret_key_ref)
|
||||
if settings.s3_secret_key_ref
|
||||
else None
|
||||
refs = (
|
||||
settings.s3_access_key_ref,
|
||||
settings.s3_secret_key_ref,
|
||||
settings.s3_session_token_ref,
|
||||
)
|
||||
|
||||
def _resolve_credentials() -> tuple[str | None, str | None, str | None]:
|
||||
access_ref, secret_ref, token_ref = refs
|
||||
return (
|
||||
resolve_secret_ref(access_ref) if access_ref else None,
|
||||
resolve_secret_ref(secret_ref) if secret_ref else None,
|
||||
resolve_secret_ref(token_ref) if token_ref else None,
|
||||
)
|
||||
|
||||
access_key, secret_key, session_token = _resolve_credentials()
|
||||
# file: refs are re-read per client so an STS sidecar/controller can
|
||||
# rotate all three values atomically without a process restart.
|
||||
refreshable = any(ref.startswith("file:") for ref in refs if ref)
|
||||
backends["s3"] = S3Backend(
|
||||
S3BackendConfig(
|
||||
endpoint_url=settings.s3_endpoint_url,
|
||||
@@ -64,11 +72,13 @@ def _build_backends(settings: Settings) -> dict[str, StorageBackend]:
|
||||
key_prefix=settings.s3_key_prefix,
|
||||
access_key_id=access_key,
|
||||
secret_access_key=secret_key,
|
||||
session_token=session_token,
|
||||
storage_class=settings.s3_storage_class or None,
|
||||
sse=settings.s3_sse or None,
|
||||
multipart_threshold_bytes=settings.s3_multipart_threshold_bytes,
|
||||
multipart_chunk_bytes=settings.s3_multipart_chunk_bytes,
|
||||
)
|
||||
),
|
||||
credentials_provider=_resolve_credentials if refreshable else None,
|
||||
)
|
||||
unknown = set(configured) - set(backends)
|
||||
if unknown:
|
||||
|
||||
@@ -62,6 +62,7 @@ class Settings(BaseSettings):
|
||||
s3_key_prefix: str = ""
|
||||
s3_access_key_ref: str = ""
|
||||
s3_secret_key_ref: str = ""
|
||||
s3_session_token_ref: str = ""
|
||||
s3_storage_class: str = ""
|
||||
s3_sse: str = ""
|
||||
s3_multipart_threshold_bytes: int = 64 * 1024 * 1024
|
||||
|
||||
@@ -29,6 +29,7 @@ class S3BackendConfig:
|
||||
key_prefix: str = ""
|
||||
access_key_id: str | None = None
|
||||
secret_access_key: str | None = None
|
||||
session_token: str | None = None
|
||||
storage_class: str | None = None
|
||||
sse: str | None = None
|
||||
multipart_threshold_bytes: int = 64 * 1024 * 1024
|
||||
@@ -37,6 +38,11 @@ class S3BackendConfig:
|
||||
|
||||
ClientFactory = Callable[[], AbstractAsyncContextManager[Any]]
|
||||
|
||||
# Returns (access_key_id, secret_access_key, session_token) for each new
|
||||
# client, so STS-vended credentials rotated by a sidecar/controller (e.g.
|
||||
# re-written mounted files) are picked up without a process restart.
|
||||
CredentialsProvider = Callable[[], tuple[str | None, str | None, str | None]]
|
||||
|
||||
|
||||
class S3Backend:
|
||||
"""Storage SPI implementation over an S3-compatible object store."""
|
||||
@@ -47,6 +53,7 @@ class S3Backend:
|
||||
*,
|
||||
backend_id: str = "s3",
|
||||
client_factory: ClientFactory | None = None,
|
||||
credentials_provider: CredentialsProvider | None = None,
|
||||
chunk_size: int = _DEFAULT_CHUNK_SIZE,
|
||||
) -> None:
|
||||
if not config.bucket:
|
||||
@@ -54,6 +61,7 @@ class S3Backend:
|
||||
self._config = config
|
||||
self._backend_id = backend_id
|
||||
self._client_factory = client_factory
|
||||
self._credentials_provider = credentials_provider
|
||||
self._chunk_size = chunk_size
|
||||
|
||||
@property
|
||||
@@ -69,9 +77,16 @@ class S3Backend:
|
||||
raise RuntimeError(
|
||||
"S3Backend requires the 'aioboto3' package; install artifactstore[s3]"
|
||||
) from exc
|
||||
if self._credentials_provider is not None:
|
||||
access_key_id, secret_access_key, session_token = self._credentials_provider()
|
||||
else:
|
||||
access_key_id = self._config.access_key_id
|
||||
secret_access_key = self._config.secret_access_key
|
||||
session_token = self._config.session_token
|
||||
session = aioboto3.Session(
|
||||
aws_access_key_id=self._config.access_key_id,
|
||||
aws_secret_access_key=self._config.secret_access_key,
|
||||
aws_access_key_id=access_key_id,
|
||||
aws_secret_access_key=secret_access_key,
|
||||
aws_session_token=session_token,
|
||||
region_name=self._config.region,
|
||||
)
|
||||
return cast(
|
||||
|
||||
Reference in New Issue
Block a user