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

commonMain.aws.sdk.kotlin.services.s3.express.DefaultS3ExpressCredentialsProvider.kt Maven / Gradle / Ivy

/*
 * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
 * SPDX-License-Identifier: Apache-2.0
 */
package aws.sdk.kotlin.services.s3.express

import aws.sdk.kotlin.services.s3.*
import aws.sdk.kotlin.services.s3.S3Attributes
import aws.sdk.kotlin.services.s3.S3Client
import aws.smithy.kotlin.runtime.ExperimentalApi
import aws.smithy.kotlin.runtime.auth.awscredentials.CloseableCredentialsProvider
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
import aws.smithy.kotlin.runtime.collections.Attributes
import aws.smithy.kotlin.runtime.collections.get
import aws.smithy.kotlin.runtime.io.SdkManagedBase
import aws.smithy.kotlin.runtime.telemetry.logging.getLogger
import aws.smithy.kotlin.runtime.time.Clock
import aws.smithy.kotlin.runtime.time.until
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
import kotlin.time.TimeSource

/**
 * The default implementation of a credentials provider for S3 Express One Zone. Performs best-effort asynchronous refresh
 * if the cached credentials are expiring within a [refreshBuffer] during a call to [resolve].
 * Otherwise, performs synchronous refresh.
 *
 * @param timeSource the time source to use. defaults to [TimeSource.Monotonic]
 * @param clock the clock to use. defaults to [Clock.System]. note: the clock is only used to get an initial [Duration]
 * until credentials expiration, [timeSource] is used as the source of truth for credentials expiration.
 * @param credentialsCache an [S3ExpressCredentialsCache] to be used for caching session credentials, defaults to
 * [S3ExpressCredentialsCache].
 * @param refreshBuffer an optional [Duration] representing the duration before expiration that [Credentials]
 * are considered refreshable, defaults to 1 minute.
 */
internal class DefaultS3ExpressCredentialsProvider(
    private val timeSource: TimeSource.WithComparableMarks = TimeSource.Monotonic,
    private val clock: Clock = Clock.System,
    private val credentialsCache: S3ExpressCredentialsCache = S3ExpressCredentialsCache(),
    private val refreshBuffer: Duration = 1.minutes,
) : CloseableCredentialsProvider, SdkManagedBase(), CoroutineScope {
    override val coroutineContext: CoroutineContext = Job() + CoroutineName("DefaultS3ExpressCredentialsProvider")

    override suspend fun resolve(attributes: Attributes): Credentials {
        val client = attributes[S3Attributes.ExpressClient] as S3Client

        val key = S3ExpressCredentialsCacheKey(attributes[S3Attributes.Bucket], client.config.credentialsProvider.resolve(attributes))

        return credentialsCache.get(key)
            ?.takeIf { !it.expiringCredentials.isExpired }
            ?.also {
                if (it.expiringCredentials.isExpiringWithin(refreshBuffer)) {
                    client.logger.trace { "Credentials for ${key.bucket} are expiring in ${it.expiringCredentials.expiresAt} and are within their refresh window, performing asynchronous refresh..." }
                    launch(coroutineContext) {
                        try {
                            it.sfg.singleFlight { createSessionCredentials(key, client) }
                        } catch (e: Exception) {
                            client.logger.warn(e) { "Asynchronous refresh for ${key.bucket} failed." }
                        }
                    }
                }
            }
            ?.expiringCredentials
            ?.value
            ?: createSessionCredentials(key, client).value
    }

    override fun close() {
        coroutineContext.cancel(null)
    }

    /**
     * Create a new set of session credentials by calling s3:CreateSession and then store them in the cache.
     */
    internal suspend fun createSessionCredentials(key: S3ExpressCredentialsCacheKey, client: S3Client): ExpiringValue =
        client.createSession { bucket = key.bucket }.credentials!!.let {
            ExpiringValue(
                Credentials(it.accessKeyId, it.secretAccessKey, it.sessionToken, it.expiration),
                expiresAt = timeSource.markNow() + clock.now().until(it.expiration),
            )
        }.also {
            credentialsCache.put(key, S3ExpressCredentialsCacheValue(it))
        }

    @OptIn(ExperimentalApi::class)
    internal val S3Client.logger get() = config.telemetryProvider.loggerProvider.getLogger()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy