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

implement AWS instance auth #816

Merged
merged 8 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions waltid-libraries/crypto/waltid-crypto/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,13 @@

The library provides the following key entities to work with:

- [JWKKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.kt) -
- [JWKKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/jwk/JWKKey.kt) -
an implementation of a local (in-memory) key (private / public)
- [TSEKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/tse/TSEKey.kt) -
- [TSEKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/tse/TSEKey.kt) -
an implementation of a Hashicorp Vault Transit Secrets Engine key (private / public)
- [AWSKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKey.kt) -
- [AWSKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/aws/AWSKey.kt) -
an implementation of an AWS key (private / public)
- [OCIKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/oci/OCIKeyRestApi.kt) -
- [OCIKey](https://github.com/walt-id/waltid-identity/blob/main/waltid-libraries/crypto/waltid-crypto/src/commonMain/kotlin/id/walt/crypto/keys/oci/OCIKeyRestApi.kt) -
an implementation of an Oracle OCI key (private / public)
<table>
<tbody>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import io.ktor.util.*
import io.ktor.utils.io.charsets.*
import io.ktor.utils.io.core.*
import kotlinx.datetime.Clock
import kotlinx.datetime.Instant
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toInstant
import kotlinx.datetime.toLocalDateTime
Expand All @@ -40,9 +41,21 @@ import kotlin.collections.component2
import kotlin.io.encoding.ExperimentalEncodingApi
import kotlin.js.ExperimentalJsExport
import kotlin.js.JsExport
import kotlin.time.Duration.Companion.seconds

private val logger = KotlinLogging.logger { }

data class AWSAuthConfiguration(
val accessKeyId: String?,
val secretAccessKey: String?,
val sessionToken: String?,
val expiration: String?
)

var _accessAWS: AWSAuthConfiguration? = null
var timeoutAt: Instant? = null


@OptIn(ExperimentalJsExport::class)
@JsExport
@Suppress("TRANSIENT_IS_REDUNDANT")
Expand Down Expand Up @@ -249,9 +262,34 @@ class AWSKey(
@JsExport.Ignore
override suspend fun getMeta(): AwsKeyMeta = AwsKeyMeta(getKeyId())


companion object : AWSKeyCreator {
val client = HttpClient()

@JsExport.Ignore
suspend fun authAccess(config: AWSKeyMetadata) {
val isAccessDataProvided = config.accessKeyId.isNotEmpty() && config.secretAccessKey.isNotEmpty()

if (isAccessDataProvided) {
_accessAWS = AWSAuthConfiguration(config.accessKeyId, config.secretAccessKey, null, null)
timeoutAt = null
} else {
val token = getIMDSv2Token()
val role = getRoleName(token)
_accessAWS = getTemporaryCredentials(token, role)
timeoutAt = Clock.System.now().plus(3600.seconds)
}
}

@JsExport.Ignore
suspend fun getAccess(config: AWSKeyMetadata): AWSAuthConfiguration? {
if (_accessAWS == null || (timeoutAt != null && timeoutAt!! <= Clock.System.now())) {
authAccess(config)
}

return _accessAWS
}


// Utility to hash data using SHA256
@OptIn(ExperimentalStdlibApi::class)
Expand Down Expand Up @@ -360,6 +398,63 @@ ${sha256Hex(canonicalRequest)}
)
}


// Function to get IMDSv2 token
@JsExport.Ignore
suspend fun getIMDSv2Token(ttlSeconds: Int = 21600): String {
val url = "http://169.254.169.254/latest/api/token"
val token = client.put(url) {
headers {
append("X-aws-ec2-metadata-token-ttl-seconds", ttlSeconds.toString())
}
}
logger.trace { "AWS TOKEN: $token" }
return token.bodyAsText()
}

// Function to get role name
@JsExport.Ignore
suspend fun getRoleName(token: String): String {
val url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
val roleName = client.get(url) {
headers {
append("X-aws-ec2-metadata-token", token)
}
}
logger.debug { "AWS Role Name: $roleName" }
return roleName.bodyAsText()
}

// Function to get temporary credentials using role name
@JsExport.Ignore
suspend fun getTemporaryCredentials(token: String, roleName: String): AWSAuthConfiguration {
val url = "http://169.254.169.254/latest/meta-data/iam/security-credentials/$roleName"
val response = client.get(url) {
headers {
append("X-aws-ec2-metadata-token", token)
}
}
val json = Json.parseToJsonElement(response.bodyAsText()).jsonObject


val accessKeyId =
json["AccessKeyId"]?.jsonPrimitive?.content ?: throw IllegalArgumentException("AccessKeyId not found")
val secretAccessKey = json["SecretAccessKey"]?.jsonPrimitive?.content
?: throw IllegalArgumentException("SecretAccessKey not found")
val sessionToken =
json["Token"]?.jsonPrimitive?.content ?: throw IllegalArgumentException("Token not found")
val expiration =
json["Expiration"]?.jsonPrimitive?.content ?: throw IllegalArgumentException("Expiration not found")

return AWSAuthConfiguration(
accessKeyId = accessKeyId,
secretAccessKey = secretAccessKey,
sessionToken = sessionToken,
expiration = expiration
)
}


@JvmBlocking
@JvmAsync
@JsPromise
Expand Down Expand Up @@ -442,6 +537,12 @@ $public

@JsExport.Ignore
override suspend fun generate(type: KeyType, config: AWSKeyMetadata): AWSKey {


if (config.accessKeyId.isEmpty() && config.secretAccessKey.isEmpty()) {
getAccess(config)
}

val keyType = keyTypeToAwsKeyMapping(type)
val body =
"""{
Expand Down
Loading