Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: implement smithy-modeled endpoint resolution for aws sdk #759

Merged
merged 13 commits into from
Nov 21, 2022
Merged
Show file tree
Hide file tree
Changes from 11 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
5 changes: 5 additions & 0 deletions .changes/019ad769-8cf1-48fc-b898-5c325fca6917.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "019ad769-8cf1-48fc-b898-5c325fca6917",
"type": "feature",
"description": "Add support for (S3 Transfer Acceleration)[https://docs.aws.amazon.com/AmazonS3/latest/userguide/transfer-acceleration.html]."
}
8 changes: 8 additions & 0 deletions .changes/20d71413-8e11-4769-bb84-ca93c82bd6fc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "20d71413-8e11-4769-bb84-ca93c82bd6fc",
"type": "feature",
"description": "Add support for (S3 Access Points)[https://aws.amazon.com/s3/features/access-points/].",
"issues": [
"awslabs/aws-sdk-kotlin#231"
]
}
5 changes: 5 additions & 0 deletions .changes/231f0336-689b-4a85-8b72-1059a9c4ce61.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "231f0336-689b-4a85-8b72-1059a9c4ce61",
"type": "feature",
"description": "Add support for dual-stack endpoints in client config."
}
5 changes: 5 additions & 0 deletions .changes/552c1b75-b19c-49e9-ac05-2f4a88ec2bb9.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "552c1b75-b19c-49e9-ac05-2f4a88ec2bb9",
"type": "feature",
"description": "**BREAKING** Add smithy-modeled endpoint resolvers for AWS services."
}
5 changes: 5 additions & 0 deletions .changes/5e992ee9-f624-423c-a388-afec012e39be.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "5e992ee9-f624-423c-a388-afec012e39be",
"type": "feature",
"description": "Add support for (S3 PrivateLink)[https://docs.aws.amazon.com/AmazonS3/latest/userguide/privatelink-interface-endpoints.html]."
}
8 changes: 8 additions & 0 deletions .changes/64c54161-6b84-4251-a1fc-a7c568424ee6.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "64c54161-6b84-4251-a1fc-a7c568424ee6",
"type": "feature",
"description": "Add support for (S3 Virtual Host Addressing)[https://docs.aws.amazon.com/AmazonS3/latest/userguide/VirtualHosting.html] (enabled by default).",
"issues": [
"awslabs/aws-sdk-kotlin#399"
]
}
5 changes: 5 additions & 0 deletions .changes/82affce8-73dd-42be-ab9e-a737337652c1.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "82affce8-73dd-42be-ab9e-a737337652c1",
"type": "feature",
"description": "Add support for [FIPS](https://aws.amazon.com/compliance/fips/) endpoints in client config."
}
5 changes: 5 additions & 0 deletions .changes/9a088733-20ae-47fc-932a-ed5dd5959e7a.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "9a088733-20ae-47fc-932a-ed5dd5959e7a",
"type": "feature",
"description": "Add support for (S3 Object Lambda)[https://aws.amazon.com/s3/features/object-lambda/]."
}
5 changes: 5 additions & 0 deletions .changes/e2422015-caee-497f-8049-feeda953bcaf.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"id": "e2422015-caee-497f-8049-feeda953bcaf",
"type": "feature",
"description": "Add support for (S3 Outposts)[https://aws.amazon.com/s3/outposts/]."
}
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public class EcsCredentialsProvider internal constructor(
}
}

op.install(ResolveEndpoint(resolver = { Endpoint(url) }))
op.install(ResolveEndpoint(provider = { Endpoint(url) }, params = null))
op.install(retryMiddleware)

logger.debug { "retrieving container credentials" }
Expand Down Expand Up @@ -153,9 +153,9 @@ public class EcsCredentialsProvider internal constructor(

// TODO - validate loopback via DNS resolution instead of fixed set. Custom host names (including localhost) that
// resolve to loopback won't work until then. ALL resolved addresses MUST resolve to the loopback device
val allowedHosts = setOf("127.0.0.1", "[::1]")
val allowedHosts = setOf("127.0.0.1", "::1")

if (url.host !in allowedHosts) {
if (url.host.toString() !in allowedHosts) {
throw ProviderConfigurationException(
"The container credentials full URI ($uri) has an invalid host. Host can only be one of [${allowedHosts.joinToString()}].",
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public class ImdsClient private constructor(builder: Builder) : InstanceMetadata
}

// cached middleware instances
private val resolveEndpointMiddleware = ResolveEndpoint(ImdsEndpointResolver(platformProvider, endpointConfiguration))
private val resolveEndpointMiddleware = ResolveEndpoint(ImdsEndpointProvider(platformProvider, endpointConfiguration), Unit)
private val userAgentMiddleware = UserAgent(
staticMetadata = AwsUserAgentMetadata.fromEnvironment(ApiMetadata(SERVICE, "unknown")),
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,22 @@ import aws.sdk.kotlin.runtime.config.AwsSdkSetting
import aws.sdk.kotlin.runtime.config.profile.loadActiveAwsProfile
import aws.sdk.kotlin.runtime.config.resolve
import aws.smithy.kotlin.runtime.http.endpoints.Endpoint
import aws.smithy.kotlin.runtime.http.endpoints.EndpointResolver
import aws.smithy.kotlin.runtime.http.endpoints.EndpointProvider
import aws.smithy.kotlin.runtime.util.PlatformProvider
import aws.smithy.kotlin.runtime.util.asyncLazy

internal const val EC2_METADATA_SERVICE_ENDPOINT_PROFILE_KEY = "ec2_metadata_service_endpoint"
internal const val EC2_METADATA_SERVICE_ENDPOINT_MODE_PROFILE_KEY = "ec2_metadata_service_endpoint_mode"

internal class ImdsEndpointResolver(
internal class ImdsEndpointProvider(
private val platformProvider: PlatformProvider,
private val endpointConfiguration: EndpointConfiguration,
) : EndpointResolver {
) : EndpointProvider<Unit> {
// cached endpoint and profile
private val resolvedEndpoint = asyncLazy(::doResolveEndpoint)
private val activeProfile = asyncLazy { loadActiveAwsProfile(platformProvider) }

override suspend fun resolve(): Endpoint = resolvedEndpoint.get()
override suspend fun resolveEndpoint(params: Unit): Endpoint = resolvedEndpoint.get()

private suspend fun doResolveEndpoint(): Endpoint = when (endpointConfiguration) {
is EndpointConfiguration.Custom -> endpointConfiguration.endpoint
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ class EcsCredentialsProviderTest {
val builder = HttpRequestBuilder().apply {
method = HttpMethod.GET
url(resolvedUrl)
header("Host", resolvedUrl.host)
header("Host", resolvedUrl.host.toString())
header("Accept", "application/json")
header("Accept-Encoding", "identity")
if (authToken != null) {
Expand Down Expand Up @@ -131,7 +131,7 @@ class EcsCredentialsProviderTest {
val provider = EcsCredentialsProvider(testPlatform, engine)
assertFailsWith<ProviderConfigurationException> {
provider.getCredentials()
}.message.shouldContain("The container credentials full URI ($uri) has an invalid host. Host can only be one of [127.0.0.1, [::1]].")
}.message.shouldContain("The container credentials full URI ($uri) has an invalid host. Host can only be one of [127.0.0.1, ::1].")
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import aws.smithy.kotlin.runtime.time.Instant
import aws.smithy.kotlin.runtime.time.ManualClock
import aws.smithy.kotlin.runtime.time.epochMilliseconds
import aws.smithy.kotlin.runtime.time.fromEpochMilliseconds
import aws.smithy.kotlin.runtime.util.net.Host
import io.kotest.matchers.string.shouldContain
import io.mockk.coVerify
import io.mockk.spyk
Expand Down Expand Up @@ -503,7 +504,7 @@ class ImdsCredentialsProviderTest {
override suspend fun roundTrip(context: ExecutionContext, request: HttpRequest): HttpCall {
if (successfulCallCount >= 2) {
return HttpCall(
HttpRequest(HttpMethod.GET, Url(Protocol.HTTP, "test", Protocol.HTTP.defaultPort, "/path/foo/bar"), Headers.Empty, HttpBody.Empty),
HttpRequest(HttpMethod.GET, Url(Protocol.HTTP, Host.parse("test"), Protocol.HTTP.defaultPort, "/path/foo/bar"), Headers.Empty, HttpBody.Empty),
HttpResponse(HttpStatusCode.InternalServerError, Headers.Empty, HttpBody.Empty),
testClock.now(),
testClock.now(),
Expand Down Expand Up @@ -578,7 +579,7 @@ class ImdsCredentialsProviderTest {
val internalServerErrorEngine = object : HttpClientEngineBase("internalServerError") {
override suspend fun roundTrip(context: ExecutionContext, request: HttpRequest): HttpCall {
return HttpCall(
HttpRequest(HttpMethod.GET, Url(Protocol.HTTP, "test", Protocol.HTTP.defaultPort, "/path/foo/bar"), Headers.Empty, HttpBody.Empty),
HttpRequest(HttpMethod.GET, Url(Protocol.HTTP, Host.parse("test"), Protocol.HTTP.defaultPort, "/path/foo/bar"), Headers.Empty, HttpBody.Empty),
HttpResponse(HttpStatusCode.InternalServerError, Headers.Empty, HttpBody.Empty),
testClock.now(),
testClock.now(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import aws.sdk.kotlin.runtime.testing.TestPlatformProvider
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
import aws.smithy.kotlin.runtime.httptest.TestConnection
import aws.smithy.kotlin.runtime.httptest.buildTestConnection
import aws.smithy.kotlin.runtime.util.net.Host
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
Expand Down Expand Up @@ -134,6 +135,6 @@ class ProfileCredentialsProviderTest {
testEngine.assertRequests()
val req = testEngine.requests().first()
// region is overridden from the environment which should take precedence
assertEquals("sts.us-west-2.amazonaws.com", req.actual.url.host)
assertEquals(Host.Domain("sts.us-west-2.amazonaws.com"), req.actual.url.host)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import aws.smithy.kotlin.runtime.http.HttpStatusCode
import aws.smithy.kotlin.runtime.http.content.ByteArrayContent
import aws.smithy.kotlin.runtime.http.response.HttpResponse
import aws.smithy.kotlin.runtime.httptest.buildTestConnection
import aws.smithy.kotlin.runtime.util.net.Host
import io.kotest.matchers.string.shouldContain
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
Expand Down Expand Up @@ -117,7 +118,7 @@ class StsAssumeRoleCredentialsProviderTest {
val actual = provider.getCredentials()
assertEquals(StsTestUtils.expectedCredentialsBase, actual)
val req = testEngine.requests().first()
assertEquals("sts.amazonaws.com", req.actual.url.host)
assertEquals(Host.Domain("sts.amazonaws.com"), req.actual.url.host)
}

@Test
Expand All @@ -136,6 +137,6 @@ class StsAssumeRoleCredentialsProviderTest {
val actual = provider.getCredentials()
assertEquals(StsTestUtils.expectedCredentialsBase, actual)
val req = testEngine.requests().first()
assertEquals("sts.us-west-2.amazonaws.com", req.actual.url.host)
assertEquals(Host.Domain("sts.us-west-2.amazonaws.com"), req.actual.url.host)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ internal const val imdsTestSuite = """
},
"fs": {},
"result": {
"Err": "Illegal character"
"Err": "is not a valid inet host"
}
},
{
Expand Down
1 change: 1 addition & 0 deletions aws-runtime/aws-endpoint/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ kotlin {
}

all {
languageSettings.optIn("aws.smithy.kotlin.runtime.util.InternalApi")
languageSettings.optIn("aws.sdk.kotlin.runtime.InternalSdkApi")
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.sdk.kotlin.runtime.endpoint

import aws.sdk.kotlin.runtime.InternalSdkApi
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningAttributes
import aws.smithy.kotlin.runtime.auth.awssigning.SigningContext
import aws.smithy.kotlin.runtime.http.endpoints.Endpoint
import aws.smithy.kotlin.runtime.http.operation.SdkHttpRequest
import aws.smithy.kotlin.runtime.util.AttributeKey

/**
* Static attribute key for AWS endpoint auth schemes.
*/
@InternalSdkApi
public val AuthSchemesAttributeKey: AttributeKey<List<AuthScheme>> = AttributeKey("authSchemes")

/**
* A set of signing constraints for an AWS endpoint.
*/
@InternalSdkApi
public sealed class AuthScheme {
public data class SigV4(
public val signingName: String?,
public val disableDoubleEncoding: Boolean,
public val signingRegion: String?,
) : AuthScheme()

public data class SigV4A(
public val signingName: String?,
public val disableDoubleEncoding: Boolean,
public val signingRegionSet: List<String>,
) : AuthScheme()
}

/**
* Sugar extension to pull an auth scheme out of the attribute set.
*
* FUTURE: Right now we only support sigv4. The auth scheme property can include multiple schemes, for now we only pull
* out the sigv4 one if present.
*/
@InternalSdkApi
public val Endpoint.authScheme: AuthScheme.SigV4?
get() = attributes.getOrNull(AuthSchemesAttributeKey)?.find { it is AuthScheme.SigV4 } as? AuthScheme.SigV4

@InternalSdkApi
public fun AuthScheme.SigV4.asSigningContext(): SigningContext = SigningContext(signingName, signingRegion)

/**
* Update a request's signing context properties with the receiving auth scheme.
*/
@InternalSdkApi
public fun AuthScheme.SigV4.applyToRequest(req: SdkHttpRequest) {
signingName?.let {
if (it.isNotBlank()) req.context[AwsSigningAttributes.SigningService] = it
}
signingRegion?.let {
if (it.isNotBlank()) req.context[AwsSigningAttributes.SigningRegion] = it
}
}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading