Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import aws.smithy.kotlin.runtime.util.asyncLazy
import kotlin.coroutines.coroutineContext

private const val CODE_ASSUME_ROLE_UNAUTHORIZED_ACCESS: String = "AssumeRoleUnauthorizedAccess"
private const val MAX_ATTEMPTS = 10
private const val PROVIDER_NAME = "IMDSv2"

/**
Expand Down Expand Up @@ -105,11 +106,15 @@ public class ImdsCredentialsProvider(

override suspend fun resolve(attributes: Attributes): Credentials = sfg.singleFlight(::resolveSingleFlight)

private suspend fun resolveSingleFlight(): Credentials {
private suspend fun resolveSingleFlight(attempts: Int = 0): Credentials {
if (providerDisabled.get()) {
throw CredentialsNotLoadedException("AWS EC2 metadata is explicitly disabled; credentials not loaded")
}

if (attempts >= MAX_ATTEMPTS) {
throw CredentialsNotLoadedException("Failed to retrieve profile credentials after $attempts attempts")
}

val profileName = instanceProfileName.get() ?: resolvedProfileName ?: try {
actualClient.get(urlBase).also {
if (apiVersion == null) {
Expand All @@ -122,7 +127,7 @@ public class ImdsCredentialsProvider(
apiVersion == null && ex.status == HttpStatusCode.NotFound -> {
// Tried EXTENDED and that didn't work; fallback to LEGACY
apiVersion = ApiVersion.LEGACY
return resolveSingleFlight()
return resolveSingleFlight(attempts) // one-time condition, do not increase `attempts`
}

ex.status == HttpStatusCode.NotFound -> {
Expand All @@ -148,13 +153,13 @@ public class ImdsCredentialsProvider(
apiVersion == null && ex.status == HttpStatusCode.NotFound -> {
// Tried EXTENDED and that didn't work; fallback to LEGACY
apiVersion = ApiVersion.LEGACY
return resolveSingleFlight()
return resolveSingleFlight() // one-time condition, do not increase `attempts`
}

instanceProfileName.get() == null && ex.status == HttpStatusCode.NotFound -> {
// A previously-resolved profile is now invalid; forget the resolved name and re-resolve
resolvedProfileName = null
return resolveSingleFlight()
return resolveSingleFlight(attempts + 1) // potentially infinite recursion, increase `attempts`
}

else -> return usePreviousCredentials()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
package aws.sdk.kotlin.runtime.config

import aws.sdk.kotlin.runtime.InternalSdkApi
import aws.sdk.kotlin.runtime.config.AwsSdkSetting.AwsAccessKeyId
import aws.sdk.kotlin.runtime.config.AwsSdkSetting.AwsContainerCredentialsRelativeUri
import aws.sdk.kotlin.runtime.config.AwsSdkSetting.AwsSecretAccessKey
import aws.sdk.kotlin.runtime.config.endpoints.AccountIdEndpointMode
import aws.sdk.kotlin.runtime.http.AWS_APP_ID_ENV
import aws.sdk.kotlin.runtime.http.AWS_APP_ID_PROP
Expand Down Expand Up @@ -88,7 +91,7 @@ public object AwsSdkSetting {
* Whether to load information such as credentials, regions from EC2 Metadata instance service.
*/
public val AwsEc2MetadataDisabled: EnvironmentSetting<Boolean> =
boolEnvSetting("aws.disableEc2Metadata", "AWS_EC2_METADATA_DISABLED").orElse(false)
boolEnvSetting("aws.ec2MetadataDisabled", "AWS_EC2_METADATA_DISABLED").orElse(false)

/**
* The EC2 instance metadata service endpoint.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ public val AwsProfile.ec2InstanceProfileName: String?
*/
@InternalSdkApi
public val AwsProfile.ec2MetadataDisabled: Boolean?
get() = getBooleanOrNull("disable_ec2_metadata")
get() = getBooleanOrNull("ec2_metadata_disabled")
Copy link
Member

Choose a reason for hiding this comment

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

Modifying these config options is a breaking change, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes it would be. I'm cancelling this PR for now due to other factors but should it be revived in the future, we'll need to check both the old (incorrect) key and the new key.


/**
* Parse a config value as a boolean, ignoring case.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,149 @@ val imdsCredentialsTestSpec = """
"result": "invalid profile"
}
]
},
{
"summary": "Test IMDS credentials provider when profile never stabilizes returns no credentials",
"config": {
"ec2InstanceProfileName": null
},
"expectations": [
{
"get": "/latest/meta-data/iam/security-credentials-extended",
"response": {
"status": 200,
"body": "my-profile-0013"
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended/my-profile-0013",
"response": {
"status": 404
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended",
"response": {
"status": 200,
"body": "my-profile-0013"
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended/my-profile-0013",
"response": {
"status": 404
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended",
"response": {
"status": 200,
"body": "my-profile-0013"
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended/my-profile-0013",
"response": {
"status": 404
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended",
"response": {
"status": 200,
"body": "my-profile-0013"
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended/my-profile-0013",
"response": {
"status": 404
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended",
"response": {
"status": 200,
"body": "my-profile-0013"
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended/my-profile-0013",
"response": {
"status": 404
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended",
"response": {
"status": 200,
"body": "my-profile-0013"
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended/my-profile-0013",
"response": {
"status": 404
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended",
"response": {
"status": 200,
"body": "my-profile-0013"
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended/my-profile-0013",
"response": {
"status": 404
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended",
"response": {
"status": 200,
"body": "my-profile-0013"
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended/my-profile-0013",
"response": {
"status": 404
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended",
"response": {
"status": 200,
"body": "my-profile-0013"
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended/my-profile-0013",
"response": {
"status": 404
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended",
"response": {
"status": 200,
"body": "my-profile-0013"
}
},
{
"get": "/latest/meta-data/iam/security-credentials-extended/my-profile-0013",
"response": {
"status": 404
}
}
],
"outcomes": [
{
"result": "no credentials"
}
]
}
]
""".trimIndent()
Loading