aws-sdk-java-v2 icon indicating copy to clipboard operation
aws-sdk-java-v2 copied to clipboard

HOWTO: Assume Role With Saml in java sdk 2.x

Open mscharp opened this issue 6 years ago • 6 comments

This may be a bug, or a misunderstanding of how to use the 2.0 sdk to assume a role with SAML. In short, I would like to use Secure Token Service to authenticate with my Identity Provider and then once authenticated, assume a role in AWS to use when calling different services.

Expected Behavior

Currently, I am using the StsAssumeRoleWithSamlCredentialProvider. Once I configure the credential provider and give it a mechanism to get a valid saml assertion from my Identity Provider, I should be able to give that credential provider to any other service client so that it will use those credentials when making API calls. Those other services should NOT attempt to get credentials from any other sources ( this includes the DefaultCredentialProvider which looks in a myriad of places).

Current Behavior

When I pass the StsAssumeRoleWithSamlCredentialProvider to another service client ( such as CloudWatchLogsClient ), and initiate a request, the request fails with multiple "Unable to load credentials from ***" errors. Here is one of the stack traces:

[http-nio-8080-exec-1] DEBUG software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain - Unable to load credentials from SystemPropertyCredentialsProvider(): Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId). software.amazon.awssdk.core.exception.SdkClientException: Unable to load credentials from system settings. Access key must be specified either via environment variable (AWS_ACCESS_KEY_ID) or system property (aws.accessKeyId). at software.amazon.awssdk.core.exception.SdkClientException$BuilderImpl.build(SdkClientException.java:97) at software.amazon.awssdk.auth.credentials.internal.SystemSettingsCredentialsProvider.resolveCredentials(SystemSettingsCredentialsProvider.java:58) at software.amazon.awssdk.auth.credentials.AwsCredentialsProviderChain.resolveCredentials(AwsCredentialsProviderChain.java:91) at software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider.resolveCredentials(DefaultCredentialsProvider.java:92) at software.amazon.awssdk.awscore.client.handler.AwsClientHandlerUtils.createExecutionContext(AwsClientHandlerUtils.java:70) at software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler.createExecutionContext(AwsSyncClientHandler.java:68) at software.amazon.awssdk.core.client.handler.BaseSyncClientHandler.execute(BaseSyncClientHandler.java:68) at software.amazon.awssdk.core.client.handler.SdkSyncClientHandler.execute(SdkSyncClientHandler.java:44) at software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler.execute(AwsSyncClientHandler.java:55) at software.amazon.awssdk.services.sts.DefaultStsClient.assumeRoleWithSAML(DefaultStsClient.java:370) at software.amazon.awssdk.services.sts.auth.StsAssumeRoleWithSamlCredentialsProvider.getUpdatedCredentials(StsAssumeRoleWithSamlCredentialsProvider.java:69) at software.amazon.awssdk.services.sts.auth.StsCredentialsProvider.updateSessionCredentials(StsCredentialsProvider.java:68) at software.amazon.awssdk.utils.cache.CachedSupplier.refreshCache(CachedSupplier.java:132) at software.amazon.awssdk.utils.cache.CachedSupplier.get(CachedSupplier.java:89) at software.amazon.awssdk.services.sts.auth.StsCredentialsProvider.resolveCredentials(StsCredentialsProvider.java:78) at software.amazon.awssdk.services.sts.auth.StsAssumeRoleWithSamlCredentialsProvider.resolveCredentials(StsAssumeRoleWithSamlCredentialsProvider.java:42) at software.amazon.awssdk.awscore.client.handler.AwsClientHandlerUtils.createExecutionContext(AwsClientHandlerUtils.java:70) at software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler.createExecutionContext(AwsSyncClientHandler.java:68) at software.amazon.awssdk.core.client.handler.BaseSyncClientHandler.execute(BaseSyncClientHandler.java:68) at software.amazon.awssdk.core.client.handler.SdkSyncClientHandler.execute(SdkSyncClientHandler.java:44) at software.amazon.awssdk.awscore.client.handler.AwsSyncClientHandler.execute(AwsSyncClientHandler.java:55) at software.amazon.awssdk.services.cloudwatchlogs.DefaultCloudWatchLogsClient.getLogEvents(DefaultCloudWatchLogsClient.java:1836) at logs.GetAWSLogs.getLogs(GetAWSLogs.java:167)

What I think may be the cause of this issue is that the StsAssumeRoleWithSamlCredentialsProvider uses the StsClient to make the request. The default StsClient uses the DefaultCredentialsProvider to authenticate with AWS in order to make the request. You can override the credentials provider in the StsClient with the overrideConfiguration method, but since the StsAssumeRoleWithSamlCredentialsProvider requires the StsClient, it seems that its a "chicken and egg" scenario. You can't configure the Sts..CredentialsProvider without an StsClient, but the StsClient needs a credentials provider itself...

Guidance on how to correctly assume a role, or if this is truly a bug, then a quick fix would be very much appreciated.

Steps to Reproduce (for bugs)

` ProxyConfiguration proxyConfig = null; try { proxyConfig = ProxyConfiguration .builder() .endpoint(new URI("http_proxy")) .build(); } catch (URISyntaxException e) { e.printStackTrace(); }

AWSSAMLAuthenticationConfiguration config = AWSSAMLAuthenticationConfiguration
            .builder()
            .userName(ad_user)
            .userPass(ad_pass)
            .roleName(aws_role)
            .region(Region.US_EAST_1)
            .proxyConfig(proxyConfig)
            .build();

SdkHttpClient httpClient = ApacheHttpClient
        .builder()
        .proxyConfiguration(proxyConfig)
        .build();

AssumeRoleWithSamlRequest request = AssumeRoleWithSamlRequest
        .builder()
        .durationSeconds(900)
        .roleArn(ROLE_ARN)
        .principalArn("PRINCIPAL_ARN")
        .build();

SamlIdentityProviderServiceFactory factory = new SamlIdentityProviderServiceFactory();
SamlIdentityProviderService service = factory.getService(config);

Supplier<AssumeRoleWithSamlRequest> supplier = () ->
        request.toBuilder()
               .samlAssertion(service.getValidAssertion())
               .build();

cp = StsAssumeRoleWithSamlCredentialsProvider
        .builder()
        .stsClient(StsClient.builder().region(Region.US_EAST_1).httpClient(httpClient).build())
        .asyncCredentialUpdateEnabled(true)
        .refreshRequest(supplier)
        .build();

cw = CloudWatchLogsClient.builder()
                .httpClient(httpClient)
                .region(Region.US_EAST_1)
                .credentialsProvider(cp)
                .build();

GetLogEventsResponse logEventsResponse = cw.getLogEvents(GetLogEventsRequest.builder()
                                                                            .logGroupName(logGroup)
                                                                            .logStreamName(logStream)
                                                                            .startFromHead(true)
                                                                            .build());

`

Context

Without the ability to correctly assume a role via saml, we cannot migrate to the 2.0 version of the SDK.

Your Environment

  • AWS Java SDK version used: 2.3.1

mscharp avatar Jan 15 '19 20:01 mscharp

@dagnir @millems

Bumping to hopefully get traction...

mscharp avatar Feb 07 '19 17:02 mscharp

Can you try configuring the STS client with an AnonymousCredentialsProvider and let me know if that works for you?

millems avatar Feb 07 '19 22:02 millems

So, I changed the following

cp = StsAssumeRoleWithSamlCredentialsProvider .builder() .stsClient(StsClient.builder().region(Region.US_EAST_1).httpClient(httpClient).build()) .asyncCredentialUpdateEnabled(true) .refreshRequest(supplier) .build();

to cp = StsAssumeRoleWithSamlCredentialsProvider .builder() .stsClient( StsClient .builder() .region(Region.US_EAST_1) .httpClient(httpClient) .credentialsProvider(AnonymousCredentialsProvider.create()).build() ) .asyncCredentialUpdateEnabled(true) .refreshRequest(supplier) .build();

And instead of getting the previously mentioned error, I'm just getting a 403 error from the STS service.

software.amazon.awssdk.services.sts.model.StsException: Access denied (Service: Sts, Status Code: 403 ...

mscharp avatar Feb 12 '19 19:02 mscharp

I had this problem too.

However, I can confirm that I can make it work with the latest version of the SDK, in my case version 2.15.26, and with the use of the AnonymousCredentialsProvider like this:

StsClient stsClient = StsClient.builder()
    .credentialsProvider(software.amazon.awssdk.auth.credentials.AnonymousCredentialsProvider.create())
    .build();

Without the use of the AnonymousCredentialsProvider I get :

"SdkClientException: Unable to load credentials from any of the providers in the chain"

I don't think the required use of the AnonymousCredentialsProvider is well-documented, but perhaps I missed something? In fact the Javadoc says this:

Calling AssumeRoleWithSAML does not require the use of AWS security credentials. The identity of the caller is validated by using keys in the metadata document that is uploaded for the SAML provider entity for your identity provider.

This sentence doesn't make it obvious that in fact you MUST use AnonymousCredentialsProvider. You can even use a bogus credentials provider, it just needs to be there and return a non-null AwsCredentials object .. which then won't be used anyway. Weird.

lbruun avatar Nov 13 '20 06:11 lbruun

I had similar issue in my code I followed @lbruun solution and worked.

stsClient = StsClient.builder()
                .credentialsProvider(AnonymousCredentialsProvider.create())
                .region(region)
                .build();

hemantc09 avatar Jan 15 '21 19:01 hemantc09

@mscharp Can you please tell me which AWS Package supports below classes AWSSAMLAuthenticationConfiguration , SamlIdentityProviderServiceFactory and SamlIdentityProviderService

I am trying to use your example for getting saml assertions before pass it to StsAssumeRoleWithSamlCredentialsProvider

missourian55 avatar Apr 25 '22 00:04 missourian55

I have a suggestion to document it via code. IF possible, there should be a special STS client that takes in the settings for SAML/Web identities then return a "normal" client that is pre-configured for that identity.

An alternative is to have a helper dedicated to the anonymous credentials provider, so it comes up when I type in "cred" on the STS client builder. Not perfect, but I can extrapolate from there, after getting the error once.

The anonymous credentials provider is the solution, but it is not easy to find.

Frontrider avatar Feb 15 '23 15:02 Frontrider