Skip to content

Commit 18559c7

Browse files
authored
feat: write cached auth files with 600 POSIX perms (#1877)
* feat: write cached auth files with `600` POSIX perms * fix changelog message emoji (☝️ very important!!!) * improve changelog message * bump smithy-kotlin version * retrigger CI * update opt-out setting name * update option names, remove profile config key * api dump * lint
1 parent 45a1465 commit 18559c7

7 files changed

Lines changed: 95 additions & 3 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"id": "dda6fb55-d53c-48d6-8aa8-11ec3a7d8732",
3+
"type": "feature",
4+
"description": "⚠️ **IMPORTANT**: Write cached auth files for SSO and AWS Login credentials providers with `600` permissions on POSIX OSes"
5+
}

aws-runtime/aws-config/api/aws-config.api

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,15 @@ public final class aws/sdk/kotlin/runtime/auth/credentials/ProviderConfiguration
141141
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/Throwable;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
142142
}
143143

144+
public final class aws/sdk/kotlin/runtime/auth/credentials/RestrictFilePermissions : java/lang/Enum {
145+
public static final field UNRESTRICTED Laws/sdk/kotlin/runtime/auth/credentials/RestrictFilePermissions;
146+
public static final field USER_READ_WRITE Laws/sdk/kotlin/runtime/auth/credentials/RestrictFilePermissions;
147+
public static fun getEntries ()Lkotlin/enums/EnumEntries;
148+
public final fun getPosixOctal ()Ljava/lang/String;
149+
public static fun valueOf (Ljava/lang/String;)Laws/sdk/kotlin/runtime/auth/credentials/RestrictFilePermissions;
150+
public static fun values ()[Laws/sdk/kotlin/runtime/auth/credentials/RestrictFilePermissions;
151+
}
152+
144153
public final class aws/sdk/kotlin/runtime/auth/credentials/SsoCredentialsProvider : aws/smithy/kotlin/runtime/auth/awscredentials/CredentialsProvider {
145154
public fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/http/engine/HttpClientEngine;Laws/smithy/kotlin/runtime/util/PlatformProvider;Laws/smithy/kotlin/runtime/time/Clock;)V
146155
public synthetic fun <init> (Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Laws/smithy/kotlin/runtime/http/engine/HttpClientEngine;Laws/smithy/kotlin/runtime/util/PlatformProvider;Laws/smithy/kotlin/runtime/time/Clock;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -315,6 +324,7 @@ public final class aws/sdk/kotlin/runtime/config/AwsSdkSetting {
315324
public final fun getAwsRequestChecksumCalculation ()Laws/smithy/kotlin/runtime/config/EnvironmentSetting;
316325
public final fun getAwsRequestMinCompressionSizeBytes ()Laws/smithy/kotlin/runtime/config/EnvironmentSetting;
317326
public final fun getAwsResponseChecksumValidation ()Laws/smithy/kotlin/runtime/config/EnvironmentSetting;
327+
public final fun getAwsRestrictFilePermissions ()Laws/smithy/kotlin/runtime/config/EnvironmentSetting;
318328
public final fun getAwsRetryMode ()Laws/smithy/kotlin/runtime/config/EnvironmentSetting;
319329
public final fun getAwsRoleArn ()Laws/smithy/kotlin/runtime/config/EnvironmentSetting;
320330
public final fun getAwsRoleSessionName ()Laws/smithy/kotlin/runtime/config/EnvironmentSetting;

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/LoginTokenProvider.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,13 @@ import aws.sdk.kotlin.runtime.auth.credentials.internal.signin.createOAuth2Token
1212
import aws.sdk.kotlin.runtime.auth.credentials.internal.signin.model.AccessDeniedException
1313
import aws.sdk.kotlin.runtime.auth.credentials.internal.signin.model.OAuth2ErrorCode
1414
import aws.sdk.kotlin.runtime.auth.credentials.internal.signin.withConfig
15+
import aws.sdk.kotlin.runtime.config.AwsSdkSetting
1516
import aws.sdk.kotlin.runtime.config.profile.normalizePath
1617
import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials
1718
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
1819
import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext
1920
import aws.smithy.kotlin.runtime.collections.Attributes
21+
import aws.smithy.kotlin.runtime.config.resolve
2022
import aws.smithy.kotlin.runtime.hashing.ecdsaSecp256r1Rs
2123
import aws.smithy.kotlin.runtime.hashing.sha256
2224
import aws.smithy.kotlin.runtime.http.engine.HttpClientEngine
@@ -39,6 +41,7 @@ import aws.smithy.kotlin.runtime.time.TimestampFormat
3941
import aws.smithy.kotlin.runtime.util.PlatformProvider
4042
import aws.smithy.kotlin.runtime.util.SingleFlightGroup
4143
import aws.smithy.kotlin.runtime.util.Uuid
44+
import aws.smithy.kotlin.runtime.util.WriteType
4245
import kotlin.coroutines.coroutineContext
4346
import kotlin.io.encoding.Base64
4447
import kotlin.io.encoding.Base64.Default.UrlSafe
@@ -99,6 +102,9 @@ internal class LoginTokenProvider(
99102
// debounce concurrent requests for a token
100103
private val sfg = SingleFlightGroup<LoginToken>()
101104

105+
private val tokenFilePermissions = AwsSdkSetting.AwsRestrictFilePermissions.resolve(platformProvider)
106+
?: RestrictFilePermissions.USER_READ_WRITE
107+
102108
override suspend fun resolve(attributes: Attributes): Credentials {
103109
val token = sfg.singleFlight { getToken(attributes) }
104110

@@ -149,7 +155,12 @@ internal class LoginTokenProvider(
149155
val filepath = normalizePath(platformProvider.filepath(cacheDirectory, cacheKey), platformProvider)
150156
val contents = serializeLoginToken(refreshed)
151157
try {
152-
platformProvider.writeFile(filepath, contents)
158+
platformProvider.write(
159+
filepath,
160+
contents,
161+
WriteType.OVERWRITE,
162+
permissions = tokenFilePermissions.posixOctal,
163+
)
153164
} catch (ex: Exception) {
154165
coroutineContext.debug<LoginTokenProvider>(ex) { "failed to write refreshed token back to disk at $filepath" }
155166
throw ex
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package aws.sdk.kotlin.runtime.auth.credentials
6+
7+
import aws.sdk.kotlin.runtime.InternalSdkApi
8+
9+
/**
10+
* Identifies a set of restricted permissions to apply when writing cached auth files (e.g., during cache token refresh
11+
* for SSO or AWS Login credentials). These values have no effect on Windows.
12+
*/
13+
@InternalSdkApi
14+
public enum class RestrictFilePermissions(public val posixOctal: String?) {
15+
/**
16+
* Specifies POSIX permissions `600`—the user has read/write permissions, everyone else has no permissions
17+
*/
18+
USER_READ_WRITE("600"),
19+
20+
/**
21+
* Specifies no restrictions for new files in the given directory. This typically means that OS-default permissions
22+
* will be applied.
23+
*
24+
* On POSIX-compliant OSes (e.g., Linux and Mac), this is defined by [`umask`](https://en.wikipedia.org/wiki/Umask).
25+
* Most modern OSes default to a umask of `022`, which results in default permissions `644`—the user has read/write
26+
* permissions, everyone else has read permission only.
27+
*/
28+
UNRESTRICTED(null),
29+
}

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/auth/credentials/SsoTokenProvider.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import aws.sdk.kotlin.runtime.ConfigurationException
99
import aws.sdk.kotlin.runtime.auth.credentials.internal.ssooidc.SsoOidcClient
1010
import aws.sdk.kotlin.runtime.auth.credentials.internal.ssooidc.createToken
1111
import aws.sdk.kotlin.runtime.auth.credentials.internal.ssooidc.model.CreateTokenResponse
12+
import aws.sdk.kotlin.runtime.config.AwsSdkSetting
1213
import aws.sdk.kotlin.runtime.config.profile.normalizePath
1314
import aws.smithy.kotlin.runtime.collections.Attributes
1415
import aws.smithy.kotlin.runtime.collections.emptyAttributes
16+
import aws.smithy.kotlin.runtime.config.resolve
1517
import aws.smithy.kotlin.runtime.hashing.sha1
1618
import aws.smithy.kotlin.runtime.http.auth.BearerToken
1719
import aws.smithy.kotlin.runtime.http.auth.BearerTokenProvider
@@ -27,6 +29,7 @@ import aws.smithy.kotlin.runtime.time.Instant
2729
import aws.smithy.kotlin.runtime.time.TimestampFormat
2830
import aws.smithy.kotlin.runtime.util.PlatformProvider
2931
import aws.smithy.kotlin.runtime.util.SingleFlightGroup
32+
import aws.smithy.kotlin.runtime.util.WriteType
3033
import kotlin.coroutines.coroutineContext
3134
import kotlin.time.Duration
3235
import kotlin.time.Duration.Companion.seconds
@@ -57,6 +60,7 @@ private const val OIDC_GRANT_TYPE_REFRESH = "refresh_token"
5760
* @param httpClient the [HttpClientEngine] instance to use to make requests. NOTE: This engine's resources and lifetime
5861
* are NOT managed by the provider. Caller is responsible for closing.
5962
* @param platformProvider the platform provider to use
63+
* @param profile The active AWS profile
6064
* @param clock the source of time for the provider
6165
*/
6266
public class SsoTokenProvider(
@@ -68,10 +72,12 @@ public class SsoTokenProvider(
6872
public val platformProvider: PlatformProvider = PlatformProvider.System,
6973
private val clock: Clock = Clock.System,
7074
) : BearerTokenProvider {
71-
7275
// debounce concurrent requests for a token
7376
private val sfg = SingleFlightGroup<SsoToken>()
7477

78+
private val tokenFilePermissions = AwsSdkSetting.AwsRestrictFilePermissions.resolve(platformProvider)
79+
?: RestrictFilePermissions.USER_READ_WRITE
80+
7581
override suspend fun resolve(attributes: Attributes): BearerToken = sfg.singleFlight {
7682
getToken(attributes)
7783
}
@@ -92,6 +98,7 @@ public class SsoTokenProvider(
9298
coroutineContext.debug<SsoTokenProvider> { "cached token is not refreshable but still valid until ${it.expiration} for sso-session: $ssoSessionName" }
9399
} ?: throwTokenExpired()
94100
}
101+
95102
private suspend fun attemptRefresh(oldToken: SsoToken): SsoToken {
96103
coroutineContext.debug<SsoTokenProvider> { "attempting to refresh token for sso-session: $ssoSessionName" }
97104
val result = runCatching { refreshToken(oldToken) }
@@ -112,7 +119,13 @@ public class SsoTokenProvider(
112119
val filepath = normalizePath(platformProvider.filepath("~", ".aws", "sso", "cache", cacheKey), platformProvider)
113120
try {
114121
val contents = serializeSsoToken(refreshed)
115-
platformProvider.writeFile(filepath, contents)
122+
123+
platformProvider.write(
124+
filepath,
125+
contents,
126+
WriteType.OVERWRITE,
127+
permissions = tokenFilePermissions.posixOctal,
128+
)
116129
} catch (ex: Exception) {
117130
coroutineContext.debug<SsoTokenProvider>(ex) { "failed to write refreshed token back to disk at $filepath" }
118131
}

aws-runtime/aws-config/common/src/aws/sdk/kotlin/runtime/config/AwsSdkSetting.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
package aws.sdk.kotlin.runtime.config
77

88
import aws.sdk.kotlin.runtime.InternalSdkApi
9+
import aws.sdk.kotlin.runtime.auth.credentials.RestrictFilePermissions
10+
import aws.sdk.kotlin.runtime.config.AwsSdkSetting.AwsAccessKeyId
11+
import aws.sdk.kotlin.runtime.config.AwsSdkSetting.AwsContainerCredentialsRelativeUri
12+
import aws.sdk.kotlin.runtime.config.AwsSdkSetting.AwsSecretAccessKey
913
import aws.sdk.kotlin.runtime.config.endpoints.AccountIdEndpointMode
1014
import aws.sdk.kotlin.runtime.http.AWS_APP_ID_ENV
1115
import aws.sdk.kotlin.runtime.http.AWS_APP_ID_PROP
@@ -238,6 +242,15 @@ public object AwsSdkSetting {
238242
*/
239243
public val AwsAuthSchemePreference: EnvironmentSetting<String> = strEnvSetting("aws.authSchemePreference", "AWS_AUTH_SCHEME_PREFERENCE")
240244

245+
/**
246+
* Configures the file permission restrictions used when creating cached authentication token files (e.g., for the
247+
* SSO token provider or Login token provider)
248+
*/
249+
public val AwsRestrictFilePermissions: EnvironmentSetting<RestrictFilePermissions> = enumEnvSetting<RestrictFilePermissions>(
250+
"aws.restrictFilePermissions",
251+
"AWS_RESTRICT_FILE_PERMISSIONS",
252+
)
253+
241254
/**
242255
* Enables the new retry behavior. When set, takes precedence over
243256
* the `SMITHY_NEW_RETRIES_2026` environment variable defined in smithy-kotlin.

aws-runtime/aws-config/common/test/aws/sdk/kotlin/runtime/auth/credentials/SsoTokenProviderTest.kt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import aws.smithy.kotlin.runtime.httptest.TestConnection
1313
import aws.smithy.kotlin.runtime.httptest.buildTestConnection
1414
import aws.smithy.kotlin.runtime.time.Instant
1515
import aws.smithy.kotlin.runtime.time.ManualClock
16+
import aws.smithy.kotlin.runtime.util.MapFilesystem
1617
import aws.smithy.kotlin.runtime.util.TestPlatformProvider
1718
import io.kotest.matchers.string.shouldContain
1819
import kotlinx.coroutines.test.runTest
@@ -117,6 +118,16 @@ class SsoTokenProviderTest {
117118
val expected = deserializeSsoToken(testCase.expectedTokenWritebackContent.encodeToByteArray())
118119
assertEquals(expected, written, "[idx=$idx]: $testCase")
119120
}
121+
122+
if (testCase.refreshResponse != null) {
123+
val fs = testPlatform.fs as MapFilesystem
124+
val actualPermissions = fs.getFilePermissions(cachePath)
125+
assertEquals(
126+
"600",
127+
actualPermissions,
128+
"""Expected `600` permissions on $cachePath for test "${testCase.name}"""",
129+
)
130+
}
120131
}
121132
}
122133
}

0 commit comments

Comments
 (0)