diff --git a/src/D2L.Bmx/AwsCredsCreator.cs b/src/D2L.Bmx/AwsCredsCreator.cs index d88911fd..56019f1e 100644 --- a/src/D2L.Bmx/AwsCredsCreator.cs +++ b/src/D2L.Bmx/AwsCredsCreator.cs @@ -3,13 +3,20 @@ namespace D2L.Bmx; +internal record AwsCredentialsInfo( + string Account, + string Role, + int Duration, + AwsCredentials Credentials +); + internal class AwsCredsCreator( IAwsClient awsClient, IConsolePrompter consolePrompter, IAwsCredentialCache awsCredentialCache, BmxConfig config ) { - public async Task CreateAwsCredsAsync( + public async Task CreateAwsCredsAsync( AuthenticatedOktaApi oktaApi, string? account, string? role, @@ -50,7 +57,11 @@ bool cache ); if( cachedCredentials is not null ) { - return cachedCredentials; + return new( + Account: account, + Role: role, + Duration: duration.Value, + Credentials: cachedCredentials ); } } @@ -92,7 +103,11 @@ bool cache ); if( cachedCredentials is not null ) { - return cachedCredentials; + return new( + Account: account, + Role: role, + Duration: duration.Value, + Credentials: cachedCredentials ); } } @@ -117,6 +132,10 @@ bool cache ); } - return credentials; + return new( + Account: account, + Role: role, + Duration: duration.Value, + Credentials: credentials ); } } diff --git a/src/D2L.Bmx/ParameterDescriptions.cs b/src/D2L.Bmx/ParameterDescriptions.cs index e579538d..726df5e5 100644 --- a/src/D2L.Bmx/ParameterDescriptions.cs +++ b/src/D2L.Bmx/ParameterDescriptions.cs @@ -8,8 +8,14 @@ internal static class ParameterDescriptions { public const string Role = "AWS role name"; public const string Duration = "AWS session duration in minutes"; public const string Profile = "AWS profile name"; - public const string Output = "Custom path to the AWS credentials file"; + public const string Output = + "Custom path to the AWS credentials file, or the AWS config file if '--use-credential-process' is supplied"; public const string Format = "Output format of AWS credentials"; public const string NonInteractive = "Run non-interactively without showing any prompts"; - public const string CacheAwsCredentials = "Enables Cache for AWS tokens"; + public const string CacheAwsCredentials = + "Enables Cache for AWS tokens. Implied if '--use-credential-process' is supplied"; + public const string UseCredentialProcess = """ + Write BMX command to AWS profile, so that AWS tools & SDKs using the profile will source credentials from BMX. + See https://docs.aws.amazon.com/cli/latest/userguide/cli-configure-sourcing-external.html. + """; } diff --git a/src/D2L.Bmx/PrintHandler.cs b/src/D2L.Bmx/PrintHandler.cs index 5b68f2a5..4add57ff 100644 --- a/src/D2L.Bmx/PrintHandler.cs +++ b/src/D2L.Bmx/PrintHandler.cs @@ -22,14 +22,14 @@ bool cacheAwsCredentials nonInteractive: nonInteractive, ignoreCache: false ); - var awsCreds = await awsCredsCreator.CreateAwsCredsAsync( + var awsCreds = ( await awsCredsCreator.CreateAwsCredsAsync( oktaApi: oktaApi, account: account, role: role, duration: duration, nonInteractive: nonInteractive, cache: cacheAwsCredentials - ); + ) ).Credentials; if( string.Equals( format, PrintFormat.Bash, StringComparison.OrdinalIgnoreCase ) ) { PrintBash( awsCreds ); diff --git a/src/D2L.Bmx/Program.cs b/src/D2L.Bmx/Program.cs index f4cbf690..d17bf61b 100644 --- a/src/D2L.Bmx/Program.cs +++ b/src/D2L.Bmx/Program.cs @@ -153,6 +153,9 @@ var profileOption = new Option( name: "--profile", description: ParameterDescriptions.Profile ); +var useCredentialProcessOption = new Option( + name: "--use-credential-process", + description: ParameterDescriptions.UseCredentialProcess ); var writeCommand = new Command( "write", "Write AWS credentials to the credentials file" ) { accountOption, @@ -164,6 +167,7 @@ userOption, nonInteractiveOption, cacheAwsCredentialsOption, + useCredentialProcessOption, }; writeCommand.SetHandler( ( InvocationContext context ) => { @@ -193,7 +197,8 @@ nonInteractive: context.ParseResult.GetValueForOption( nonInteractiveOption ), output: context.ParseResult.GetValueForOption( outputOption ), profile: context.ParseResult.GetValueForOption( profileOption ), - cacheAwsCredentials: context.ParseResult.GetValueForOption( cacheAwsCredentialsOption ) + cacheAwsCredentials: context.ParseResult.GetValueForOption( cacheAwsCredentialsOption ), + useCredentialProcess: context.ParseResult.GetValueForOption( useCredentialProcessOption ) ); } ); diff --git a/src/D2L.Bmx/WriteHandler.cs b/src/D2L.Bmx/WriteHandler.cs index caebc79e..d424b03a 100644 --- a/src/D2L.Bmx/WriteHandler.cs +++ b/src/D2L.Bmx/WriteHandler.cs @@ -20,15 +20,18 @@ public async Task HandleAsync( bool nonInteractive, string? output, string? profile, - bool cacheAwsCredentials + bool cacheAwsCredentials, + bool useCredentialProcess ) { + cacheAwsCredentials = cacheAwsCredentials || useCredentialProcess; + var oktaApi = await oktaAuth.AuthenticateAsync( org: org, user: user, nonInteractive: nonInteractive, ignoreCache: false ); - var awsCreds = await awsCredsCreator.CreateAwsCredsAsync( + var awsCredsInfo = await awsCredsCreator.CreateAwsCredsAsync( oktaApi: oktaApi, account: account, role: role, @@ -48,24 +51,41 @@ bool cacheAwsCredentials if( !string.IsNullOrEmpty( output ) && !Path.IsPathRooted( output ) ) { output = "./" + output; } - string credentialsFilePath = new SharedCredentialsFile( output ).FilePath; - string credentialsFolderPath = Path.GetDirectoryName( credentialsFilePath ) - ?? throw new BmxException( "Invalid AWS credentials file path" ); - if( !Directory.Exists( credentialsFolderPath ) ) { - Directory.CreateDirectory( credentialsFolderPath ); + if( string.IsNullOrEmpty( output ) ) { + output = useCredentialProcess + ? SharedCredentialsFile.DefaultConfigFilePath + : SharedCredentialsFile.DefaultFilePath; } - if( !File.Exists( credentialsFilePath ) ) { - using( File.Create( credentialsFilePath ) ) { }; + + string outputFolder = Path.GetDirectoryName( output ) + ?? throw new BmxException( "Invalid output path" ); + Directory.CreateDirectory( outputFolder ); + if( !File.Exists( output ) ) { + using( File.Create( output ) ) { }; } - var data = parser.ReadFile( credentialsFilePath ); - if( !data.Sections.ContainsSection( profile ) ) { - data.Sections.AddSection( profile ); + var data = parser.ReadFile( output ); + if( useCredentialProcess ) { + string sectionName = $"profile {profile}"; + if( !data.Sections.ContainsSection( sectionName ) ) { + data.Sections.AddSection( sectionName ); + } + data[sectionName]["credential_process"] = + "bmx print --format json --cache --non-interactive" + + $" --org \"{oktaApi.Org}\"" + + $" --user \"{oktaApi.User}\"" + + $" --account \"{awsCredsInfo.Account}\"" + + $" --role \"{awsCredsInfo.Role}\"" + + $" --duration {awsCredsInfo.Duration}"; + } else { + if( !data.Sections.ContainsSection( profile ) ) { + data.Sections.AddSection( profile ); + } + data[profile]["aws_access_key_id"] = awsCredsInfo.Credentials.AccessKeyId; + data[profile]["aws_secret_access_key"] = awsCredsInfo.Credentials.SecretAccessKey; + data[profile]["aws_session_token"] = awsCredsInfo.Credentials.SessionToken; } - data[profile]["aws_access_key_id"] = awsCreds.AccessKeyId; - data[profile]["aws_secret_access_key"] = awsCreds.SecretAccessKey; - data[profile]["aws_session_token"] = awsCreds.SessionToken; - parser.WriteFile( credentialsFilePath, data, new UTF8Encoding( false ) ); + parser.WriteFile( output, data, new UTF8Encoding( encoderShouldEmitUTF8Identifier: false ) ); } }