Skip to content

Commit

Permalink
Update logic to read the region when reading non default profiles
Browse files Browse the repository at this point in the history
  • Loading branch information
gcbeattyAWS committed Nov 6, 2024
1 parent 2963053 commit ad23dcb
Show file tree
Hide file tree
Showing 15 changed files with 1,087 additions and 22 deletions.
17 changes: 17 additions & 0 deletions src/AWS.Deploy.CLI/AWSCredentialsFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime;

namespace AWS.Deploy.CLI
{
/// <inheritdoc />
public class AWSCredentialsFactory : IAWSCredentialsFactory
{
/// <inheritdoc />
public AWSCredentials Create()
{
return FallbackCredentialsFactory.GetCredentials();
}
}
}
53 changes: 39 additions & 14 deletions src/AWS.Deploy.CLI/AWSUtilities.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Linq;
using System.Threading.Tasks;
using Amazon.Runtime;
using Amazon.Runtime.CredentialManagement;
using AWS.Deploy.CLI.Utilities;
using AWS.Deploy.Common;
using AWS.Deploy.Common.IO;
Expand All @@ -17,7 +16,7 @@ namespace AWS.Deploy.CLI
{
public interface IAWSUtilities
{
Task<AWSCredentials> ResolveAWSCredentials(string? profileName);
Task<Tuple<AWSCredentials, string?>> ResolveAWSCredentials(string? profileName);
string ResolveAWSRegion(string? region, string? lastRegionUsed = null);
}

Expand All @@ -28,35 +27,54 @@ public class AWSUtilities : IAWSUtilities
private readonly IDirectoryManager _directoryManager;
private readonly IOptionSettingHandler _optionSettingHandler;
private readonly IServiceProvider _serviceProvider;
private readonly ICredentialProfileStoreChainFactory _credentialChainFactory;
private readonly ISharedCredentialsFileFactory _sharedCredentialsFileFactory;
private readonly IAWSCredentialsFactory _awsCredentialsFactory;

public AWSUtilities(
IServiceProvider serviceProvider,
IToolInteractiveService toolInteractiveService,
IConsoleUtilities consoleUtilities,
IDirectoryManager directoryManager,
IOptionSettingHandler optionSettingHandler)
IOptionSettingHandler optionSettingHandler,
ICredentialProfileStoreChainFactory credentialChainFactory,
ISharedCredentialsFileFactory sharedCredentialsFileFactory,
IAWSCredentialsFactory awsCredentialsFactory)
{
_serviceProvider = serviceProvider;
_toolInteractiveService = toolInteractiveService;
_consoleUtilities = consoleUtilities;
_directoryManager = directoryManager;
_optionSettingHandler = optionSettingHandler;
_credentialChainFactory = credentialChainFactory;
_sharedCredentialsFileFactory = sharedCredentialsFileFactory;
_awsCredentialsFactory = awsCredentialsFactory;
}

public async Task<AWSCredentials> ResolveAWSCredentials(string? profileName)

/// <summary>
/// At a high level there are 2 possible return values for this function:
/// 1. <Credentials, regionName> In this case, both the credentials and region were able to be read from the profile.
/// 2. <Credentials, null>: In this case, the region was not able to be read from the profile, so we return null for it. The null case will be handled later on by <see cref="ResolveAWSRegion">.
/// </summary>
public async Task<Tuple<AWSCredentials, string?>> ResolveAWSCredentials(string? profileName)
{
async Task<AWSCredentials> Resolve()
async Task<Tuple<AWSCredentials, string?>> Resolve()
{
var chain = new CredentialProfileStoreChain();
var chain = _credentialChainFactory.Create();

// Use provided profile to read credentials
if (!string.IsNullOrEmpty(profileName))
{
if (chain.TryGetAWSCredentials(profileName, out var profileCredentials) &&
// Skip checking CanLoadCredentials for AssumeRoleAWSCredentials because it might require an MFA token and the callback hasn't been setup yet.
(profileCredentials is AssumeRoleAWSCredentials || await CanLoadCredentials(profileCredentials)))
{
_toolInteractiveService.WriteLine($"Configuring AWS Credentials from Profile {profileName}.");
return profileCredentials;
chain.TryGetProfile(profileName, out var profile);
// Return the credential since they must be found at this point.
// For region, we try and reaqd it the profile. If its not defined in the profile, return null and region selection will be handled later on by ResolveAWSRegion
return Tuple.Create<AWSCredentials, string?>(profileCredentials, profile.Region?.SystemName);
}
else
{
Expand All @@ -65,14 +83,17 @@ async Task<AWSCredentials> Resolve()
}
}

// Use default credentials
try
{
var fallbackCredentials = FallbackCredentialsFactory.GetCredentials();
var fallbackCredentials = _awsCredentialsFactory.Create();

if (await CanLoadCredentials(fallbackCredentials))
{
// Always return the credential since they must be found at this point.
// For region, we return null here, since it will read from default region in ResolveAWSRegion
_toolInteractiveService.WriteLine("Configuring AWS Credentials using AWS SDK credential search.");
return fallbackCredentials;
return Tuple.Create<AWSCredentials, string?>(fallbackCredentials, null);
}
}
catch (AmazonServiceException ex)
Expand All @@ -82,7 +103,8 @@ async Task<AWSCredentials> Resolve()
_toolInteractiveService.WriteDebugLine(ex.PrettyPrint());
}

var sharedCredentials = new SharedCredentialsFile();
// Use Shared Credentials
var sharedCredentials = _sharedCredentialsFileFactory.Create();
if (sharedCredentials.ListProfileNames().Count == 0)
{
throw new NoAWSCredentialsFoundException(DeployToolErrorCode.UnableToResolveAWSCredentials, "Unable to resolve AWS credentials to access AWS.");
Expand All @@ -93,21 +115,24 @@ async Task<AWSCredentials> Resolve()
if (chain.TryGetAWSCredentials(selectedProfileName, out var selectedProfileCredentials) &&
(await CanLoadCredentials(selectedProfileCredentials)))
{
return selectedProfileCredentials;
// Always return the credential since they must be found at this point
// For region, we try and reaqd it the profile. If its not defined in the profile, return null and region selection will be handled later on by <ResolveAWSRegion>
chain.TryGetProfile(selectedProfileName, out var profile);
return Tuple.Create<AWSCredentials, string?>(selectedProfileCredentials, profile.Region?.SystemName);
}

throw new NoAWSCredentialsFoundException(DeployToolErrorCode.UnableToCreateAWSCredentials, $"Unable to create AWS credentials for profile {selectedProfileName}.");
}

var credentials = await Resolve();
var credentialsAndRegion = await Resolve();

if (credentials is AssumeRoleAWSCredentials assumeRoleAWSCredentials)
if (credentialsAndRegion.Item1 is AssumeRoleAWSCredentials assumeRoleAWSCredentials)
{
var assumeOptions = assumeRoleAWSCredentials.Options;
assumeOptions.MfaTokenCodeCallback = ActivatorUtilities.CreateInstance<AssumeRoleMfaTokenCodeCallback>(_serviceProvider, assumeOptions).Execute;
}

return credentials;
return credentialsAndRegion;
}

private async Task<bool> CanLoadCredentials(AWSCredentials credentials)
Expand Down
12 changes: 6 additions & 6 deletions src/AWS.Deploy.CLI/Commands/CommandFactory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -209,8 +209,8 @@ private Command BuildDeployCommand()
deploymentSettings = await _deploymentSettingsHandler.ReadSettings(applyPath);
}

var awsCredentials = await _awsUtilities.ResolveAWSCredentials(input.Profile ?? deploymentSettings?.AWSProfile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? deploymentSettings?.AWSRegion);
var (awsCredentials, regionFromProfile) = await _awsUtilities.ResolveAWSCredentials(input.Profile ?? deploymentSettings?.AWSProfile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? deploymentSettings?.AWSRegion ?? regionFromProfile);

_commandLineWrapper.RegisterAWSContext(awsCredentials, awsRegion);
_awsClientFactory.RegisterAWSContext(awsCredentials, awsRegion);
Expand Down Expand Up @@ -318,8 +318,8 @@ private Command BuildDeleteCommand()
_toolInteractiveService.Diagnostics = input.Diagnostics;
_toolInteractiveService.DisableInteractive = input.Silent;

var awsCredentials = await _awsUtilities.ResolveAWSCredentials(input.Profile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region);
var (awsCredentials, regionFromProfile) = await _awsUtilities.ResolveAWSCredentials(input.Profile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? regionFromProfile);

_awsClientFactory.ConfigureAWSOptions(awsOption =>
{
Expand Down Expand Up @@ -401,8 +401,8 @@ private Command BuildListCommand()
{
_toolInteractiveService.Diagnostics = input.Diagnostics;

var awsCredentials = await _awsUtilities.ResolveAWSCredentials(input.Profile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region);
var (awsCredentials, regionFromProfile) = await _awsUtilities.ResolveAWSCredentials(input.Profile);
var awsRegion = _awsUtilities.ResolveAWSRegion(input.Region ?? regionFromProfile);

_awsClientFactory.ConfigureAWSOptions(awsOptions =>
{
Expand Down
17 changes: 17 additions & 0 deletions src/AWS.Deploy.CLI/CredentialProfileStoreChainFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;

namespace AWS.Deploy.CLI
{
/// <inheritdoc />
public class CredentialProfileStoreChainFactory : ICredentialProfileStoreChainFactory
{
/// <inheritdoc />
public CredentialProfileStoreChain Create()
{
return new CredentialProfileStoreChain();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ public static void AddCustomServices(this IServiceCollection serviceCollection,
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IEnvironmentVariableManager), typeof(EnvironmentVariableManager), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDeployToolWorkspaceMetadata), typeof(DeployToolWorkspaceMetadata), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IDeploymentSettingsHandler), typeof(DeploymentSettingsHandler), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ICredentialProfileStoreChainFactory), typeof(CredentialProfileStoreChainFactory), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(ISharedCredentialsFileFactory), typeof(SharedCredentialsFileFactory), lifetime));
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IAWSCredentialsFactory), typeof(AWSCredentialsFactory), lifetime));

var packageJsonTemplate = typeof(PackageJsonGenerator).Assembly.ReadEmbeddedFile(PackageJsonGenerator.TemplateIdentifier);
serviceCollection.TryAdd(new ServiceDescriptor(typeof(IPackageJsonGenerator), (serviceProvider) => new PackageJsonGenerator(packageJsonTemplate), lifetime));
Expand Down
18 changes: 18 additions & 0 deletions src/AWS.Deploy.CLI/IAWSCredentialsFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime;

namespace AWS.Deploy.CLI
{
/// <summary>
/// Represents a factory for creating <see cref="AWSCredentials">
/// </summary>
public interface IAWSCredentialsFactory
{
/// <summary>
/// Creates <see cref="AWSCredentials">
/// </summary>
AWSCredentials Create();
}
}
18 changes: 18 additions & 0 deletions src/AWS.Deploy.CLI/ICredentialProfileStoreChainFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;

namespace AWS.Deploy.CLI
{
/// <summary>
/// Represents a factory for creating <see cref="CredentialProfileStoreChain">
/// </summary>
public interface ICredentialProfileStoreChainFactory
{
/// <summary>
/// Creates a <see cref="CredentialProfileStoreChain">
/// </summary>
CredentialProfileStoreChain Create();
}
}
18 changes: 18 additions & 0 deletions src/AWS.Deploy.CLI/ISharedCredentialsFileFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;

namespace AWS.Deploy.CLI
{
/// <summary>
/// Represents a factory for creating <see cref="SharedCredentialsFile">
/// </summary>
public interface ISharedCredentialsFileFactory
{
/// <summary>
/// Creates <see cref="SharedCredentialsFile">
/// </summary>
SharedCredentialsFile Create();
}
}
17 changes: 17 additions & 0 deletions src/AWS.Deploy.CLI/SharedCredentialsFileFactory.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime.CredentialManagement;

namespace AWS.Deploy.CLI
{
/// <inheritdoc />
public class SharedCredentialsFileFactory : ISharedCredentialsFileFactory
{
/// <inheritdoc />
public SharedCredentialsFile Create()
{
return new SharedCredentialsFile();
}
}
}
8 changes: 6 additions & 2 deletions src/AWS.Deploy.Orchestration/CDK/CDKBootstrapTemplate.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,11 @@ Resources:
- Id: CleanupOldVersions
Status: Enabled
NoncurrentVersionExpiration:
NoncurrentDays: 365
NoncurrentDays: 30
- Id: AbortIncompleteMultipartUploads
Status: Enabled
AbortIncompleteMultipartUpload:
DaysAfterInitiation: 1
UpdateReplacePolicy: Delete
DeletionPolicy: Delete
StagingBucketPolicy:
Expand Down Expand Up @@ -611,7 +615,7 @@ Resources:
Type: String
Name:
Fn::Sub: /cdk-bootstrap/${Qualifier}/version
Value: "23"
Value: "25"
Outputs:
BucketName:
Description: The name of the S3 bucket owned by the CDK toolkit stack
Expand Down
25 changes: 25 additions & 0 deletions test/AWS.Deploy.CLI.UnitTests/AWSCredentialsFactoryTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

using Amazon.Runtime;
using Xunit;

namespace AWS.Deploy.CLI.UnitTests
{
public class AWSCredentialsFactoryTests
{
[Fact]
public void Create_ReturnsAWSCredentialsInstance()
{
// Arrange
var factory = new AWSCredentialsFactory();

// Act
var result = factory.Create();

// Assert
Assert.NotNull(result);
Assert.IsAssignableFrom<AWSCredentials>(result);
}
}
}
Loading

0 comments on commit ad23dcb

Please sign in to comment.