Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
11 changes: 11 additions & 0 deletions runtime/auth/aws-signing-common/api/aws-signing-common.api
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
public final class aws/smithy/kotlin/runtime/auth/awssigning/AuthTokenGenerator {
public synthetic fun <init> (Ljava/lang/String;Laws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider;JLaws/smithy/kotlin/runtime/auth/awssigning/AwsSigner;Laws/smithy/kotlin/runtime/time/Clock;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public synthetic fun <init> (Ljava/lang/String;Laws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider;JLaws/smithy/kotlin/runtime/auth/awssigning/AwsSigner;Laws/smithy/kotlin/runtime/time/Clock;Lkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun generateAuthToken-exY8QGI (Laws/smithy/kotlin/runtime/net/url/Url;Ljava/lang/String;JLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun getClock ()Laws/smithy/kotlin/runtime/time/Clock;
public final fun getCredentialsProvider ()Laws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider;
public final fun getCredentialsRefreshBuffer-UwyO8pc ()J
public final fun getService ()Ljava/lang/String;
public final fun getSigner ()Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigner;
}

public final class aws/smithy/kotlin/runtime/auth/awssigning/AwsChunkedByteReadChannel : aws/smithy/kotlin/runtime/io/SdkByteReadChannel {
public fun <init> (Laws/smithy/kotlin/runtime/io/SdkByteReadChannel;Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigner;Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigningConfig;[BLaws/smithy/kotlin/runtime/http/DeferredHeaders;)V
public synthetic fun <init> (Laws/smithy/kotlin/runtime/io/SdkByteReadChannel;Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigner;Laws/smithy/kotlin/runtime/auth/awssigning/AwsSigningConfig;[BLaws/smithy/kotlin/runtime/http/DeferredHeaders;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0
*/
package aws.smithy.kotlin.runtime.auth.awssigning

import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
import aws.smithy.kotlin.runtime.auth.awssigning.AwsSigningConfig.Companion.invoke
import aws.smithy.kotlin.runtime.http.HttpMethod
import aws.smithy.kotlin.runtime.http.request.HttpRequest
import aws.smithy.kotlin.runtime.net.url.Url
import aws.smithy.kotlin.runtime.time.Clock
import aws.smithy.kotlin.runtime.util.ExpiringValue
import kotlin.time.Duration
import kotlin.time.Duration.Companion.minutes
import kotlin.time.Duration.Companion.seconds

// The default expiration value to use for [Credentials] when none is provided.
private val DEFAULT_CREDENTIALS_EXPIRATION = 10.minutes

/**
* Generates an authentication token, which is a SigV4-signed URL with the HTTP scheme removed.
* @param service The name of the service the token is being generated for
* @param credentialsProvider The [CredentialsProvider] which will provide credentials to use when generating the auth token
* @param credentialsRefreshBuffer The amount of time before the resolved [Credentials] expire in which they are considered expired, defaults to 10 seconds.
* @param signer The [AwsSigner] implementation to use when creating the authentication token
* @param clock The [Clock] implementation to use
*/
public class AuthTokenGenerator(
public val service: String,
public val credentialsProvider: CredentialsProvider,
public val credentialsRefreshBuffer: Duration = 10.seconds,
public val signer: AwsSigner,
public val clock: Clock = Clock.System,
) {
private lateinit var credentials: ExpiringValue<Credentials>

private fun Url.trimScheme(): String = toString().removePrefix(scheme.protocolName).removePrefix("://")

public suspend fun generateAuthToken(endpoint: Url, region: String, expiration: Duration): String {
if (!::credentials.isInitialized || (credentials.expiresAt - clock.now()).absoluteValue <= credentialsRefreshBuffer) {
val resolved = credentialsProvider.resolve()
credentials = ExpiringValue(resolved, resolved.expiration ?: (clock.now() + DEFAULT_CREDENTIALS_EXPIRATION))
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Question: Why duplicate this logic from our caching layer? Why not simply call the resolve method on the provider like we do in regular signing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't we try to cache credentials instead of resolving them for every call to generateAuthToken?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess the default chain will cache credentials, but not if a user passes in a custom credentials provider.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, people who use a custom creds provider without caching will face the same behaviors in regular requests and in auth token generation, not all of which are necessarily negative. If someone intentionally wants fresh credentials every time or they have their own caching mechanism, we'll bypass that by adding in a new caching layer. We've got a clean, easy way for users to wrap their custom provider chains/impls in our caching provider which should be good enough.


val req = HttpRequest(HttpMethod.GET, endpoint)

val creds = credentials.value
val serv = service

val config = AwsSigningConfig {
credentials = creds
this.region = region
service = serv
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: You can do this.service = service to avoid creating serv. Same for credentials: this.credentials = credentials.value

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No it's not possible, I tried already. It's because those service and credentials are class parameters not function parameters

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought about it some more and was able to get this to work: service = [email protected]

signingDate = clock.now()
expiresAfter = expiration
signatureType = AwsSignatureType.HTTP_REQUEST_VIA_QUERY_PARAMS
}

return signer.sign(req, config).output.url.trimScheme()
}
}
Loading