{"record":{"id":"139ae5b2e27fd4a0","repo":"elastic/elasticsearch","slug":"sts-assumerolewithwebidentity-response-did-not-inc-139ae5","errorCode":null,"errorMessage":"STS AssumeRoleWithWebIdentity response did not include a credential expiry","messagePattern":"STS AssumeRoleWithWebIdentity response did not include a credential expiry","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"libs/workload-identity-aws/src/main/java/org/elasticsearch/workload/identity/aws/AsyncWebIdentityCredentialsProvider.java","lineNumber":202,"sourceCode":"    }\n\n    private CompletableFuture<AssumeRoleWithWebIdentityResponse> requestCredentials(String token) {\n        AssumeRoleWithWebIdentityRequest request = AssumeRoleWithWebIdentityRequest.builder()\n            .roleArn(roleArn)\n            .roleSessionName(roleSessionName)\n            .webIdentityToken(token)\n            .build();\n        return stsAsyncClient.assumeRoleWithWebIdentity(request);\n    }\n\n    private Cached toCached(AssumeRoleWithWebIdentityResponse response) {\n        Credentials credentials = response.credentials();\n        if (credentials == null) {\n            throw new IllegalStateException(\"STS AssumeRoleWithWebIdentity response did not include credentials\");\n        }\n        Instant expiry = credentials.expiration();\n        if (expiry == null) {\n            throw new IllegalStateException(\"STS AssumeRoleWithWebIdentity response did not include a credential expiry\");\n        }\n        Instant now = clock.instant();\n        if (expiry.isAfter(now) == false) {\n            throw new IllegalStateException(\"STS returned credentials that are already expired at [\" + expiry + \"] (now [\" + now + \"])\");\n        }\n        AwsSessionCredentials sessionCredentials = AwsSessionCredentials.builder()\n            .accessKeyId(credentials.accessKeyId())\n            .secretAccessKey(credentials.secretAccessKey())\n            .sessionToken(credentials.sessionToken())\n            .expirationTime(expiry)\n            .build();\n        return new Cached(sessionCredentials, expiry.minus(prefetchTime), expiry.minus(staleTime));\n    }\n\n    private static Throwable unwrap(Throwable t) {\n        return t instanceof CompletionException && t.getCause() != null ? t.getCause() : t;\n    }\n","sourceCodeStart":184,"sourceCodeEnd":220,"githubUrl":"https://github.com/elastic/elasticsearch/blob/db6a809a667c081ca1dc7500389d26975573215f/libs/workload-identity-aws/src/main/java/org/elasticsearch/workload/identity/aws/AsyncWebIdentityCredentialsProvider.java#L184-L220","documentation":"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.","triggerScenarios":"STS returns a Credentials object whose expiration() is null. Reached only after the null-credentials check passed.","commonSituations":"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.","solutions":["Ensure the Credentials response includes expiration (for tests: Credentials.builder().expiration(Instant.now().plusSeconds(900)))","If hitting a real STS-compatible endpoint, confirm it populates the Expiration field per the AWS schema","Upgrade/align the AWS SDK version so the field deserialises correctly"],"exampleFix":"// before\nCredentials c = Credentials.builder().accessKeyId(\"k\").secretAccessKey(\"s\").build();\n// after\nCredentials c = Credentials.builder().accessKeyId(\"k\").secretAccessKey(\"s\").expiration(Instant.now().plusSeconds(900)).build();","handlingStrategy":"validation","validationCode":"Credentials c = resp.credentials();\nif (c == null || c.expiration() == null) {\n    throw new IllegalStateException(\"STS credentials missing expiry\");\n}","typeGuard":"static boolean hasExpiry(Credentials c) {\n    return c != null && c.expiration() != null;\n}","tryCatchPattern":"try { toCached(resp); }\ncatch (IllegalStateException e) { /* log + retry */ }","preventionTips":["Always set expiration in test stubs","Verify STS-compatible endpoints populate Expiration","Pin the AWS SDK version"],"tags":["aws","sts","credentials","runtime"],"backgroundTag":null,"analyzedSha":"db6a809a667c081ca1dc7500389d26975573215f","analyzedAt":"2026-08-12T01:39:14.192Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}