databendlabs/databend · error · InvalidInput

s3.session-token requires s3.access-key-id and…

Error message

s3.session-token requires s3.access-key-id and s3.secret-access-key

What it means

The same validator rejects an S3 session token supplied without the key pair it belongs to. s3.session-token is a temporary credential that is only meaningful together with s3.access-key-id and s3.secret-access-key, so having the token alone is treated as InvalidInput.

Solutions

  1. Add both s3.access-key-id and s3.secret-access-key alongside s3.session-token; a session token is only valid with its key pair.
  2. If you do not need static temporary credentials, remove s3.session-token and rely on the ambient credential chain (IAM role).
  3. Double-check key spelling/whitespace in the properties map so the access/secret keys are actually recognized.

Example fix

// before
props.insert("s3.session-token".into(), "tok...".into());
// after
props.insert("s3.access-key-id".into(), "AKIA...".into());
props.insert("s3.secret-access-key".into(), "secret...".into());
props.insert("s3.session-token".into(), "tok...".into());
Defensive patterns

Strategy: validation

Validate before calling

let has_token = props.contains_key("s3.session-token");
let has_pair = props.contains_key("s3.access-key-id") && props.contains_key("s3.secret-access-key");
if has_token && !has_pair {
    return Err("s3.session-token requires s3.access-key-id and s3.secret-access-key".into());
}

Try / catch

// catch and surface as a credentials-completeness problem
if let Err(e) = file_io.get_operator_path(location) {
    if e.message().contains("session-token requires") {
        return Err(ConfigError::SessionTokenWithoutKeys);
    }
    return Err(e);
}

Prevention

When it happens

Trigger: build_operator -> validate_s3_credentials where props contains s3.session-token but is missing s3.access-key-id or s3.secret-access-key (or both).

Common situations: Users configuring STS/temporary credentials who paste the session token but omit the associated access/secret keys; automation that injects only the session token from an assumed-role secret; typo'd key names for the access/secret pair so they are not recognized.

Related errors


AI-assisted analysis of databendlabs/databend@288d84d76e (2026-09-11). Data as JSON: /api/errors/c5b57efd7f725283. Report an issue: GitHub.

Appendix: source

Thrown at src/common/storage/src/operator.rs:754

    pub fn new(file_io: iceberg::io::FileIO) -> Self {
        let (scheme, props, _extensions) = file_io.into_builder().into_parts();
        Self { scheme, props }
    }

    fn validate_s3_credentials(&self) -> Result<()> {
        let has_access_key = self.props.contains_key("s3.access-key-id");
        let has_secret_key = self.props.contains_key("s3.secret-access-key");
        let has_session_token = self.props.contains_key("s3.session-token");

        if has_access_key != has_secret_key {
            return Err(Error::new(
                ErrorKind::InvalidInput,
                "s3.access-key-id and s3.secret-access-key must be configured together",
            ));
        }

        if has_session_token && !(has_access_key && has_secret_key) {
            return Err(Error::new(
                ErrorKind::InvalidInput,
                "s3.session-token requires s3.access-key-id and s3.secret-access-key",
            ));
        }

        Ok(())
    }

    fn build_operator(&self, location: &str) -> Result<(Operator, usize)> {
        let url = url::Url::parse(location)
            .map_err(|e| Error::new(ErrorKind::InvalidInput, e.to_string()))?;

        let scheme = url.scheme();

        // Handle file:// and memory:// URIs which don't have a host/bucket
        let is_local_scheme = matches!(scheme, "file" | "memory" | "");
        let (bucket, relative_path_pos) = if is_local_scheme {
            // For file:// URIs, the path starts after "file://"

View on GitHub (pinned to 288d84d76e)