elastic/elasticsearch · error · IllegalStateException

STS AssumeRoleWithWebIdentity response did not include crede

Error message

STS AssumeRoleWithWebIdentity response did not include credentials

What it means

toCached converts the STS AssumeRoleWithWebIdentityResponse into a Cached entry. AWS SDK v2 allows response.credentials() to be null on a malformed or partial response; the provider treats a null Credentials object as an illegal state (not a retryable client error).

Source

Thrown at libs/workload-identity-aws/src/main/java/org/elasticsearch/workload/identity/aws/AsyncWebIdentityCredentialsProvider.java:198

    private CompletableFuture<String> requestToken() {
        CompletableFuture<String> future = new CompletableFuture<>();
        ActionListener.run(ActionListener.<String>wrap(future::complete, future::completeExceptionally), tokenSupplier::accept);
        return future;
    }

    private CompletableFuture<AssumeRoleWithWebIdentityResponse> requestCredentials(String token) {
        AssumeRoleWithWebIdentityRequest request = AssumeRoleWithWebIdentityRequest.builder()
            .roleArn(roleArn)
            .roleSessionName(roleSessionName)
            .webIdentityToken(token)
            .build();
        return stsAsyncClient.assumeRoleWithWebIdentity(request);
    }

    private Cached toCached(AssumeRoleWithWebIdentityResponse response) {
        Credentials credentials = response.credentials();
        if (credentials == null) {
            throw new IllegalStateException("STS AssumeRoleWithWebIdentity response did not include credentials");
        }
        Instant expiry = credentials.expiration();
        if (expiry == null) {
            throw new IllegalStateException("STS AssumeRoleWithWebIdentity response did not include a credential expiry");
        }
        Instant now = clock.instant();
        if (expiry.isAfter(now) == false) {
            throw new IllegalStateException("STS returned credentials that are already expired at [" + expiry + "] (now [" + now + "])");
        }
        AwsSessionCredentials sessionCredentials = AwsSessionCredentials.builder()
            .accessKeyId(credentials.accessKeyId())
            .secretAccessKey(credentials.secretAccessKey())
            .sessionToken(credentials.sessionToken())
            .expirationTime(expiry)
            .build();
        return new Cached(sessionCredentials, expiry.minus(prefetchTime), expiry.minus(staleTime));
    }

View on GitHub (pinned to db6a809a66)

Solutions

  1. Inspect the full STS response logging / SDK metrics to see why Credentials was absent
  2. Verify the IAM trust policy on the role permits sts:AssumeRoleWithWebIdentity for the caller
  3. If using a test stub, ensure the mocked response includes a non-null Credentials
  4. File an issue if STS genuinely returns 200 with null Credentials - that indicates an upstream fault

Example fix

// before (mock)
when(sts.assumeRoleWithWebIdentity(req)).thenReturn(AssumeRoleWithWebIdentityResponse.builder().build());
// after
when(sts.assumeRoleWithWebIdentity(req)).thenReturn(
    AssumeRoleWithWebIdentityResponse.builder()
        .credentials(Credentials.builder().accessKeyId("k").secretAccessKey("s").sessionToken("t").expiration(Instant.now().plusSeconds(900)).build())
        .build());
Defensive patterns

Strategy: try-catch

Validate before calling

AssumeRoleWithWebIdentityResponse resp = requestCredentials(token).join();
if (resp.credentials() == null) {
    throw new IllegalStateException("STS returned no credentials for role " + roleArn);
}

Type guard

static boolean hasCredentials(AssumeRoleWithWebIdentityResponse r) {
    return r != null && r.credentials() != null;
}

Try / catch

try { toCached(resp); }
catch (IllegalStateException e) { /* log + surface as auth failure */ }

Prevention

When it happens

Trigger: requestCredentials returns a response whose credentials() is null. This is reached after the async STS call completes and the future is being unwrapped.

Common situations: STS returned an error-shaped response without an exception (rare); a misbehaving test double returns a response with no Credentials; an SDK version mismatch where the response shape changed; IAM/permissions edge cases where STS acknowledges the call but returns an empty credentials block.

Related errors


AI-assisted analysis of elastic/elasticsearch@db6a809a66 (2026-08-12). Data as JSON: /api/errors/3777ef0a4067aed8. Report an issue: GitHub.