Skip to content

Commit 44d2d5c

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 e7e0e5a commit 44d2d5c

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
}
@@ -348,8 +351,10 @@ public final class aws/sdk/kotlin/runtime/config/endpoints/ResolversKt {
348351
}
349352

350353
public final class aws/sdk/kotlin/runtime/config/imds/EC2MetadataError : aws/sdk/kotlin/runtime/AwsServiceException {
354+
public fun <init> (ILjava/lang/String;)V
351355
public fun <init> (Laws/smithy/kotlin/runtime/http/HttpStatusCode;Ljava/lang/String;)V
352-
public final fun getStatusCode ()Laws/smithy/kotlin/runtime/http/HttpStatusCode;
356+
public final fun getStatus ()Laws/smithy/kotlin/runtime/http/HttpStatusCode;
357+
public final fun getStatusCode ()I
353358
}
354359

355360
public abstract class aws/sdk/kotlin/runtime/config/imds/EndpointConfiguration {
@@ -418,6 +423,13 @@ public final class aws/sdk/kotlin/runtime/config/imds/ImdsClient$Companion {
418423
public final fun invoke (Lkotlin/jvm/functions/Function1;)Laws/sdk/kotlin/runtime/config/imds/ImdsClient;
419424
}
420425

426+
public final class aws/sdk/kotlin/runtime/config/imds/ImdsResolversKt {
427+
public static final fun resolveDisableEc2Metadata (Laws/smithy/kotlin/runtime/util/PlatformProvider;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
428+
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;
429+
public static final fun resolveEc2InstanceProfileName (Laws/smithy/kotlin/runtime/util/PlatformProvider;Laws/smithy/kotlin/runtime/util/LazyAsyncValue;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
430+
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;
431+
}
432+
421433
public abstract interface class aws/sdk/kotlin/runtime/config/imds/InstanceMetadataProvider : java/io/Closeable {
422434
public abstract fun get (Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
423435
}
@@ -518,6 +530,8 @@ public final class aws/sdk/kotlin/runtime/config/profile/AwsProfileKt {
518530
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;
519531
public static final fun getCredentialProcess (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Ljava/lang/String;
520532
public static final fun getDisableRequestCompression (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Ljava/lang/Boolean;
533+
public static final fun getEc2InstanceProfileName (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Ljava/lang/String;
534+
public static final fun getEc2MetadataDisabled (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Ljava/lang/Boolean;
521535
public static final fun getEndpointDiscoveryEnabled (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Ljava/lang/Boolean;
522536
public static final fun getEndpointUrl (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Laws/smithy/kotlin/runtime/net/url/Url;
523537
public static final fun getIgnoreEndpointUrls (Laws/sdk/kotlin/runtime/config/profile/ConfigSection;)Ljava/lang/Boolean;

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
@@ -177,6 +177,20 @@ public val AwsProfile.requestChecksumCalculation: RequestHttpChecksumConfig?
177177
public val AwsProfile.responseChecksumValidation: ResponseHttpChecksumConfig?
178178
get() = getEnumOrNull<ResponseHttpChecksumConfig>("response_checksum_validation")
179179

180+
/**
181+
* Specifies a named EC2 instance profile to use which allows bypassing auto-discovery
182+
*/
183+
@InternalSdkApi
184+
public val AwsProfile.ec2InstanceProfileName: String?
185+
get() = getOrNull("ec2_instance_profile_name")
186+
187+
/**
188+
* The flag which disables the use of IMDS for credentials
189+
*/
190+
@InternalSdkApi
191+
public val AwsProfile.ec2MetadataDisabled: Boolean?
192+
get() = getBooleanOrNull("disable_ec2_metadata")
193+
180194
/**
181195
* Parse a config value as a boolean, ignoring case.
182196
*/

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)