elastic/elasticsearch · error · IllegalStateException

STS AssumeRoleWithWebIdentity response did not include a cre

Error message

STS AssumeRoleWithWebIdentity response did not include a credential expiry

What it means

After Credentials is present, toCached requires credentials.expiration() to be non-null. The expiry drives prefetchAt and staleAt computation; without it the cache cannot know when to refresh. A missing expiry is treated as an illegal state.

Source

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

    }

    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));
    }

    private static Throwable unwrap(Throwable t) {
        return t instanceof CompletionException && t.getCause() != null ? t.getCause() : t;
    }

View on GitHub (pinned to db6a809a66)

Solutions

  1. Ensure the Credentials response includes expiration (for tests: Credentials.builder().expiration(Instant.now().plusSeconds(900)))
  2. If hitting a real STS-compatible endpoint, confirm it populates the Expiration field per the AWS schema
  3. Upgrade/align the AWS SDK version so the field deserialises correctly

Example fix

// before
Credentials c = Credentials.builder().accessKeyId("k").secretAccessKey("s").build();
// after
Credentials c = Credentials.builder().accessKeyId("k").secretAccessKey("s").expiration(Instant.now().plusSeconds(900)).build();
Defensive patterns

Strategy: validation

Validate before calling

Credentials c = resp.credentials();
if (c == null || c.expiration() == null) {
    throw new IllegalStateException("STS credentials missing expiry");
}

Type guard

static boolean hasExpiry(Credentials c) {
    return c != null && c.expiration() != null;
}

Try / catch

try { toCached(resp); }
catch (IllegalStateException e) { /* log + retry */ }

Prevention

When it happens

Trigger: STS returns a Credentials object whose expiration() is null. Reached only after the null-credentials check passed.

Common situations: Mocked or stubbed credentials without an expiration; an STS-compatible service that omits the expiration field; SDK version skew where the field is named differently; misconfigured local STS emulator.

Related errors


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