Design: credential and endpoint resolution — robust, flexible, ready-to-go #15
Replies: 1 comment
Resolved — ADR-0012, merged in #18Three of this discussion's premises did not survive measurement. All checked on botocore 1. "The obvious fix inverts v0's precedence" — nobotocore already ranks v1 re-ranks nothing; it stops discarding an argument. That deletes the entire "own a 2. The SigV2 scope is wrong in #10 and in ADR-0003 §4Region-gated, not universal — 12 v2-capable region strings in 3.
|
Uh oh!
There was an error while loading. Please reload this page.
Splitting this out of #11 because it is the root cause of three of the worst bugs in v0.1.9, and because the obvious fix is migration-unsafe.
What's broken today
Verified against the current release:
endpoint_urlis silently dropped when env credentials exist —base.py:82callsget_client("environment variables", **session_kwargs)without forwarding it. A store configured for R2/MinIO/Supabase talks to AWS.base.py:71does{**session_kwargs, **aws_credentials}, so env wins over what the caller passed.url_forpresigns with SigV2 (url_for produces SigV2 presigned URLs, which modern S3 and every major S3-compatible provider reject #10) — becausesignature_versionis never set explicitly.All three share one cause: credential/endpoint resolution is spread across three functions instead of living in one connection object.
Two facts that make a naive fix dangerous
AWS_ENDPOINT_URL_S3silently outranks everything. It is service-specific so it beats the genericAWS_ENDPOINT_URL, and it is not in botocore'sBOTOCORE_DEFAUT_SESSION_VARIABLES— it is resolved in the endpoint-provider layer, so it is invisible to naive introspection. It is also why CI is currently green on a data-misrouting bug: the test environment re-supplies the endpoint the code throws away.Consequence nobody would guess: v0's effective precedence when env credentials exist is
AWS_ENDPOINT_URL_S3 > AWS_ENDPOINT_URL > explicit kwarg— and "explicit wins" inverts the top of it. Any deployment relying on that env var to override a stale hard-coded endpoint silently redirects on upgrade. Upgrading must not move anyone's data target without telling them.Credential refresh is easy to destroy. boto3 clients hold a
Credentialsobject and callget_frozen_credentials()per request, so SSO/STS/IMDS refresh works — if you let the chain resolve. v0 defeats this twice: by merging env keys into session kwargs, and by building a raw client from a credential snapshot that can never refresh (and which dropsprofile_nameentirely).The part I think is uncontroversial
Make resolution a pure function of
(spec, environ, aws_config)returning a record where every field carries its provenance:No I/O, so tier-1 tests can cover the ladder exhaustively with a fake env dict — which is what makes a precedence rule trustworthy rather than aspirational.
s3dol.diagnose()prints this table (never the secret). The connection stays a frozen dataclass carrying a spec, with the client as a lazycached_property, so it is picklable (today stores raisePicklingError, andbotocore.UNSIGNEDis itself unpicklable so it must never enter the dataclass).The four questions
Q1 — what wins?
AWS_ENDPOINT_URL_S3.strict=Truemode that raises on any ambiguity, off by default.Lean: (b) as default + (c) available, warning removed in v2.
Q2 — what shape is
credentials=?Options: raw kwargs (v0's mistake — defeats refresh); a profile name; a
botocore.Session; a callable credential provider returning{key, secret, token, expires_at}(obstore's shape, and the one that composes with SSO); or polymorphic "the thing or a spec for it", à laazuredol.AzureConnection.from_anything.Lean: polymorphic accept, normalize to a session/provider internally, never to frozen keys. The rule to encode: never construct a client from a credential snapshot.
Q3 — how much does a preset get to set?
AWS_ENDPOINT_URL_S3outrank a preset the user explicitly named? Proposed no — an explicitly named preset is an explicit argument.Q4 — where does
anonlive, and what does'auto'mean?It must be on the top-level factory (
s3_store(bucket, anon=True)), not only on the connection, or the open-data notebook case requires learning Layer A — and reading a public bucket is currently impossible in s3dol at all.'auto'needs a precise meaning. Proposed: "try unsigned if no credentials resolve at all" — explicitly not "retry unsigned afterAccessDenied", because an expired token would then silently downgrade to a different, public view of the data. Note requester-pays buckets forbid anonymous access outright, so a silent fallback confuses there regardless.Also:
url_formust raise when the resolved signer isUNSIGNED— botocore otherwise returns a plain unsigned URL with no error.Sequencing (already in ADR-0007 §5, repeated because it's the risky part)
Because merging auto-publishes to PyPI and version numbers burn permanently:
s3dol.diagnose()in a non-breaking 0.1.x patch. Have each dependent run it in their real environment and record the answer.Design context: ADR-0003, ADR-0007 §4–5, and state-of-play §8.
All reactions