Improvement Request
Problem
An s3 store cannot be configured without embedding a static access_key / secret_key, even when the process already has a perfectly good AWS identity.
s3 is the only protocol in storage.py that mandates credentials:
# settings.py — DataJointConfig.get_store_spec
required_keys = {
"file": ("protocol", "location"),
"s3": ("protocol", "endpoint", "bucket", "access_key", "secret_key", "location"),
"gcs": ("protocol", "bucket", "location"),
"azure": ("protocol", "container", "location"),
}
gcs and azure require none, and _create_filesystem reads theirs tolerantly — token=self.spec.get("token"), account_key=self.spec.get("account_key") — so they already fall through to Application Default Credentials and DefaultAzureCredential. Only s3 uses hard subscripts:
return fsspec.filesystem(
"s3",
key=self.spec["access_key"],
secret=self.spec["secret_key"],
...
)
The result is that every environment where AWS credentials are ambient — EC2 instance profile, EKS service-account role, ECS task role, or a workstation with AWS_PROFILE / SSO — has to mint and distribute long-lived access keys purely to satisfy DataJoint, when boto3 would have resolved an identity by itself.
Requirements
Make access_key and secret_key optional for protocol: s3, matching gcs and azure:
settings.py — drop both from required_keys["s3"].
storage.py::_validate_spec — drop both from required.
storage.py::_create_filesystem — self.spec["access_key"] → self.spec.get("access_key") (same for secret_key). This one is easy to miss: relaxing only the two validators leaves a subscript that raises KeyError after validation passes.
When both are absent, pass nothing and let the underlying chain resolve. When both are present, behaviour is exactly as today — this is backward compatible.
Two details worth deciding explicitly:
- Reject an empty string rather than forwarding it.
s3fs filters out None kwargs before calling botocore, so None is genuinely equivalent to absence. "" is not: it survives that filter, and botocore treats any non-None pair as explicit credentials, producing a confusing InvalidAccessKeyId at first object access instead of a clear config error. Since the current check is not self.spec.get(k), empty is already treated as missing — worth keeping that behaviour explicit once the keys become optional.
- The two validators disagree about
None today. settings.py tests presence (k not in spec) while storage.py tests truthiness (not self.spec.get(k)), so "bucket": null passes the first and fails the second. Harmless at the moment, but the same divergence would make a partially-applied version of this change behave inconsistently.
No new configuration key is proposed — see below.
Justification
- Consistency. This makes
s3 behave like the two object-store backends that already work this way. It is removing an outlier, not adding a mode.
- Credentials that refresh. An instance-profile or web-identity chain re-resolves for the life of the process. Static keys in a store spec do not, which is what makes long-running jobs the awkward case today.
- Fewer long-lived secrets. Environments that have an ambient identity currently have to create static keys anyway, purely to populate a config field — the opposite of what their security posture is usually aiming for.
Alternative Considerations
- Inject temporary STS credentials into the store spec. Works briefly, then expires. The spec is typically materialised once (env var, config file) and never refreshed, so a long job fails partway with
ExpiredToken. Letting botocore own the chain avoids the problem rather than shortening it.
- Add an explicit opt-in field (e.g.
credentials: "auto"). Rejected on compatibility grounds: allowed_keys is a strict whitelist and unknown keys raise, so any added field breaks every client older than the change. Absence is already the signal s3fs itself uses — core.py comments the key is None or secret is None branch as "automatic credentials".
anon=True. Different meaning: unsigned/public-bucket access, not "resolve an identity normally".
- Workaround today: none within DataJoint. Callers must create static keys, or monkey-patch
_create_filesystem.
Related Errors
With an ambient identity available but no static keys configured:
DataJointError: config.stores["<name>"] is missing: access_key, secret_key
and, if only the settings.py list is relaxed:
DataJointError: Missing S3 configuration: access_key, secret_key
Verified against s3fs 2024.6.1 / botocore 1.38.13: s3fs drops None kwargs (core.py, init_kwargs comprehension) and botocore falls through to the default chain unless both credentials are non-None (session.py::create_client), raising PartialCredentialsError when exactly one is supplied.
Improvement Request
Problem
An
s3store cannot be configured without embedding a staticaccess_key/secret_key, even when the process already has a perfectly good AWS identity.s3is the only protocol instorage.pythat mandates credentials:gcsandazurerequire none, and_create_filesystemreads theirs tolerantly —token=self.spec.get("token"),account_key=self.spec.get("account_key")— so they already fall through to Application Default Credentials andDefaultAzureCredential. Onlys3uses hard subscripts:The result is that every environment where AWS credentials are ambient — EC2 instance profile, EKS service-account role, ECS task role, or a workstation with
AWS_PROFILE/ SSO — has to mint and distribute long-lived access keys purely to satisfy DataJoint, whenboto3would have resolved an identity by itself.Requirements
Make
access_keyandsecret_keyoptional forprotocol: s3, matchinggcsandazure:settings.py— drop both fromrequired_keys["s3"].storage.py::_validate_spec— drop both fromrequired.storage.py::_create_filesystem—self.spec["access_key"]→self.spec.get("access_key")(same forsecret_key). This one is easy to miss: relaxing only the two validators leaves a subscript that raisesKeyErrorafter validation passes.When both are absent, pass nothing and let the underlying chain resolve. When both are present, behaviour is exactly as today — this is backward compatible.
Two details worth deciding explicitly:
s3fsfilters outNonekwargs before callingbotocore, soNoneis genuinely equivalent to absence.""is not: it survives that filter, andbotocoretreats any non-Nonepair as explicit credentials, producing a confusingInvalidAccessKeyIdat first object access instead of a clear config error. Since the current check isnot self.spec.get(k), empty is already treated as missing — worth keeping that behaviour explicit once the keys become optional.Nonetoday.settings.pytests presence (k not in spec) whilestorage.pytests truthiness (not self.spec.get(k)), so"bucket": nullpasses the first and fails the second. Harmless at the moment, but the same divergence would make a partially-applied version of this change behave inconsistently.No new configuration key is proposed — see below.
Justification
s3behave like the two object-store backends that already work this way. It is removing an outlier, not adding a mode.Alternative Considerations
ExpiredToken. Lettingbotocoreown the chain avoids the problem rather than shortening it.credentials: "auto"). Rejected on compatibility grounds:allowed_keysis a strict whitelist and unknown keys raise, so any added field breaks every client older than the change. Absence is already the signals3fsitself uses —core.pycomments thekey is None or secret is Nonebranch as "automatic credentials".anon=True. Different meaning: unsigned/public-bucket access, not "resolve an identity normally"._create_filesystem.Related Errors
With an ambient identity available but no static keys configured:
and, if only the
settings.pylist is relaxed:Verified against
s3fs 2024.6.1/botocore 1.38.13:s3fsdropsNonekwargs (core.py,init_kwargscomprehension) andbotocorefalls through to the default chain unless both credentials are non-None(session.py::create_client), raisingPartialCredentialsErrorwhen exactly one is supplied.