risingwavelabs/risingwave · error · SinkError::Config

Either assume_role or access_key and secret_key must be…

Error message

Either assume_role or access_key and secret_key must be provided for Redshift COPY command

What it means

The Redshift `COPY` command that loads staged S3 files into Redshift needs AWS credentials: either an `assume_role` ARN or an `access_key`/`secret_key` pair. `build_copy_into_sql` throws this Config error when neither form of credentials is present in the sink config, because the generated COPY statement's authorization clause would be empty.

Solutions

  1. Add `aws.assume_role = 'arn:aws:iam::<account>:role/<role>'` to the sink WITH options.
  2. Or set both `aws.access_key` and `aws.secret_key` in the WITH options.
  3. Verify keys aren't empty strings and that the WITH options were actually passed to the sink (recreate the sink if needed).

Example fix

// before
WITH (connector='redshift', aws.access_key='AKIA...', type='append-only');
// after
WITH (connector='redshift', aws.access_key='AKIA...', aws.secret_key='...', type='append-only');
Defensive patterns

Strategy: validation

Validate before calling

fn has_copy_auth(p: &BTreeMap<String, String>) -> bool {
    p.contains_key("aws.assume_role")
        || (p.contains_key("aws.access_key") && p.contains_key("aws.secret_key"))
}

Prevention

When it happens

Trigger: Calling `copy_into_from_s3_to_redshift` when the sink config lacks `aws.assume_role` and also lacks both `aws.access_key` and `aws.secret_key` (one alone is insufficient).

Common situations: User configures IAM role auth but forgets `aws.assume_role`; provides only `aws.access_key` without `aws.secret_key`; empty-string keys parsed as None after validation.

Related errors


AI-assisted analysis of risingwavelabs/risingwave@6469eb736d (2026-09-11). Data as JSON: /api/errors/4e810a801c5a8b3b. Report an issue: GitHub.

Appendix: source

Thrown at src/connector/src/sink/snowflake_redshift/redshift.rs:1010

fn build_copy_into_sql(
    schema_name: Option<&str>,
    table_name: &str,
    manifest_dir: &str,
    access_key: &Option<String>,
    secret_key: &Option<String>,
    assume_role: &Option<String>,
) -> Result<String> {
    let table_name = build_full_table_name(schema_name, table_name);
    let credentials = if let Some(assume_role) = assume_role {
        &format!("aws_iam_role={}", assume_role)
    } else if let (Some(access_key), Some(secret_key)) = (access_key, secret_key) {
        &format!(
            "aws_access_key_id={};aws_secret_access_key={}",
            access_key, secret_key
        )
    } else {
        return Err(SinkError::Config(anyhow!(
            "Either assume_role or access_key and secret_key must be provided for Redshift COPY command"
        )));
    };
    Ok(format!(
        r#"
        COPY {table_name}
        FROM '{manifest_dir}'
        CREDENTIALS '{credentials}'
        FORMAT AS JSON 'auto'
        DATEFORMAT 'auto'
        TIMEFORMAT 'auto'
        MANIFEST;
        "#,
        table_name = table_name,
        manifest_dir = manifest_dir,
        credentials = credentials
    ))
}

View on GitHub (pinned to 6469eb736d)