Skip to content

Conversation

@aajtodd
Copy link
Contributor

@aajtodd aajtodd commented Oct 15, 2021

🚨 Speculative PR alert! 🚨

Interested to hear about the use of CredentialsError w.r.t CredentialsException. The goal was/is to provide more meaningful exceptions and error messages to customers. There are other ways of modeling this though.

Issue #

closes #17

Description of changes

  • Implement EC2 based IMDS CredentialsProvider

NOTE: This has the same issue with loading the active profile per/provider. Eventually we may want to thread through an active profile as a LazyAsyncValue, that way the first to need it will load it, subsequent callers will get the cached version.

Scope

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)

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

@aajtodd aajtodd requested review from ianbotsf and kggilmer October 15, 2021 18:06
client.value.get(CREDENTIALS_BASE_PATH)
} catch (ex: EC2MetadataError) {
if (ex.statusCode == 404) {
logger.info { "received 404 from IMDS when loading profile information. Hint: This instance may not have an IAM role associated" }
Copy link
Contributor

Choose a reason for hiding this comment

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

Nit: "received" → "Received"

return try {
client.value.get(CREDENTIALS_BASE_PATH)
} catch (ex: EC2MetadataError) {
if (ex.statusCode == 404) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Style: Suggest using a constant.

Comment on lines 10 to 39
public sealed class CredentialsError(public val message: String) {
/**
* No credentials were available from this provider
*/
public class CredentialsNotLoaded(message: String?) : CredentialsError(message ?: "The provider could not provide credentials or required configuration was not set")

/**
* The provider was given an invalid configuration (e.g. invalid aws configuration file, invalid IMDS endpoint, etc)
*/
public class InvalidConfiguration(message: String) : CredentialsError(message)

/**
* The provider experienced an error during credentials resolution
*/
public class ProviderError(message: String) : CredentialsError(message)

/**
* An unexpected error occurred during credential resolution.
* If the error is something that can occur during expected usage of a provider, [ProviderError] should be returned
* instead. Unknown is for exceptional cases, for example data this is missing required fields.
*/
public class Unknown(message: String) : CredentialsError(message)

override fun toString(): String = "${this::class.simpleName}: $message"
}

/**
* Exception base class for credential provider errors
*/
public open class CredentialsException(public val error: CredentialsError, cause: Throwable? = null) : ClientException(error.toString(), cause)
Copy link
Contributor

Choose a reason for hiding this comment

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

Comment: Given that these are mutually exclusive and represent distinct types of error, I think these subtypes may work better in the exception hierarchy itself. Kotlin makes it easy to catch exceptions by type with a simple catch block whereas call-site filtering for these would require if/when inside the catch.

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.

Interested in discussing the embedded Error type inside of the Exception


import aws.sdk.kotlin.runtime.ClientException

public sealed class CredentialsError(public val message: String) {
Copy link
Contributor

Choose a reason for hiding this comment

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

style

IMO the least surprising way of modeling this would be that the members of CredentialsError would be concrete exception types with CredentialsException being the parent. What advantage(s) do you see with an error type as a member of a general exception?

throwCredentialsError(CredentialsError.CredentialsNotLoaded("AWS EC2 metadata is explicitly disabled; credentials not loaded"))
}

val profileName = runCatching {
Copy link
Contributor

Choose a reason for hiding this comment

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

question

what is this offering over

val profile = try { ... } catch (e: Ex) { throw CredentialsException(.., e) }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Nothing. Just style. Can update and change to that.

client.value.get(CREDENTIALS_BASE_PATH)
} catch (ex: EC2MetadataError) {
if (ex.statusCode == 404) {
logger.info { "received 404 from IMDS when loading profile information. Hint: This instance may not have an IAM role associated" }
Copy link
Contributor

Choose a reason for hiding this comment

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

nit

terminate last sentence w/ period


override fun close() {
if (client.isInitialized()) {
client.value.close()
Copy link
Contributor

Choose a reason for hiding this comment

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

comment

it would be nice to get lazy eval without having the additional value access. I don't see a way to do it offhand but 🤷

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ya you can't have properties/delegated properties be suspend so I don't see a way around it

val expiration: Instant,
) : JsonCredentialsResponse()

// TODO - add support for static credentials
Copy link
Contributor

Choose a reason for hiding this comment

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

question

these will come in subsequent PR associated w/ the imds client?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Not as much to do with IMDS so much as other JSON based credential responses. IMDS, STS, ECS all have the same JSON response structure (or rather are subsets of one another)


return when (code) {
// IMDS does not appear to reply with `Code` missing but documentation indicates it may be possible
"Success", null -> {
Copy link
Contributor

Choose a reason for hiding this comment

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

question

it's guaranteed no variations in case?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

As far as I know there is not

Copy link
Contributor

Choose a reason for hiding this comment

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

Then probably makes sense to do the string check without regard to case.

@aajtodd aajtodd merged commit b442cd6 into main Oct 19, 2021
@aajtodd aajtodd deleted the feat-ec2-credentials-provider branch October 19, 2021 14:06
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.

AWS Credentials: Implement EC2 credential provider

3 participants