From 6d933fda243591823ef180a28759eeb868de3b96 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Tue, 9 May 2023 15:11:36 -0500 Subject: [PATCH 01/13] Respect when users explicitly set the profile --- src/AWSCredentials.jl | 10 ++++-- test/AWSCredentials.jl | 69 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 3 deletions(-) diff --git a/src/AWSCredentials.jl b/src/AWSCredentials.jl index d6ca0588dd..2587e65584 100644 --- a/src/AWSCredentials.jl +++ b/src/AWSCredentials.jl @@ -110,12 +110,13 @@ Checks credential locations in the order: function AWSCredentials(; profile=nothing, throw_cred_error=true) creds = nothing credential_function = () -> nothing + explicit_profile = !isnothing(profile) profile = @something profile _aws_get_profile() # Define our search options, expected to be callable with no arguments. # Throw NoCredentials if none are found functions = [ - env_var_credentials, + () -> env_var_credentials(explicit_profile), () -> dot_aws_credentials(profile), () -> dot_aws_config(profile), credentials_from_webtoken, @@ -355,12 +356,15 @@ function ecs_instance_credentials() end """ - env_var_credentials() -> Union{AWSCredential, Nothing} + env_var_credentials(explicit_profile::Bool=false) -> Union{AWSCredential, Nothing} Use AWS environmental variables (e.g. AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, etc.) to create AWSCredentials. """ -function env_var_credentials() +function env_var_credentials(explicit_profile::Bool=false) + # Skip using environmental variables when a profile has been explicitly set + explicit_profile && return nothing + if haskey(ENV, "AWS_ACCESS_KEY_ID") && haskey(ENV, "AWS_SECRET_ACCESS_KEY") return AWSCredentials( ENV["AWS_ACCESS_KEY_ID"], diff --git a/test/AWSCredentials.jl b/test/AWSCredentials.jl index 60c513b5d6..f283cfd395 100644 --- a/test/AWSCredentials.jl +++ b/test/AWSCredentials.jl @@ -399,6 +399,75 @@ end end end end + + @testset "Precedence" begin + mktempdir() do dir + config_file = joinpath(dir, "config") + creds_file = joinpath(dir, "creds") + + write( + creds_file, + """ + [profile1] + aws_access_key_id = AKI1 + aws_secret_access_key = SAK1 + + [profile2] + aws_access_key_id = AKI2 + aws_secret_access_key = SAK2 + """ + ) + + withenv( + [k => nothing for k in filter(startswith("AWS_"), keys(ENV))]..., + "AWS_SHARED_CREDENTIALS_FILE" => creds_file, + "AWS_CONFIG_FILE" => config_file, + ) do + + @testset "explicit profile preferred" begin + withenv( + "AWS_PROFILE" => "profile1", + ) do + creds = AWSCredentials(profile="profile2") + @test creds.access_key_id == "AKI2" + end + + withenv( + "AWS_ACCESS_KEY_ID" => "AKI0", + "AWS_SECRET_ACCESS_KEY" => "SAK0", + ) do + creds = AWSCredentials(profile="profile2") + @test creds.access_key_id == "AKI2" + end + end + + @testset "AWS_ACCESS_KEY_ID preferred over AWS_PROFILE" begin + withenv( + "AWS_PROFILE" => "profile1", + "AWS_ACCESS_KEY_ID" => "AKI0", + "AWS_SECRET_ACCESS_KEY" => "SAK0", + ) do + creds = AWSCredentials() + @test creds.access_key_id == "AKI0" + end + end + + # The AWS CLI used to use `AWS_DEFAULT_PROFILE` to set the AWS profile via the + # command line but this was deprecated in favor of `AWS_PROFILE`. We'll probably + # keeps support for this as long as AWS CLI continues to support it. + # https://github.com/aws/aws-cli/issues/2597 + @testset "AWS_PROFILE preferred over AWS_DEFAULT_PROFILE" begin + withenv( + "AWS_DEFAULT_PROFILE" => "profile1", + "AWS_PROFILE" => "profile2", + ) do + creds = AWSCredentials() + @test creds.access_key_id == "AKI2" + end + end + end + end + end end @testset "Retrieving AWS Credentials" begin From 497dfbe2ea69611e8295e4288b93f8a9be89d42d Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Tue, 9 May 2023 15:32:32 -0500 Subject: [PATCH 02/13] Prefer SSO to credential files --- src/AWSCredentials.jl | 25 +++++++++++++++++++++++++ src/utilities/credentials.jl | 2 +- test/AWSCredentials.jl | 31 +++++++++++++++++++++++++++---- 3 files changed, 53 insertions(+), 5 deletions(-) diff --git a/src/AWSCredentials.jl b/src/AWSCredentials.jl index 2587e65584..a3e05c4de0 100644 --- a/src/AWSCredentials.jl +++ b/src/AWSCredentials.jl @@ -117,6 +117,7 @@ function AWSCredentials(; profile=nothing, throw_cred_error=true) # Throw NoCredentials if none are found functions = [ () -> env_var_credentials(explicit_profile), + () -> sso_credentials(profile), () -> dot_aws_credentials(profile), () -> dot_aws_config(profile), credentials_from_webtoken, @@ -408,6 +409,30 @@ function dot_aws_credentials_file() end end +function sso_credentials(profile=nothing) + config_file = @mock dot_aws_config_file() + + if isfile(config_file) + ini = read(Inifile(), config_file) + p = @something profile _aws_get_profile() + + # get all the fields for that profile + settings = _aws_profile_config(ini, p) + isempty(settings) && return nothing + + sso_start_url = get(settings, "sso_start_url", nothing) + + if !isnothing(sso_start_url) + access_key, secret_key, token, expiry = _aws_get_sso_credential_details(p, ini) + return AWSCredentials(access_key, secret_key, token; expiry=expiry) + else + return _aws_get_role(p, ini) + end + end + + return nothing +end + """ dot_aws_config(profile=nothing) -> Union{AWSCredential, Nothing} diff --git a/src/utilities/credentials.jl b/src/utilities/credentials.jl index 599489cf49..6e2bc52407 100644 --- a/src/utilities/credentials.jl +++ b/src/utilities/credentials.jl @@ -71,7 +71,7 @@ function _aws_get_role(role::AbstractString, ini::Inifile) duration_seconds = get(settings, "duration_seconds", nothing) credentials = nothing - for f in (dot_aws_credentials, dot_aws_config) + for f in (sso_credentials, dot_aws_credentials, dot_aws_config) credentials = f(source_profile) credentials === nothing || break end diff --git a/test/AWSCredentials.jl b/test/AWSCredentials.jl index f283cfd395..c94572a652 100644 --- a/test/AWSCredentials.jl +++ b/test/AWSCredentials.jl @@ -405,9 +405,7 @@ end config_file = joinpath(dir, "config") creds_file = joinpath(dir, "creds") - write( - creds_file, - """ + basic_creds_content = """ [profile1] aws_access_key_id = AKI1 aws_secret_access_key = SAK1 @@ -416,7 +414,6 @@ end aws_access_key_id = AKI2 aws_secret_access_key = SAK2 """ - ) withenv( [k => nothing for k in filter(startswith("AWS_"), keys(ENV))]..., @@ -425,6 +422,9 @@ end ) do @testset "explicit profile preferred" begin + isfile(config_file) && rm(config_file) + write(creds_file, basic_creds_content) + withenv( "AWS_PROFILE" => "profile1", ) do @@ -442,6 +442,9 @@ end end @testset "AWS_ACCESS_KEY_ID preferred over AWS_PROFILE" begin + isfile(config_file) && rm(config_file) + write(creds_file, basic_creds_content) + withenv( "AWS_PROFILE" => "profile1", "AWS_ACCESS_KEY_ID" => "AKI0", @@ -457,6 +460,9 @@ end # keeps support for this as long as AWS CLI continues to support it. # https://github.com/aws/aws-cli/issues/2597 @testset "AWS_PROFILE preferred over AWS_DEFAULT_PROFILE" begin + isfile(config_file) && rm(config_file) + write(creds_file, basic_creds_content) + withenv( "AWS_DEFAULT_PROFILE" => "profile1", "AWS_PROFILE" => "profile2", @@ -465,6 +471,23 @@ end @test creds.access_key_id == "AKI2" end end + + @testset "SSO preferred over credentials file" begin + write( + config_file, + """ + [profile profile1] + sso_start_url = https://my-sso-portal.awsapps.com/start + sso_role_name = role1 + """ + ) + write(creds_file, basic_creds_content) + + apply(Patches.sso_service_patches("AKI0", "SAK0")) do + creds = AWSCredentials(profile="profile1") + @test creds.access_key_id == "AKI0" + end + end end end end From 77d954862a5a7b555411e7bc63d3887f49e4e300 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Tue, 9 May 2023 16:07:52 -0500 Subject: [PATCH 03/13] Test credential files are preferred over credential_process --- test/AWSCredentials.jl | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/AWSCredentials.jl b/test/AWSCredentials.jl index c94572a652..06b726e77d 100644 --- a/test/AWSCredentials.jl +++ b/test/AWSCredentials.jl @@ -488,6 +488,25 @@ end @test creds.access_key_id == "AKI0" end end + + @testset "Credential file over credential_process" begin + json = Dict( + "Version" => 1, + "AccessKeyId" => "AKI0", + "SecretAccessKey" => "SAK0", + ) + write( + config_file, + """ + [profile profile1] + credential_process = echo '$(JSON.json(json))' + """ + ) + write(creds_file, basic_creds_content) + + creds = AWSCredentials(profile="profile1") + @test creds.access_key_id == "AKI1" + end end end end From a4669539203ede3d3ff0023940e7ddcaf45958f0 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Tue, 9 May 2023 16:12:18 -0500 Subject: [PATCH 04/13] Revise test ensuring credential_process is used over config creds --- test/AWSCredentials.jl | 72 ++++++++++++++++++++---------------------- 1 file changed, 34 insertions(+), 38 deletions(-) diff --git a/test/AWSCredentials.jl b/test/AWSCredentials.jl index 06b726e77d..9f269d7475 100644 --- a/test/AWSCredentials.jl +++ b/test/AWSCredentials.jl @@ -507,6 +507,27 @@ end creds = AWSCredentials(profile="profile1") @test creds.access_key_id == "AKI1" end + + @testset "credential_process over config credentials" begin + json = Dict( + "Version" => 1, + "AccessKeyId" => "AKI0", + "SecretAccessKey" => "SAK0", + ) + write( + config_file, + """ + [profile profile1] + aws_access_key_id = AKI1 + aws_secret_access_key = SAK1 + credential_process = echo '$(JSON.json(json))' + """ + ) + isfile(creds_file) && rm(creds_file) + + creds = AWSCredentials(profile="profile1") + @test creds.access_key_id == "AKI0" + end end end end @@ -640,47 +661,22 @@ end chmod(credential_process_file, 0o700) withenv("AWS_CONFIG_FILE" => config_file) do - @testset "support" begin - open(config_file, "w") do io - write( - io, - """ - [profile $(test_values["Test-Config-Profile"])] - credential_process = $(abspath(credential_process_file)) - """, - ) - end - - result = dot_aws_config(test_values["Test-Config-Profile"]) - - @test result.access_key_id == test_values["Test-AccessKeyId"] - @test result.secret_key == test_values["Test-SecretAccessKey"] - @test isempty(result.token) - @test result.expiry == typemax(DateTime) + open(config_file, "w") do io + write( + io, + """ + [profile $(test_values["Test-Config-Profile"])] + credential_process = $(abspath(credential_process_file)) + """, + ) end - # The AWS CLI uses the config file `credential_process` setting over - # specifying the config file `aws_access_key_id`/`aws_secret_access_key`. - @testset "precedence" begin - open(config_file, "w") do io - write( - io, - """ - [profile $(test_values["Test-Config-Profile"])] - aws_access_key_id = invalid - aws_secret_access_key = invalid - credential_process = $(abspath(credential_process_file)) - """, - ) - end - - result = dot_aws_config(test_values["Test-Config-Profile"]) + result = dot_aws_config(test_values["Test-Config-Profile"]) - @test result.access_key_id == test_values["Test-AccessKeyId"] - @test result.secret_key == test_values["Test-SecretAccessKey"] - @test isempty(result.token) - @test result.expiry == typemax(DateTime) - end + @test result.access_key_id == test_values["Test-AccessKeyId"] + @test result.secret_key == test_values["Test-SecretAccessKey"] + @test isempty(result.token) + @test result.expiry == typemax(DateTime) end end end From 58e44c3e54061cbfa8ca62208fc37c70ed6eb575 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Tue, 9 May 2023 16:19:55 -0500 Subject: [PATCH 05/13] Revise docstrings --- src/AWSCredentials.jl | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/AWSCredentials.jl b/src/AWSCredentials.jl index a3e05c4de0..478e7615c3 100644 --- a/src/AWSCredentials.jl +++ b/src/AWSCredentials.jl @@ -409,6 +409,15 @@ function dot_aws_credentials_file() end end +""" + sso_credentials(profile=nothing) -> Union{AWSCredential, Nothing} + +Retrieve credentials via AWS single sign-on settings defined in the `profile` within the AWS +config file. If no SSO settings are found for the `profile` `nothing` is returned. + +# Arguments +- `profile`: Specific profile used to get `AWSCredential`s, default is `nothing` +""" function sso_credentials(profile=nothing) config_file = @mock dot_aws_config_file() @@ -437,8 +446,8 @@ end dot_aws_config(profile=nothing) -> Union{AWSCredential, Nothing} Retrieve AWSCredentials for the default or specified profile from the `~/.aws/config` file. -Single sign-on profiles are also valid. If this fails, try to retrieve credentials from -`_aws_get_role()`, otherwise return `nothing` +If this fails, try to retrieve credentials from `_aws_get_role()`, otherwise return +`nothing`. # Arguments - `profile`: Specific profile used to get AWSCredentials, default is `nothing` From 9a40d2bda36108753a98e441eea808379849dc8f Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Wed, 10 May 2023 10:00:34 -0500 Subject: [PATCH 06/13] Comment on credential precedence testset --- test/AWSCredentials.jl | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test/AWSCredentials.jl b/test/AWSCredentials.jl index 9f269d7475..8de2660d17 100644 --- a/test/AWSCredentials.jl +++ b/test/AWSCredentials.jl @@ -400,7 +400,15 @@ end end end - @testset "Precedence" begin + # Verify that the search order for credentials mirrors the behavior of the AWS CLI + # (version 2.11.13). Whenever support is added for new credential types new tests should + # be added to this test set. To determine the credential preference order used by AWS + # CLI it is recommended you use a set of valid credentials and a set of invalid + # credentials to determine the precedence. + # + # Documentation on credential preference for the AWS SDK for .NET: + # https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/creds-assign.html + @testset "Credential Precedence" begin mktempdir() do dir config_file = joinpath(dir, "config") creds_file = joinpath(dir, "creds") From eab2126b6b4c6237f1586edccec623b6efc809f6 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Wed, 10 May 2023 10:06:52 -0500 Subject: [PATCH 07/13] Remove SSO support from dot_aws_config --- src/AWSCredentials.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/AWSCredentials.jl b/src/AWSCredentials.jl index 478e7615c3..9f416968dc 100644 --- a/src/AWSCredentials.jl +++ b/src/AWSCredentials.jl @@ -465,7 +465,6 @@ function dot_aws_config(profile=nothing) credential_process = get(settings, "credential_process", nothing) access_key = get(settings, "aws_access_key_id", nothing) - sso_start_url = get(settings, "sso_start_url", nothing) if !isnothing(credential_process) cmd = Cmd(Base.shell_split(credential_process)) @@ -473,9 +472,6 @@ function dot_aws_config(profile=nothing) elseif !isnothing(access_key) access_key, secret_key, token = _aws_get_credential_details(p, ini) return AWSCredentials(access_key, secret_key, token) - elseif !isnothing(sso_start_url) - access_key, secret_key, token, expiry = _aws_get_sso_credential_details(p, ini) - return AWSCredentials(access_key, secret_key, token; expiry=expiry) else return _aws_get_role(p, ini) end From 5e873876b2c751086abe369568f4d5c83978475e Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Tue, 9 May 2023 16:20:45 -0500 Subject: [PATCH 08/13] Set project version to 1.86.0 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 9d152ec62f..28485f2c57 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "AWS" uuid = "fbe9abb3-538b-5e4e-ba9e-bc94f4f92ebc" license = "MIT" -version = "1.85.0" +version = "1.86.0" [deps] Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f" From e13fca81e34f0815099ede9174d8f1dc0922801f Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Wed, 10 May 2023 10:40:11 -0500 Subject: [PATCH 09/13] Formatting --- test/AWSCredentials.jl | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/test/AWSCredentials.jl b/test/AWSCredentials.jl index 8de2660d17..9d5a22eef3 100644 --- a/test/AWSCredentials.jl +++ b/test/AWSCredentials.jl @@ -428,23 +428,21 @@ end "AWS_SHARED_CREDENTIALS_FILE" => creds_file, "AWS_CONFIG_FILE" => config_file, ) do - @testset "explicit profile preferred" begin isfile(config_file) && rm(config_file) write(creds_file, basic_creds_content) - withenv( - "AWS_PROFILE" => "profile1", - ) do - creds = AWSCredentials(profile="profile2") + withenv("AWS_PROFILE" => "profile1") do + creds = AWSCredentials(; profile="profile2") @test creds.access_key_id == "AKI2" end withenv( "AWS_ACCESS_KEY_ID" => "AKI0", "AWS_SECRET_ACCESS_KEY" => "SAK0", + # format trick: using this comment to force use of multiple lines ) do - creds = AWSCredentials(profile="profile2") + creds = AWSCredentials(; profile="profile2") @test creds.access_key_id == "AKI2" end end @@ -474,6 +472,7 @@ end withenv( "AWS_DEFAULT_PROFILE" => "profile1", "AWS_PROFILE" => "profile2", + # format trick: using this comment to force use of multiple lines ) do creds = AWSCredentials() @test creds.access_key_id == "AKI2" @@ -487,12 +486,12 @@ end [profile profile1] sso_start_url = https://my-sso-portal.awsapps.com/start sso_role_name = role1 - """ + """, ) write(creds_file, basic_creds_content) apply(Patches.sso_service_patches("AKI0", "SAK0")) do - creds = AWSCredentials(profile="profile1") + creds = AWSCredentials(; profile="profile1") @test creds.access_key_id == "AKI0" end end @@ -502,17 +501,18 @@ end "Version" => 1, "AccessKeyId" => "AKI0", "SecretAccessKey" => "SAK0", + # format trick: using this comment to force use of multiple lines ) write( config_file, """ [profile profile1] credential_process = echo '$(JSON.json(json))' - """ + """, ) write(creds_file, basic_creds_content) - creds = AWSCredentials(profile="profile1") + creds = AWSCredentials(; profile="profile1") @test creds.access_key_id == "AKI1" end @@ -521,6 +521,7 @@ end "Version" => 1, "AccessKeyId" => "AKI0", "SecretAccessKey" => "SAK0", + # format trick: using this comment to force use of multiple lines ) write( config_file, @@ -529,11 +530,11 @@ end aws_access_key_id = AKI1 aws_secret_access_key = SAK1 credential_process = echo '$(JSON.json(json))' - """ + """, ) isfile(creds_file) && rm(creds_file) - creds = AWSCredentials(profile="profile1") + creds = AWSCredentials(; profile="profile1") @test creds.access_key_id == "AKI0" end end From 962f34b5f2eb52bc10878e9ec7057e9a3ca97938 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Wed, 10 May 2023 13:25:33 -0500 Subject: [PATCH 10/13] Add EC2 instance credential precedence --- src/AWSCredentials.jl | 4 +- test/AWSCredentials.jl | 86 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/src/AWSCredentials.jl b/src/AWSCredentials.jl index 9f416968dc..90743440ee 100644 --- a/src/AWSCredentials.jl +++ b/src/AWSCredentials.jl @@ -113,8 +113,8 @@ function AWSCredentials(; profile=nothing, throw_cred_error=true) explicit_profile = !isnothing(profile) profile = @something profile _aws_get_profile() - # Define our search options, expected to be callable with no arguments. - # Throw NoCredentials if none are found + # Define the credential preference order + # https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-authentication.html#cli-chap-authentication-precedence functions = [ () -> env_var_credentials(explicit_profile), () -> sso_credentials(profile), diff --git a/test/AWSCredentials.jl b/test/AWSCredentials.jl index 9d5a22eef3..a592f25635 100644 --- a/test/AWSCredentials.jl +++ b/test/AWSCredentials.jl @@ -406,8 +406,10 @@ end # CLI it is recommended you use a set of valid credentials and a set of invalid # credentials to determine the precedence. # - # Documentation on credential preference for the AWS SDK for .NET: - # https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/creds-assign.html + # Documentation on credential precedence: + # - https://docs.aws.amazon.com/cli/latest/userguide/cli-chap-authentication.html#cli-chap-authentication-precedence + # - https://docs.aws.amazon.com/sdk-for-net/v3/developer-guide/creds-assign.html + # - https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/credentials.html @testset "Credential Precedence" begin mktempdir() do dir config_file = joinpath(dir, "config") @@ -423,6 +425,55 @@ end aws_secret_access_key = SAK2 """ + ec2_expiration = floor(now(UTC), Second) + ec2_json = Dict( + "AccessKeyId" => "AKI_EC2", + "SecretAccessKey" => "SAK_EC2", + "Token" => "TOK_EC2", + "Expiration" => Dates.format(ec2_expiration, dateformat"yyyy-mm-dd\THH:MM:SS\Z"), + ) + + function ec2_metadata(url::AbstractString) + name = "local-credentials" + metadata_uri = "http://169.254.169.254/latest/meta-data" + if url == "$metadata_uri/iam/info" + return HTTP.Response(200, JSON.json("InstanceProfileArn" => "ARN0")) + elseif url == "$metadata_uri/iam/security-credentials/" + return HTTP.Response(200, name) + elseif url == "$metadata_uri/iam/security-credentials/$name" + return HTTP.Response(200, JSON.json(ec2_json)) + else + return HTTP.Response(404) + end + end + + ecs_expiration = floor(now(UTC), Second) + ecs_json = Dict( + "AccessKeyId" => "AKI_ECS", + "SecretAccessKey" => "SAK_ECS", + "Token" => "TOK_ECS", + "Expiration" => Dates.format(ecs_expiration, dateformat"yyyy-mm-dd\THH:MM:SS\Z"), + ) + + function ecs_metadata(url::AbstractString) + if startswith(url, "http://169.254.170.2/") + return HTTP.Response(200, JSON.json(ecs_json)) + else + return HTTP.Response(404) + end + end + + function http_request_patcher(funcs) + @patch function HTTP.request(method, url, args...; kwargs...) + local r + for f in funcs + r = f(string(url)) + r.status != 404 && break + end + return r + end + end + withenv( [k => nothing for k in filter(startswith("AWS_"), keys(ENV))]..., "AWS_SHARED_CREDENTIALS_FILE" => creds_file, @@ -537,6 +588,37 @@ end creds = AWSCredentials(; profile="profile1") @test creds.access_key_id == "AKI0" end + + @testset "default config credentials over EC2 instance credentials" begin + write( + config_file, + """ + [default] + aws_access_key_id = AKI1 + aws_secret_access_key = SAK1 + """, + ) + isfile(creds_file) && rm(creds_file) + + apply(http_request_patcher([ecs_metadata])) do + @test isnothing(AWS._aws_get_profile(; default=nothing)) + + creds = AWSCredentials() + @test creds.access_key_id == "AKI1" + end + end + + # Note: The AWS CLI behavior was not tested here as this scenario is + # challenging to test for. + @testset "EC2 instance credentials over container credentials" begin + isfile(config_file) && rm(config_file) + isfile(creds_file) && rm(creds_file) + + apply(http_request_patcher([ec2_metadata, ecs_metadata])) do + creds = AWSCredentials() + @test creds.access_key_id == "AKI_EC2" + end + end end end end From 8d65dbd49680a8288fe915cdfd8e010d7883839a Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Wed, 10 May 2023 13:30:36 -0500 Subject: [PATCH 11/13] SSO fix --- src/AWSCredentials.jl | 3 ++- test/AWSCredentials.jl | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/AWSCredentials.jl b/src/AWSCredentials.jl index 90743440ee..a0ab383b54 100644 --- a/src/AWSCredentials.jl +++ b/src/AWSCredentials.jl @@ -23,7 +23,8 @@ export AWSCredentials, external_process_credentials, localhost_is_ec2, localhost_is_lambda, - localhost_maybe_ec2 + localhost_maybe_ec2, + sso_credentials function localhost_maybe_ec2() return localhost_is_ec2() || isfile("/sys/devices/virtual/dmi/id/product_uuid") diff --git a/test/AWSCredentials.jl b/test/AWSCredentials.jl index a592f25635..b154d367b5 100644 --- a/test/AWSCredentials.jl +++ b/test/AWSCredentials.jl @@ -725,7 +725,7 @@ end test_values["AccessKeyId"], test_values["SecretAccessKey"] ), ) do - specified_result = dot_aws_config(test_values["Test-SSO-Profile"]) + specified_result = sso_credentials(test_values["Test-SSO-Profile"]) @test specified_result.access_key_id == test_values["AccessKeyId"] @test specified_result.secret_key == test_values["SecretAccessKey"] From 9483fb8b4516c2bb8bb4e9cfd1644450dac7907b Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Wed, 10 May 2023 13:33:51 -0500 Subject: [PATCH 12/13] Expiration date format --- test/AWSCredentials.jl | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test/AWSCredentials.jl b/test/AWSCredentials.jl index b154d367b5..e2cd2e9a50 100644 --- a/test/AWSCredentials.jl +++ b/test/AWSCredentials.jl @@ -13,6 +13,8 @@ macro test_ecode(error_codes, expr) end end +const EXPIRATION_FMT = dateformat"yyyy-mm-dd\THH:MM:SS\Z" + @testset "Load Credentials" begin user = aws_user_arn(aws) @test occursin(r"^arn:aws:(iam|sts)::[0-9]+:[^:]+$", user) @@ -425,12 +427,11 @@ end aws_secret_access_key = SAK2 """ - ec2_expiration = floor(now(UTC), Second) ec2_json = Dict( "AccessKeyId" => "AKI_EC2", "SecretAccessKey" => "SAK_EC2", "Token" => "TOK_EC2", - "Expiration" => Dates.format(ec2_expiration, dateformat"yyyy-mm-dd\THH:MM:SS\Z"), + "Expiration" => Dates.format(now(UTC), EXPIRATION_FMT), ) function ec2_metadata(url::AbstractString) @@ -447,12 +448,11 @@ end end end - ecs_expiration = floor(now(UTC), Second) ecs_json = Dict( "AccessKeyId" => "AKI_ECS", "SecretAccessKey" => "SAK_ECS", "Token" => "TOK_ECS", - "Expiration" => Dates.format(ecs_expiration, dateformat"yyyy-mm-dd\THH:MM:SS\Z"), + "Expiration" => Dates.format(now(UTC), EXPIRATION_FMT), ) function ecs_metadata(url::AbstractString) @@ -978,7 +978,7 @@ end "AccessKeyId" => "access-key", "SecretAccessKey" => "secret-key", "SessionToken" => "session-token", - "Expiration" => Dates.format(expiration, dateformat"yyyy-mm-dd\THH:MM:SS\Z"), + "Expiration" => Dates.format(expiration, EXPIRATION_FMT), ) creds = external_process_credentials(gen_process(temporary_resp)) @test creds.access_key_id == temporary_resp["AccessKeyId"] @@ -995,7 +995,7 @@ end "Version" => 1, "AccessKeyId" => "access-key", "SecretAccessKey" => "secret-key", - "Expiration" => Dates.format(expiration, dateformat"yyyy-mm-dd\THH:MM:SS\Z"), + "Expiration" => Dates.format(expiration, EXPIRATION_FMT), ) ex = KeyError("SessionToken") @test_throws ex external_process_credentials(gen_process(missing_token_resp)) From 67c7f3c8142d8b9082526a0acdb7449c41da4b90 Mon Sep 17 00:00:00 2001 From: Curtis Vogt Date: Wed, 10 May 2023 13:57:00 -0500 Subject: [PATCH 13/13] Fix source_profile traversal --- src/AWSCredentials.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/AWSCredentials.jl b/src/AWSCredentials.jl index a0ab383b54..2b843354e8 100644 --- a/src/AWSCredentials.jl +++ b/src/AWSCredentials.jl @@ -435,8 +435,6 @@ function sso_credentials(profile=nothing) if !isnothing(sso_start_url) access_key, secret_key, token, expiry = _aws_get_sso_credential_details(p, ini) return AWSCredentials(access_key, secret_key, token; expiry=expiry) - else - return _aws_get_role(p, ini) end end