Skip to content

Commit 4c6317a

Browse files
committed
addressing PR feedback:
* close auto-created ImdsClient in default creds chain * adding support for profile settings * removing println calls leftover from debug * rename resolveUnderLock to resolveSingleFlight * add FIXME for static stability messaging/caching in CachedCredentialsProvider * re-adding removed/broken members for API compatibility
1 parent 8a5114d commit 4c6317a

File tree

9 files changed

+118
-43
lines changed

9 files changed

+118
-43
lines changed

aws-runtime/aws-config/api/aws-config.api

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,9 @@ public final class aws/sdk/kotlin/runtime/auth/credentials/ImdsCredentialsProvid
8282
public fun <init> (Ljava/lang/String;Lkotlin/Lazy;Laws/smithy/kotlin/runtime/util/PlatformEnvironProvider;Laws/smithy/kotlin/runtime/time/Clock;)V
8383
public synthetic fun <init> (Ljava/lang/String;Lkotlin/Lazy;Laws/smithy/kotlin/runtime/util/PlatformEnvironProvider;Laws/smithy/kotlin/runtime/time/Clock;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
8484
public fun close ()V
85+
public final fun getClient ()Lkotlin/Lazy;
86+
public final fun getPlatformProvider ()Laws/smithy/kotlin/runtime/util/PlatformEnvironProvider;
87+
public final fun getProfileOverride ()Ljava/lang/String;
8588
public fun resolve (Laws/smithy/kotlin/runtime/collections/Attributes;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
8689
public fun toString ()Ljava/lang/String;
8790
}
@@ -324,8 +327,10 @@ public final class aws/sdk/kotlin/runtime/config/endpoints/ResolversKt {
324327
}
325328

326329
public final class aws/sdk/kotlin/runtime/config/imds/EC2MetadataError : aws/sdk/kotlin/runtime/AwsServiceException {
330+
public fun <init> (ILjava/lang/String;)V
327331
public fun <init> (Laws/smithy/kotlin/runtime/http/HttpStatusCode;Ljava/lang/String;)V
328-
public final fun getStatusCode ()Laws/smithy/kotlin/runtime/http/HttpStatusCode;
332+
public final fun getStatus ()Laws/smithy/kotlin/runtime/http/HttpStatusCode;
333+
public final fun getStatusCode ()I
329334
}
330335

331336
public abstract class aws/sdk/kotlin/runtime/config/imds/EndpointConfiguration {
@@ -394,6 +399,13 @@ public final class aws/sdk/kotlin/runtime/config/imds/ImdsClient$Companion {
394399
public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/runtime/config/imds/ImdsClient;
395400
}
396401

402+
public final class aws/sdk/kotlin/runtime/config/imds/ImdsResolversKt {
403+
public static final fun resolveDisableEc2Metadata (Laws/smithy/kotlin/runtime/util/PlatformProvider;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
404+
public static synthetic fun resolveDisableEc2Metadata$default (Laws/smithy/kotlin/runtime/util/PlatformProvider;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
405+
public static final fun resolveEc2InstanceProfileName (Laws/smithy/kotlin/runtime/util/PlatformProvider;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
406+
public static synthetic fun resolveEc2InstanceProfileName$default (Laws/smithy/kotlin/runtime/util/PlatformProvider;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
407+
}
408+
397409
public abstract interface class aws/sdk/kotlin/runtime/config/imds/InstanceMetadataProvider : java/io/Closeable {
398410
public abstract fun get (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
399411
}
@@ -494,6 +506,8 @@ public final class aws/sdk/kotlin/runtime/config/profile/AwsProfileKt {
494506
public static synthetic fun getBooleanOrNull$default (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)Ljava/lang/Boolean;
495507
public static final fun getCredentialProcess (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Ljava/lang/String;
496508
public static final fun getDisableRequestCompression (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Ljava/lang/Boolean;
509+
public static final fun getEc2InstanceProfileName (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Ljava/lang/String;
510+
public static final fun getEc2MetadataDisabled (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Ljava/lang/Boolean;
497511
public static final fun getEndpointUrl (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Laws/smithy/kotlin/runtime/net/url/Url;
498512
public static final fun getIgnoreEndpointUrls (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Ljava/lang/Boolean;
499513
public static final fun getIntOrNull (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Integer;

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/DefaultChainCredentialsProvider.kt

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ public class DefaultChainCredentialsProvider(
5151
private val manageEngine = httpClient == null
5252
private val engine = httpClient ?: DefaultHttpEngine()
5353

54+
private val imdsClient = ImdsClient {
55+
platformProvider = this@DefaultChainCredentialsProvider.platformProvider
56+
engine = this@DefaultChainCredentialsProvider.engine
57+
}
58+
5459
private val chain = CredentialsProviderChain(
5560
SystemPropertyCredentialsProvider(platformProvider::getProperty),
5661
EnvironmentCredentialsProvider(platformProvider::getenv),
@@ -59,10 +64,7 @@ public class DefaultChainCredentialsProvider(
5964
ProfileCredentialsProvider(profileName = profileName, platformProvider = platformProvider, httpClient = engine, region = region),
6065
EcsCredentialsProvider(platformProvider, engine),
6166
ImdsCredentialsProvider(
62-
client = ImdsClient {
63-
platformProvider = this@DefaultChainCredentialsProvider.platformProvider
64-
engine = this@DefaultChainCredentialsProvider.engine
65-
},
67+
client = imdsClient,
6668
platformProvider = platformProvider,
6769
),
6870
)
@@ -73,6 +75,7 @@ public class DefaultChainCredentialsProvider(
7375

7476
override fun close() {
7577
provider.close()
78+
imdsClient.close()
7679
if (manageEngine) {
7780
engine.closeIfCloseable()
7881
}

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/ImdsCredentialsProvider.kt

Lines changed: 43 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,11 @@
66
package aws.sdk.kotlin.runtime.auth.credentials
77

88
import aws.sdk.kotlin.runtime.auth.credentials.internal.credentials
9-
import aws.sdk.kotlin.runtime.config.AwsSdkSetting
10-
import aws.sdk.kotlin.runtime.config.imds.EC2MetadataError
11-
import aws.sdk.kotlin.runtime.config.imds.ImdsClient
12-
import aws.sdk.kotlin.runtime.config.imds.InstanceMetadataProvider
9+
import aws.sdk.kotlin.runtime.config.imds.*
1310
import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.AwsBusinessMetric
1411
import aws.sdk.kotlin.runtime.http.interceptors.businessmetrics.withBusinessMetric
1512
import aws.smithy.kotlin.runtime.auth.awscredentials.*
1613
import aws.smithy.kotlin.runtime.collections.Attributes
17-
import aws.smithy.kotlin.runtime.config.resolve
1814
import aws.smithy.kotlin.runtime.http.HttpStatusCode
1915
import aws.smithy.kotlin.runtime.io.IOException
2016
import aws.smithy.kotlin.runtime.serde.json.JsonDeserializer
@@ -23,6 +19,7 @@ import aws.smithy.kotlin.runtime.time.Clock
2319
import aws.smithy.kotlin.runtime.util.PlatformEnvironProvider
2420
import aws.smithy.kotlin.runtime.util.PlatformProvider
2521
import aws.smithy.kotlin.runtime.util.SingleFlightGroup
22+
import aws.smithy.kotlin.runtime.util.asyncLazy
2623
import kotlin.coroutines.coroutineContext
2724

2825
private const val CODE_ASSUME_ROLE_UNAUTHORIZED_ACCESS: String = "AssumeRoleUnauthorizedAccess"
@@ -43,7 +40,7 @@ private const val PROVIDER_NAME = "IMDSv2"
4340
public class ImdsCredentialsProvider(
4441
instanceProfileName: String? = null,
4542
client: InstanceMetadataProvider? = null,
46-
private val platformProvider: PlatformProvider = PlatformProvider.System,
43+
platformProvider: PlatformProvider = PlatformProvider.System,
4744
) : CloseableCredentialsProvider {
4845

4946
@Deprecated("This constructor supports parameters which are no longer used in the implementation. It will be removed in version 1.5.")
@@ -52,20 +49,37 @@ public class ImdsCredentialsProvider(
5249
client: Lazy<InstanceMetadataProvider> = lazy { ImdsClient() },
5350
platformProvider: PlatformEnvironProvider = PlatformProvider.System,
5451
@Suppress("UNUSED_PARAMETER") clock: Clock = Clock.System,
55-
) : this(profileOverride, client.value, platformProvider = platformProvider as? PlatformProvider ?: PlatformProvider.System)
52+
) : this(
53+
profileOverride,
54+
client.value,
55+
platformProvider = platformProvider as? PlatformProvider ?: PlatformProvider.System,
56+
)
57+
58+
private val actualPlatformProvider = platformProvider
59+
60+
@Deprecated("This property is retained for backwards compatibility but no longer needs to be public and will be removed in version 1.5.")
61+
public val platformProvider: PlatformEnvironProvider = actualPlatformProvider
5662

5763
private val manageClient: Boolean = client == null
5864

59-
private val client: InstanceMetadataProvider = client ?: ImdsClient {
60-
this.platformProvider = this@ImdsCredentialsProvider.platformProvider
65+
private val actualClient = client ?: ImdsClient {
66+
this.platformProvider = actualPlatformProvider
6167
}
6268

63-
// FIXME This only resolves from env vars and sys props but we need to resolve from profiles too
64-
private val instanceProfileName = instanceProfileName
65-
?: AwsSdkSetting.AwsEc2InstanceProfileName.resolve(platformProvider)
69+
@Deprecated("This property is retained for backwards compatibility but no longer needs to be public and will be removed in version 1.5.")
70+
public val client: Lazy<InstanceMetadataProvider>
71+
get() = lazyOf(actualClient)
72+
73+
private val instanceProfileName = asyncLazy {
74+
instanceProfileName ?: resolveEc2InstanceProfileName(platformProvider)
75+
}
6676

67-
// FIXME This only resolves from env vars and sys props but we need to resolve from profiles too
68-
private val providerDisabled = AwsSdkSetting.AwsEc2MetadataDisabled.resolve(platformProvider) == true
77+
@Deprecated("This property is retained for backwards compatibility but no longer needs to be public and will be removed in version 1.5.")
78+
public val profileOverride: String? = instanceProfileName
79+
80+
private val providerDisabled = asyncLazy {
81+
resolveDisableEc2Metadata(platformProvider) ?: false
82+
}
6983

7084
/**
7185
* Tracks the known-good version of IMDS APIs available in the local environment. This starts as `null` and will be
@@ -89,33 +103,29 @@ public class ImdsCredentialsProvider(
89103
*/
90104
private val sfg = SingleFlightGroup<Credentials>()
91105

92-
override suspend fun resolve(attributes: Attributes): Credentials = sfg.singleFlight { resolveUnderLock() }
93-
94-
private suspend fun resolveUnderLock(): Credentials {
95-
println("**** Resolving creds (instanceProfileName=$instanceProfileName; apiVersion=$apiVersion; urlBase=$urlBase)")
106+
override suspend fun resolve(attributes: Attributes): Credentials = sfg.singleFlight(::resolveSingleFlight)
96107

97-
if (providerDisabled) {
98-
println("**** Explicitly disabled")
108+
private suspend fun resolveSingleFlight(): Credentials {
109+
if (providerDisabled.get()) {
99110
throw CredentialsNotLoadedException("AWS EC2 metadata is explicitly disabled; credentials not loaded")
100111
}
101112

102-
val profileName = instanceProfileName ?: resolvedProfileName ?: try {
103-
println("**** Resolving profile")
104-
client.get(urlBase).also {
113+
val profileName = instanceProfileName.get() ?: resolvedProfileName ?: try {
114+
actualClient.get(urlBase).also {
105115
if (apiVersion == null) {
106116
// Tried EXTENDED and it worked; remember that for the future
107117
apiVersion = ApiVersion.EXTENDED
108118
}
109119
}
110120
} catch (ex: EC2MetadataError) {
111121
when {
112-
apiVersion == null && ex.statusCode == HttpStatusCode.NotFound -> {
122+
apiVersion == null && ex.status == HttpStatusCode.NotFound -> {
113123
// Tried EXTENDED and that didn't work; fallback to LEGACY
114124
apiVersion = ApiVersion.LEGACY
115-
return resolveUnderLock()
125+
return resolveSingleFlight()
116126
}
117127

118-
ex.statusCode == HttpStatusCode.NotFound -> {
128+
ex.status == HttpStatusCode.NotFound -> {
119129
coroutineContext.info<ImdsCredentialsProvider> {
120130
"Received 404 when loading profile name. This instance may not have an associated profile."
121131
}
@@ -132,19 +142,19 @@ public class ImdsCredentialsProvider(
132142
}
133143

134144
val credsPayload = try {
135-
client.get("$urlBase$profileName")
145+
actualClient.get("$urlBase$profileName")
136146
} catch (ex: EC2MetadataError) {
137147
when {
138-
apiVersion == null && ex.statusCode == HttpStatusCode.NotFound -> {
148+
apiVersion == null && ex.status == HttpStatusCode.NotFound -> {
139149
// Tried EXTENDED and that didn't work; fallback to LEGACY
140150
apiVersion = ApiVersion.LEGACY
141-
return resolveUnderLock()
151+
return resolveSingleFlight()
142152
}
143153

144-
instanceProfileName == null && ex.statusCode == HttpStatusCode.NotFound -> {
154+
instanceProfileName.get() == null && ex.status == HttpStatusCode.NotFound -> {
145155
// A previously-resolved profile is now invalid; forget the resolved name and re-resolve
146156
resolvedProfileName = null
147-
return resolveUnderLock()
157+
return resolveSingleFlight()
148158
}
149159

150160
else -> return usePreviousCredentials()
@@ -157,7 +167,7 @@ public class ImdsCredentialsProvider(
157167
throw ImdsCredentialsException(profileName, ex).wrapAsCredentialsProviderException()
158168
}
159169

160-
if (instanceProfileName == null) {
170+
if (instanceProfileName.get() == null) {
161171
// No profile name was provided at construction time; cache the resolved name
162172
resolvedProfileName = profileName
163173
}
@@ -186,7 +196,7 @@ public class ImdsCredentialsProvider(
186196

187197
override fun close() {
188198
if (manageClient) {
189-
client.close()
199+
actualClient.close()
190200
}
191201
}
192202

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsClient.kt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,13 @@ public enum class EndpointMode(internal val defaultEndpoint: Endpoint) {
228228

229229
/**
230230
* Exception thrown when an error occurs retrieving metadata from IMDS
231-
* @param statusCode The HTTP status code of the response
231+
* @param status The HTTP status code of the response
232232
* @param message The error message
233233
*/
234-
public class EC2MetadataError(public val statusCode: HttpStatusCode, message: String) : AwsServiceException(message)
234+
public class EC2MetadataError(public val status: HttpStatusCode, message: String) : AwsServiceException(message) {
235+
@Deprecated("This constructor passes HTTP status as an Int instead of as HttpStatusCode")
236+
public constructor(statusCode: Int, message: String) : this(HttpStatusCode.fromValue(statusCode), message)
237+
238+
@Deprecated("This property is now deprecated and should be fetched from status.value")
239+
public val statusCode: Int = status.value
240+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package aws.sdk.kotlin.runtime.config.imds
2+
3+
import aws.sdk.kotlin.runtime.InternalSdkApi
4+
import aws.sdk.kotlin.runtime.config.AwsSdkSetting
5+
import aws.sdk.kotlin.runtime.config.profile.AwsProfile
6+
import aws.sdk.kotlin.runtime.config.profile.ec2InstanceProfileName
7+
import aws.sdk.kotlin.runtime.config.profile.ec2MetadataDisabled
8+
import aws.sdk.kotlin.runtime.config.profile.loadAwsSharedConfig
9+
import aws.smithy.kotlin.runtime.config.resolve
10+
import aws.smithy.kotlin.runtime.util.LazyAsyncValue
11+
import aws.smithy.kotlin.runtime.util.PlatformProvider
12+
import aws.smithy.kotlin.runtime.util.asyncLazy
13+
14+
/**
15+
* Attempts to resolve a named EC2 instance profile to use which allows bypassing auto-discovery
16+
*/
17+
@InternalSdkApi
18+
public suspend fun resolveEc2InstanceProfileName(
19+
provider: PlatformProvider = PlatformProvider.System,
20+
profile: LazyAsyncValue<AwsProfile> = asyncLazy { loadAwsSharedConfig(provider).activeProfile },
21+
): String? = AwsSdkSetting.AwsEc2InstanceProfileName.resolve(provider) ?: profile.get().ec2InstanceProfileName
22+
23+
/**
24+
* Attempts to resolve the flag which disables the use of IMDS for credentials
25+
*/
26+
public suspend fun resolveDisableEc2Metadata(
27+
provider: PlatformProvider = PlatformProvider.System,
28+
profile: LazyAsyncValue<AwsProfile> = asyncLazy { loadAwsSharedConfig(provider).activeProfile },
29+
): Boolean? = AwsSdkSetting.AwsEc2MetadataDisabled.resolve(provider) ?: profile.get().ec2MetadataDisabled

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/imds/ImdsRetryPolicy.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ internal class ImdsRetryPolicy(
2323

2424
private fun evaluate(throwable: Throwable): RetryDirective = when (throwable) {
2525
is EC2MetadataError -> {
26-
val status = throwable.statusCode
26+
val status = throwable.status
2727
when {
2828
status.category() == HttpStatusCode.Category.SERVER_ERROR -> RetryDirective.RetryError(RetryErrorType.ServerSide)
2929
// 401 indicates the token has expired, this is retryable

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/profile/AwsProfile.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,20 @@ public val AwsProfile.requestChecksumCalculation: RequestHttpChecksumConfig?
169169
public val AwsProfile.responseChecksumValidation: ResponseHttpChecksumConfig?
170170
get() = getEnumOrNull<ResponseHttpChecksumConfig>("response_checksum_validation")
171171

172+
/**
173+
* Specifies a named EC2 instance profile to use which allows bypassing auto-discovery
174+
*/
175+
@InternalSdkApi
176+
public val AwsProfile.ec2InstanceProfileName: String?
177+
get() = getOrNull("ec2_instance_profile_name")
178+
179+
/**
180+
* The flag which disables the use of IMDS for credentials
181+
*/
182+
@InternalSdkApi
183+
public val AwsProfile.ec2MetadataDisabled: Boolean?
184+
get() = getBooleanOrNull("disable_ec2_metadata")
185+
172186
/**
173187
* Parse a config value as a boolean, ignoring case.
174188
*/

aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/config/imds/ImdsClientTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ class ImdsClientTest {
203203
client.get("/latest/metadata")
204204
}
205205

206-
assertEquals(HttpStatusCode.Forbidden, ex.statusCode)
206+
assertEquals(HttpStatusCode.Forbidden, ex.status)
207207
connection.assertRequests()
208208
}
209209

aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/util/VerifyingInstanceMetadataProvider.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ class VerifyingInstanceMetadataProvider(expectations: List<Pair<String, () -> St
1212

1313
override suspend fun get(path: String): String {
1414
val trimmedPath = path.trimEnd('/') // remove trailing slashes to simplify testing
15-
println("**** IMDS: $trimmedPath")
1615
val next = assertNotNull(expectations.removeFirstOrNull(), "Call to \"$trimmedPath\" was unexpected!")
1716
val (expectedPath, result) = next
1817
assertEquals(trimmedPath, expectedPath, "Expected call to \"$expectedPath\" but got \"$trimmedPath\" instead!")

0 commit comments

Comments
 (0)