All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonTest.aws.sdk.kotlin.runtime.auth.credentials.EcsCredentialsProviderTest.kt Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */

package aws.sdk.kotlin.runtime.auth.credentials

import aws.sdk.kotlin.runtime.config.AwsSdkSetting
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProviderException
import aws.smithy.kotlin.runtime.http.Headers
import aws.smithy.kotlin.runtime.http.HttpMethod
import aws.smithy.kotlin.runtime.http.HttpStatusCode
import aws.smithy.kotlin.runtime.http.content.ByteArrayContent
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.http.request.HttpRequestBuilder
import aws.smithy.kotlin.runtime.http.request.header
import aws.smithy.kotlin.runtime.http.request.url
import aws.smithy.kotlin.runtime.http.response.HttpResponse
import aws.smithy.kotlin.runtime.httptest.TestConnection
import aws.smithy.kotlin.runtime.httptest.buildTestConnection
import aws.smithy.kotlin.runtime.net.Url
import aws.smithy.kotlin.runtime.retries.StandardRetryStrategy
import aws.smithy.kotlin.runtime.time.Instant
import aws.smithy.kotlin.runtime.time.TimestampFormat
import aws.smithy.kotlin.runtime.util.TestPlatformProvider
import io.kotest.matchers.string.shouldContain
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.test.runTest
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertFailsWith
import kotlin.time.Duration.Companion.minutes

@OptIn(ExperimentalCoroutinesApi::class)
class EcsCredentialsProviderTest {
    private val epoch = Instant.fromIso8601("2020-10-16T03:56:00Z")
    private val expectedExpiration = epoch + 15.minutes
    private val expectedCredentials = Credentials(
        "AKID",
        "test-secret",
        "test-token",
        expectedExpiration,
        "EcsContainer",
    )

    private fun ecsResponse(): HttpResponse {
        val payload = """
        {
            "Code" : "Success",
            "LastUpdated" : "2021-09-17T20:57:08Z",
            "Type" : "AWS-HMAC",
            "AccessKeyId" : "AKID",
            "SecretAccessKey" : "test-secret",
            "Token" : "test-token",
            "Expiration" : "${expectedExpiration.format(TimestampFormat.ISO_8601)}"
        }
        """.encodeToByteArray()
        return HttpResponse(HttpStatusCode.OK, Headers.Empty, ByteArrayContent(payload))
    }

    private fun errorResponse(
        statusCode: HttpStatusCode = HttpStatusCode.BadRequest,
        headers: Headers = Headers.Empty,
        body: String = "",
    ): HttpResponse =
        HttpResponse(statusCode, headers, ByteArrayContent(body.encodeToByteArray()))

    private fun ecsRequest(url: String, authToken: String? = null): HttpRequest {
        val resolvedUrl = Url.parse(url)
        val builder = HttpRequestBuilder().apply {
            method = HttpMethod.GET
            url(resolvedUrl)
            header("Host", resolvedUrl.host.toString())
            header("Accept", "application/json")
            header("Accept-Encoding", "identity")
            if (authToken != null) {
                header("Authorization", authToken)
            }
        }
        return builder.build()
    }

    @Test
    fun testRelativeUri() = runTest {
        val engine = buildTestConnection {
            expect(
                ecsRequest("http://169.254.170.2/relative?foo=bar"),
                ecsResponse(),
            )
        }

        val testPlatform = TestPlatformProvider(
            env = mapOf(AwsSdkSetting.AwsContainerCredentialsRelativeUri.envVar to "/relative?foo=bar"),
        )

        val provider = EcsCredentialsProvider(testPlatform, engine)
        val actual = provider.resolve()
        assertEquals(expectedCredentials, actual)
        engine.assertRequests()
    }

    @Test
    fun testFullUri() = runTest {
        val uri = "http://127.0.0.1/full?foo=bar"
        val engine = buildTestConnection {
            expect(
                ecsRequest(uri),
                ecsResponse(),
            )
        }

        val testPlatform = TestPlatformProvider(
            env = mapOf(AwsSdkSetting.AwsContainerCredentialsFullUri.envVar to uri),
        )

        val provider = EcsCredentialsProvider(testPlatform, engine)
        val actual = provider.resolve()
        assertEquals(expectedCredentials, actual)
        engine.assertRequests()
    }

    @Test
    fun testNonLocalFullUri() = runTest {
        val uri = "http://amazonaws.com/full"
        val engine = TestConnection()

        val testPlatform = TestPlatformProvider(
            env = mapOf(AwsSdkSetting.AwsContainerCredentialsFullUri.envVar to uri),
        )

        val provider = EcsCredentialsProvider(testPlatform, engine)
        assertFailsWith {
            provider.resolve()
        }.message.shouldContain("The container credentials full URI ($uri) has an invalid host. Host can only be one of [127.0.0.1, ::1].")
    }

    @Test
    fun testNonLocalFullUriHttps() = runTest {
        val uri = "https://amazonaws.com/full"
        val engine = buildTestConnection {
            expect(
                ecsRequest(uri),
                ecsResponse(),
            )
        }

        val testPlatform = TestPlatformProvider(
            env = mapOf(AwsSdkSetting.AwsContainerCredentialsFullUri.envVar to uri),
        )

        val provider = EcsCredentialsProvider(testPlatform, engine)
        val actual = provider.resolve()
        assertEquals(expectedCredentials, actual)
        engine.assertRequests()
    }

    @Test
    fun testNoUri() = runTest {
        val engine = TestConnection()
        val testPlatform = TestPlatformProvider()

        val provider = EcsCredentialsProvider(testPlatform, engine)
        assertFailsWith {
            provider.resolve()
        }.message.shouldContain("Container credentials URI not set")
    }

    @Test
    fun testAuthToken() = runTest {
        val token = "auth-token"
        val engine = buildTestConnection {
            expect(
                ecsRequest("http://169.254.170.2/relative", token),
                ecsResponse(),
            )
        }

        val testPlatform = TestPlatformProvider(
            env = mapOf(
                AwsSdkSetting.AwsContainerCredentialsRelativeUri.envVar to "/relative",
                AwsSdkSetting.AwsContainerAuthorizationToken.envVar to token,
            ),
        )

        val provider = EcsCredentialsProvider(testPlatform, engine)
        val actual = provider.resolve()
        assertEquals(expectedCredentials, actual)
        engine.assertRequests()
    }

    @Test
    fun testErrorResponse() = runTest {
        val engine = buildTestConnection {
            expect(
                ecsRequest("http://169.254.170.2/relative"),
                errorResponse(),
            )
        }

        val testPlatform = TestPlatformProvider(
            env = mapOf(AwsSdkSetting.AwsContainerCredentialsRelativeUri.envVar to "/relative"),
        )

        val provider = EcsCredentialsProvider(testPlatform, engine)
        assertFailsWith {
            provider.resolve()
        }.message.shouldContain("Error retrieving credentials from container service: HTTP 400: Bad Request")

        engine.assertRequests()
    }

    @Test
    fun testThrottledErrorResponse() = runTest {
        val engine = buildTestConnection {
            repeat(StandardRetryStrategy.Config.DEFAULT_MAX_ATTEMPTS) {
                expect(
                    ecsRequest("http://169.254.170.2/relative"),
                    errorResponse(statusCode = HttpStatusCode.TooManyRequests),
                )
            }
        }

        val testPlatform = TestPlatformProvider(
            env = mapOf(AwsSdkSetting.AwsContainerCredentialsRelativeUri.envVar to "/relative"),
        )

        val provider = EcsCredentialsProvider(testPlatform, engine)
        assertFailsWith {
            provider.resolve()
        }.message.shouldContain("Error retrieving credentials from container service: HTTP 429: Too Many Requests")

        engine.assertRequests()
    }

    @Test
    fun testJsonErrorResponse() = runTest {
        val engine = buildTestConnection {
            expect(
                ecsRequest("http://169.254.170.2/relative"),
                errorResponse(
                    HttpStatusCode.BadRequest,
                    Headers { append("Content-Type", "application/json") },
                    """
                        {
                            "Code": "failed",
                            "Message": "request was malformed"
                        }
                    """,
                ),
            )
        }

        val testPlatform = TestPlatformProvider(
            env = mapOf(AwsSdkSetting.AwsContainerCredentialsRelativeUri.envVar to "/relative"),
        )

        val provider = EcsCredentialsProvider(testPlatform, engine)
        assertFailsWith {
            provider.resolve()
        }.message.shouldContain("Error retrieving credentials from container service: code=failed; message=request was malformed")

        engine.assertRequests()
    }

    @Test
    fun testThrottledJsonErrorResponse() = runTest {
        val engine = buildTestConnection {
            repeat(StandardRetryStrategy.Config.DEFAULT_MAX_ATTEMPTS) {
                expect(
                    ecsRequest("http://169.254.170.2/relative"),
                    errorResponse(
                        HttpStatusCode.TooManyRequests,
                        Headers { append("Content-Type", "application/json") },
                        """
                        {
                            "Code": "failed",
                            "Message": "too many requests"
                        }
                    """,
                    ),
                )
            }
        }

        val testPlatform = TestPlatformProvider(
            env = mapOf(AwsSdkSetting.AwsContainerCredentialsRelativeUri.envVar to "/relative"),
        )

        val provider = EcsCredentialsProvider(testPlatform, engine)
        assertFailsWith {
            provider.resolve()
        }.message.shouldContain("Error retrieving credentials from container service: code=failed; message=too many requests")

        engine.assertRequests()
    }

    @Test
    fun test5XXErrorResponse() = runTest {
        val engine = buildTestConnection {
            repeat(StandardRetryStrategy.Config.DEFAULT_MAX_ATTEMPTS) {
                expect(
                    ecsRequest("http://169.254.170.2/relative"),
                    errorResponse(
                        HttpStatusCode.BadGateway,
                    ),
                )
            }
        }

        val testPlatform = TestPlatformProvider(
            env = mapOf(AwsSdkSetting.AwsContainerCredentialsRelativeUri.envVar to "/relative"),
        )

        val provider = EcsCredentialsProvider(testPlatform, engine)
        assertFailsWith {
            provider.resolve()
        }.message.shouldContain("Error retrieving credentials from container service: HTTP 502: Bad Gateway")

        engine.assertRequests()
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy