Skip to content

Conversation

@aajtodd
Copy link
Contributor

@aajtodd aajtodd commented Dec 10, 2021

Issue #

N/A

Description of changes

Refactor the STS credential providers to use internal generated service clients rather than CRT.

Couple of benefits of this route:

  • KMP implementation
  • CRT STS client only has support for global sts endpoint (MRAP requires regional STS endpoints)
  • CRT STS providers don't support JVM system properties or explicit configuration

Testing Done:

{

    // val provider = StsAssumeRoleCredentialsProvider(
    //     credentialsProvider = DefaultChainCredentialsProvider(),
    //     roleArn = "<test-role>",
    //     region = "us-west-2"
    //
    // )

    val provider = StsWebIdentityCredentialsProvider(
        roleArn = "<test-role>",
        roleSessionName = "test-sts-web-ident-provider",
        webIdentityTokenFilePath = "/path/to/my/token.jwt",
        region = "us-west-2"
    )

    StsClient{
        region = "us-east-2"
        credentialsProvider = provider
    }.use { client ->
        val resp = client.getCallerIdentity {}

        println("Account: ${resp.account}")
        println("UserID: ${resp.userId}")
        println("ARN: ${resp.arn}")
    }
}

STS Assume role is fairly easy to test. The web identity provider is a bit more involved and requires setting up an OIDC provider. There is an internal setup script in the repo referenced from the (in-progress) SEP that can do this by configuring an S3 bucket and as an OIDC provider. You have to turn on public access temporarily to get it to work though.

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.

@aajtodd aajtodd requested a review from a team as a code owner December 10, 2021 20:46
@aajtodd aajtodd requested review from ianbotsf and kggilmer December 10, 2021 20:46
@github-actions
Copy link

A new generated diff is ready to view: __generated-main...__generated-feat-sts-providers

Comment on lines -20 to +52
public class StsAssumeRoleCredentialsProvider public constructor(
credentialsProvider: CredentialsProvider,
roleArn: String,
sessionName: String,
durationSeconds: Int? = null,
) : CrtCredentialsProvider {
override val crtProvider: StsAssumeRoleCredentialsProviderCrt = StsAssumeRoleCredentialsProviderCrt.build {
clientBootstrap = SdkDefaultIO.ClientBootstrap
tlsContext = SdkDefaultIO.TlsContext
this.credentialsProvider = asCrt(credentialsProvider)
this.roleArn = roleArn
this.sessionName = sessionName
this.durationSeconds = durationSeconds
@OptIn(ExperimentalTime::class)
public class StsAssumeRoleCredentialsProvider(
private val credentialsProvider: CredentialsProvider,
private val roleArn: String,
private val region: String? = null,
private val roleSessionName: String? = null,
private val externalId: String? = null,
private val duration: Duration = Duration.seconds(DEFAULT_CREDENTIALS_REFRESH_SECONDS),
private val httpClientEngine: HttpClientEngine? = null
) : CredentialsProvider {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: This is a breaking change so the commit message should have a ! after the type/scope.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can/will be fixed up in the squashed commit


override suspend fun getCredentials(): Credentials {
val logger = Logger.getLogger<StsAssumeRoleCredentialsProvider>()
logger.debug { "retrieving assumed credentials" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment: This log message could be made more informative by including additional details like the role ARN, session name, etc.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is role ARN potentially considered sensitive?

client.close()
}

logger.debug { "obtained assumed credentials" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment: This log message could be made more informative by including additional details like expiration time.

Copy link
Contributor

@kggilmer kggilmer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looking to close on handling of exceptions as mentioned in feedback

roleSessionName = provider.roleSessionName ?: defaultSessionName()
durationSeconds = provider.duration.inWholeSeconds.toInt()
}
} catch (ex: Exception) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

concern

This catch seems overly broad. It seems that we'd want to pass I/O exceptions (any anything else that is volatile/environmental) back up directly rather than wrapping in another exception type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

after team discussion it seems that this is the preferred treatment for io exceptions

durationSeconds = provider.duration.inWholeSeconds.toInt()
}
} catch (ex: Exception) {
logger.debug { "sts refused to grant assumed role credentials" }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

Related to previous comment, in the case of say an IO exception this debug message may be misleading to readers, implying some policy constraint in STS where in fact it was something more basic.

logger.debug { "sts refused to grant assumed role credentials" }
when (ex) {
is RegionDisabledException -> throw ProviderConfigurationException(
"STS is not activated in the requested region. Please check your configuration and activate STS in the target region if necessary",
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit

seems like it would be safe to specify the region itself that was the problem in this message.


// role session name must be provided to assume a role, when the user doesn't provide one we choose a name for them
internal fun defaultSessionName(platformEnvironProvider: PlatformEnvironProvider = Platform): String =
AwsSdkSetting.AwsRoleSessionName.resolve(platformEnvironProvider) ?: "aws-sdk-kotlin-${Instant.now().epochMilliseconds}"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question

I assume the prefix aws-sdk-kotlin is meant to disambiguate from something else? where would this string be visible to to the customer, if anywhere? (I'm trying to understand the value of the static string prefix)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It ends up in the assumed role arn, e.g. arn:aws:sts::123456789012:<role-name>/<session-name>

You can see examples in Go and Rust. I opted to follow Go here as I think it's more clear to us and a customer that it is an inferred value.

durationSeconds = provider.duration.inWholeSeconds.toInt()
roleSessionName = provider.roleSessionName ?: defaultSessionName(platformProvider)
}
} catch (ex: Exception) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

concern

same as above regarding capturing I/O exception types

Copy link
Contributor

@kggilmer kggilmer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

concerns regarding exception handling addressed in offline discussion

@github-actions
Copy link

A new generated diff is ready to view: __generated-main...__generated-feat-sts-providers

@sonarqubecloud
Copy link

SonarCloud Quality Gate failed.    Quality Gate failed

Bug A 0 Bugs
Vulnerability A 0 Vulnerabilities
Security Hotspot A 0 Security Hotspots
Code Smell A 7 Code Smells

No Coverage information No Coverage information
16.8% 16.8% Duplication

@github-actions
Copy link

A new generated diff is ready to view: __generated-main...__generated-feat-sts-providers

@aajtodd aajtodd merged commit ba2090e into feat-cred-providers Dec 15, 2021
@aajtodd aajtodd deleted the feat-sts-providers branch December 15, 2021 21:29
aajtodd added a commit that referenced this pull request Feb 17, 2022
…ible (#469)

Refactor credential providers to remove CRT dependency and make them KMP compatible. Added SSO provider to default chain. Lots of misc cleanup and improvements.


* feat(rt): standalone sso credentials provider (#462)
* refactor(rt)!: generated sts and sts web identity credential providers (#470)
* refactor(rt)!: implement kmp ecs provider (#475)
* feat(rt)!: implement kmp profile credentials provider (#478)
* feat(rt)!: kmp default credentials provider chain (#491)
* fix: work around machine-specific Gradle bug with aws-config variants (#496)
* fix: credentials provider ownership (#498)

Co-authored-by: Ian Botsford <83236726+ianbotsf@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants