diff --git a/src/MongoDB.Driver.Authentication.AWS/AGENTS.md b/src/MongoDB.Driver.Authentication.AWS/AGENTS.md index 52d99faed15..d00d29fee80 100644 --- a/src/MongoDB.Driver.Authentication.AWS/AGENTS.md +++ b/src/MongoDB.Driver.Authentication.AWS/AGENTS.md @@ -9,7 +9,7 @@ adjacent-areas: [Driver/Authentication] This is an **optional** project providing the `MONGODB-AWS` SASL mechanism. Kept separate from `MongoDB.Driver` so that consumers who don't need AWS IAM auth aren't forced to ship the AWS SDK / signing dependencies. -For full coverage — credential resolution (explicit MongoCredential vs the AWS SDK's `FallbackCredentialsFactory` chain; consult the AWS SDK for the authoritative source order), SigV4 signing of `sts`-service requests, region derivation from the STS host header (defaulting to `us-east-1`), and registration via the `ISaslMechanismRegistry` exposed by `MongoClientSettings.Extensions.SaslMechanisms` — see the AWS IAM section in `src/MongoDB.Driver/Authentication/AGENTS.md`. +For full coverage — credential resolution (explicit MongoCredential vs the AWS SDK's `DefaultAWSCredentialsIdentityResolver` chain; consult the AWS SDK for the authoritative source order), SigV4 signing of `sts`-service requests, region derivation from the STS host header (defaulting to `us-east-1`), and registration via the `ISaslMechanismRegistry` exposed by `MongoClientSettings.Extensions.SaslMechanisms` — see the AWS IAM section in `src/MongoDB.Driver/Authentication/AGENTS.md`. Wiring entry point: consumers call `MongoClientSettings.Extensions.AddAWSAuthentication()` once to opt into this assembly. That call resolves to the `ExtensionManagerExtensions.AddAWSAuthentication` extension method on `IExtensionManager` (in `src/MongoDB.Driver.Authentication.AWS/ExtensionManagerExtensions.cs`), which registers both `MONGODB-AWS` with `SaslMechanisms` and the AWS KMS provider with `KmsProviders`. The two are registered together because they share the same `aws-sdk-net` dependency this assembly carries — having opted in to the AWS SDK at all, registering the KMS provider too is essentially free and avoids a second opt-in for CSFLE consumers; auth-only consumers can leave `KmsProviders` empty and the KMS-side registration is inert. Nothing in this project registers itself at assembly load; the call is explicit. diff --git a/src/MongoDB.Driver.Authentication.AWS/AWSSaslMechanism.cs b/src/MongoDB.Driver.Authentication.AWS/AWSSaslMechanism.cs index 7f814e6f6e5..751a9e8d076 100644 --- a/src/MongoDB.Driver.Authentication.AWS/AWSSaslMechanism.cs +++ b/src/MongoDB.Driver.Authentication.AWS/AWSSaslMechanism.cs @@ -141,7 +141,6 @@ public void OnReAuthenticationRequired() public bool TryHandleAuthenticationException(MongoException exception, ISaslStep step, SaslConversation conversation, ConnectionDescription description, out ISaslStep nextStep) { - _credentialsSource.ResetCache(); nextStep = null; return false; } diff --git a/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/AWSFallbackCredentialsSource.cs b/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/AWSFallbackCredentialsSource.cs index bc706d44ca1..2d20efa3e76 100644 --- a/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/AWSFallbackCredentialsSource.cs +++ b/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/AWSFallbackCredentialsSource.cs @@ -16,6 +16,7 @@ using System.Threading; using System.Threading.Tasks; using Amazon.Runtime; +using Amazon.Runtime.Credentials; namespace MongoDB.Driver.Authentication.AWS.CredentialsSources { @@ -23,60 +24,22 @@ internal sealed class AWSFallbackCredentialsSource : IAWSCredentialsSource { public static readonly AWSFallbackCredentialsSource Instance = new(); - private readonly SemaphoreSlim _lock = new(1); - - public void Dispose() => _lock?.Dispose(); - public AWSCredentials GetCredentials(CancellationToken cancellationToken) { - Amazon.Runtime.AWSCredentials credentialsSource; - _lock.Wait(cancellationToken); - try - { - // returns cached credentials source immediately. Only if cached source unavailable, makes quite heavy steps - credentialsSource = FallbackCredentialsFactory.GetCredentials(); - } - finally - { - _lock.Release(); - } - + cancellationToken.ThrowIfCancellationRequested(); + var credentialsSource = DefaultAWSCredentialsIdentityResolver.GetCredentials(null); var immutableCredentials = credentialsSource.GetCredentials(); return CreateAWSCredentials(immutableCredentials); } public async Task GetCredentialsAsync(CancellationToken cancellationToken) { - Amazon.Runtime.AWSCredentials credentialsSource; - await _lock.WaitAsync(cancellationToken).ConfigureAwait(false); - try - { - // returns cached credentials source immediately. Only if cached source unavailable, makes quite heavy steps - credentialsSource = FallbackCredentialsFactory.GetCredentials(); - } - finally - { - _lock.Release(); - } - + cancellationToken.ThrowIfCancellationRequested(); + var credentialsSource = await DefaultAWSCredentialsIdentityResolver.GetCredentialsAsync(null).ConfigureAwait(false); var immutableCredentials = await credentialsSource.GetCredentialsAsync().ConfigureAwait(false); return CreateAWSCredentials(immutableCredentials); } - public void ResetCache() - { - _lock.Wait(); - - try - { - FallbackCredentialsFactory.Reset(); - } - finally - { - _lock.Release(); - } - } - private AWSCredentials CreateAWSCredentials(ImmutableCredentials immutableCredentials) { var token = immutableCredentials.Token; diff --git a/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/AWSInstanceCredentialsSource.cs b/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/AWSInstanceCredentialsSource.cs index 7b9a06b73ae..0c3a621ec6d 100644 --- a/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/AWSInstanceCredentialsSource.cs +++ b/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/AWSInstanceCredentialsSource.cs @@ -27,18 +27,10 @@ public AWSInstanceCredentialsSource(AWSCredentials credentials) _credentials = credentials; } - public void Dispose() - { - } - public AWSCredentials GetCredentials(CancellationToken cancellationToken) => _credentials; public Task GetCredentialsAsync(CancellationToken cancellationToken) => Task.FromResult(_credentials); - - public void ResetCache() - { - } } } diff --git a/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/IAWSCredentialsSource.cs b/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/IAWSCredentialsSource.cs index 81473d5c794..74a45e6229b 100644 --- a/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/IAWSCredentialsSource.cs +++ b/src/MongoDB.Driver.Authentication.AWS/CredentialsSources/IAWSCredentialsSource.cs @@ -13,18 +13,15 @@ * limitations under the License. */ -using System; using System.Threading; using System.Threading.Tasks; namespace MongoDB.Driver.Authentication.AWS.CredentialsSources { - internal interface IAWSCredentialsSource : IDisposable + internal interface IAWSCredentialsSource { AWSCredentials GetCredentials(CancellationToken cancellationToken); Task GetCredentialsAsync(CancellationToken cancellationToken); - - void ResetCache(); } } diff --git a/src/MongoDB.Driver.Authentication.AWS/MongoDB.Driver.Authentication.AWS.csproj b/src/MongoDB.Driver.Authentication.AWS/MongoDB.Driver.Authentication.AWS.csproj index 8559576e535..a04b9d6102f 100644 --- a/src/MongoDB.Driver.Authentication.AWS/MongoDB.Driver.Authentication.AWS.csproj +++ b/src/MongoDB.Driver.Authentication.AWS/MongoDB.Driver.Authentication.AWS.csproj @@ -10,7 +10,7 @@ - + diff --git a/src/MongoDB.Driver/Authentication/AGENTS.md b/src/MongoDB.Driver/Authentication/AGENTS.md index 50a02783e77..5aa7e7e034a 100644 --- a/src/MongoDB.Driver/Authentication/AGENTS.md +++ b/src/MongoDB.Driver/Authentication/AGENTS.md @@ -79,7 +79,7 @@ Mid-operation token expiry: `TryHandleAuthenticationException` can refresh and r Mechanism: `MONGODB-AWS`. Lives in its own NuGet package to avoid forcing the AWS SDK on every consumer. Files: `AWSSaslMechanism.cs`, `CredentialsSources/IAWSCredentialsSource.cs` (the abstraction), `CredentialsSources/AWSInstanceCredentialsSource.cs`, `CredentialsSources/AWSFallbackCredentialsSource.cs`, `SaslSteps/AWSFirstSaslStep.cs`, `SaslSteps/AWSLastSaslStep.cs`, `AWSSignatureVersion4.cs`. The `MONGODB-AWS` mechanism is wired into the global `ISaslMechanismRegistry` via `ExtensionManagerExtensions.cs`. -Credential resolution: explicit credentials supplied via `MongoCredential.CreateCredential("$external", username, password)` with `mechanism=MONGODB-AWS` (or via the connection-string equivalent) are handled by `AWSInstanceCredentialsSource`. There is no `CreateAwsIam` factory on `MongoCredential`. Anything else is delegated to the AWS SDK's `FallbackCredentialsFactory` (via `AWSFallbackCredentialsSource`), which owns the chain — see the AWS SDK for the authoritative ordering of sources (env vars, AppConfig/profile, `AssumeRoleWithWebIdentity`, ECS task role, EC2 instance profile via IMDS, etc.). Don't go hunting for chain logic in this tree; the SDK owns it. +Credential resolution: explicit credentials supplied via `MongoCredential.CreateCredential("$external", username, password)` with `mechanism=MONGODB-AWS` (or via the connection-string equivalent) are handled by `AWSInstanceCredentialsSource`. There is no `CreateAwsIam` factory on `MongoCredential`. Anything else is delegated to the AWS SDK's `DefaultAWSCredentialsIdentityResolver` (via `AWSFallbackCredentialsSource`), which owns the chain — see the AWS SDK for the authoritative ordering of sources (env vars, AppConfig/profile, `AssumeRoleWithWebIdentity`, ECS task role, EC2 instance profile via IMDS, etc.). Don't go hunting for chain logic in this tree; the SDK owns it. Wire format: SigV4-signed `sts` service request (hard-coded in `AWSSignatureVersion4.cs` per the MONGODB-AWS spec) embedded in the SASL payload. Region is **derived from the STS host header** — `AWSSignatureVersion4.GetRegion(host)` hard-codes `us-east-1` for the exact host `sts.amazonaws.com`, splits any other host on `.` and takes the segment after the first dot (i.e. `split[1]`, e.g. `sts.us-west-2.amazonaws.com` → `us-west-2`), and falls back to `us-east-1` when no such segment is present. It is **not** parsed from the connection string or fetched from EC2 metadata. **No** speculative auth — `AWSSaslMechanism.CreateSpeculativeAuthenticationStep()` returns `null`; only SCRAM, X.509, and OIDC (with a non-expired cached token) speculate. diff --git a/tests/MongoDB.Driver.Tests/Communication/Security/AwsAuthenticationTests.cs b/tests/MongoDB.Driver.Tests/Communication/Security/AwsAuthenticationTests.cs index da55817facf..4e18df623f7 100644 --- a/tests/MongoDB.Driver.Tests/Communication/Security/AwsAuthenticationTests.cs +++ b/tests/MongoDB.Driver.Tests/Communication/Security/AwsAuthenticationTests.cs @@ -14,8 +14,6 @@ */ using System; -using Amazon.Runtime; -using Amazon.Runtime.CredentialManagement; using FluentAssertions; using MongoDB.Bson; using MongoDB.TestHelpers.XunitExtensions; @@ -64,105 +62,5 @@ public void Ecs_should_fill_AWS_CONTAINER_CREDENTIALS_RELATIVE_URI() var awsContainerUri = Environment.GetEnvironmentVariable("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") ?? Environment.GetEnvironmentVariable("AWS_CONTAINER_CREDENTIALS_FULL_URI"); (awsContainerUri != null).Should().Be(isEcs); } - - [Fact] - public void AwsSdk_should_support_all_required_handlers() - { - var credentialsGeneratorsDelegatesEnumerator = FallbackCredentialsFactory.CredentialsGenerators.GetEnumerator(); - - // AppConfigAWSCredentials - AWSCredentials credentials = null; - if (Type.GetType("Amazon.Runtime.AppConfigAWSCredentials, AWSSDK.Core", throwOnError: false) != null) // app.config/web.config does not present on windows - { - var appConfigAWSCredentialsException = Record.Exception(() => RunTestCase()); - // app.config/web.config case is based on ConfigurationManager. This is not configured for this test - appConfigAWSCredentialsException.Message.Should().Contain("The app.config/web.config files for the application did not contain credential information"); - } - - // AssumeRoleWithWebIdentityCredentials.FromEnvironmentVariables() - var exception = Record.Exception(() => RunTestCase()); - if (Environment.GetEnvironmentVariable("AWS_WEB_IDENTITY_TOKEN_FILE") != null) - { - // aws-web-identity-credentials is configured - exception.Should().BeNull(); - credentials.Should().BeOfType(); - } - else - { - // otherwise fail - exception.Message.Should().Contain("webIdentityTokenFile"); - } - - // GetAWSCredentials (Profile) - exception = Record.Exception(() => RunTestCase()); - if (IsWithAwsProfileOnMachine()) - { - // current machine contains configured aws profile, which may include: - // 1. BasicAWSCredentials (aws_access_key_id and aws_secret_access_key) - // 2. SessionAWSCredentials (aws_access_key_id, aws_secret_access_key, aws_session_token) - exception.Should().BeNull(); - credentials.Should().Match(x => x is BasicAWSCredentials || x is SessionAWSCredentials); - } - else - { - // otherwise fail - exception.Message.Should().Contain("Credential").And.Subject.Should().Contain("profile"); - } - - // EnvironmentVariablesAWSCredentials - exception = Record.Exception(() => RunTestCase()); - if (Environment.GetEnvironmentVariable("AWS_ACCESS_KEY_ID") != null && Environment.GetEnvironmentVariable("AWS_SECRET_ACCESS_KEY") != null) - { - // environment variables code path - exception.Should().BeNull(); - credentials.Should().BeOfType(); - } - else - { - // otherwise fail - exception.Message.Should().Contain("The environment variables").And.Subject.Contains("were not set with AWS credentials"); - } - - // ECSEC2CredentialsWrapper - exception = Record.Exception(() => RunTestCase()); - if (Environment.GetEnvironmentVariable("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") != null || Environment.GetEnvironmentVariable("AWS_CONTAINER_CREDENTIALS_FULL_URI") != null) - { - exception.Should().BeNull(); - credentials.Should().BeOfType(); - } - else - { - exception.Should().BeNull(); - credentials.GetType().Name.Should().Contain("DefaultInstanceProfileAWSCredentials"); // EC2 case - } - - credentialsGeneratorsDelegatesEnumerator.MoveNext().Should().BeFalse(); // no more handlers - - bool IsWithAwsProfileOnMachine() - { - var credentialProfileChain = new CredentialProfileStoreChain(); - if (credentialProfileChain.TryGetProfile(Environment.GetEnvironmentVariable("AWS_PROFILE") ?? "default", out var profile)) - { - try - { - _ = profile.GetAWSCredentials(credentialProfileChain); - return true; - } - catch - { - return false; - } - } - - return false; - } - - void RunTestCase() - { - credentials = null; - credentialsGeneratorsDelegatesEnumerator.MoveNext().Should().BeTrue(); - credentials = credentialsGeneratorsDelegatesEnumerator.Current(); - } - } } }