Skip to content

Commit 0aa8fed

Browse files
bors[bot]omus
andauthored
Merge #622
622: Support providing a full URI to the ECS credential provider r=omus a=omus Discovered that our ECS credential support was lacking support for the [`AWS_CONTAINER_CREDENTIALS_FULL_URI` and the `AWS_CONTAINER_AUTHORIZATION_TOKEN`](https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html) while I was working on `aws-vault` support (#612). With `aws-vault` you can use `aws-vault exec --ecs-server` to run the tool in server mode which emulates the ECS credential provider and uses these environmental variables. Co-authored-by: Curtis Vogt <[email protected]>
2 parents 9a4322a + 13bece5 commit 0aa8fed

File tree

3 files changed

+145
-35
lines changed

3 files changed

+145
-35
lines changed

Project.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "AWS"
22
uuid = "fbe9abb3-538b-5e4e-ba9e-bc94f4f92ebc"
33
license = "MIT"
4-
version = "1.86.0"
4+
version = "1.87.0"
55

66
[deps]
77
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"

src/AWSCredentials.jl

+15-3
Original file line numberDiff line numberDiff line change
@@ -343,17 +343,29 @@ More information can be found at:
343343
function ecs_instance_credentials()
344344
# The Amazon ECS agent will automatically populate the environmental variable
345345
# `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` when running inside of an ECS task. We're
346-
# interpreting this to mean than ECS credential provider should only be used if the
347-
# `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` variable is set.
346+
# interpreting this to mean than ECS credential provider should only be used if any of
347+
# the `AWS_CONTAINER_CREDENTIALS_*_URI` variables are set.
348348
# – https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
349+
#
350+
# > Note: This setting (`AWS_CONTAINER_CREDENTIALS_FULL_URI`) is an alternative to
351+
# > `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` and will only be used if
352+
# > `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` is not set.
353+
# – https://docs.aws.amazon.com/sdkref/latest/guide/feature-container-credentials.html
349354
if haskey(ENV, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
350355
endpoint = "http://169.254.170.2" * ENV["AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"]
356+
elseif haskey(ENV, "AWS_CONTAINER_CREDENTIALS_FULL_URI")
357+
endpoint = ENV["AWS_CONTAINER_CREDENTIALS_FULL_URI"]
351358
else
352359
return nothing
353360
end
354361

362+
headers = Pair{String,String}[]
363+
if haskey(ENV, "AWS_CONTAINER_AUTHORIZATION_TOKEN")
364+
push!(headers, "Authorization" => ENV["AWS_CONTAINER_AUTHORIZATION_TOKEN"])
365+
end
366+
355367
response = try
356-
@mock HTTP.request("GET", endpoint; retry=false, connect_timeout=5)
368+
@mock HTTP.request("GET", endpoint, headers; retry=false, connect_timeout=5)
357369
catch e
358370
e isa HTTP.Exceptions.ConnectError && return nothing
359371
rethrow()

test/AWSCredentials.jl

+129-31
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ end
1515

1616
const EXPIRATION_FMT = dateformat"yyyy-mm-dd\THH:MM:SS\Z"
1717

18+
http_header(h::Vector, k, d="") = get(Dict(h), k, d)
19+
http_header(args...) = HTTP.header(args...)
20+
1821
@testset "Load Credentials" begin
1922
user = aws_user_arn(aws)
2023
@test occursin(r"^arn:aws:(iam|sts)::[0-9]+:[^:]+$", user)
@@ -463,6 +466,14 @@ end
463466
end
464467
end
465468

469+
function ecs_metadata_localhost(url::AbstractString)
470+
if startswith(url, "http://localhost:8080")
471+
return HTTP.Response(200, JSON.json(ecs_json))
472+
else
473+
return HTTP.Response(404)
474+
end
475+
end
476+
466477
function http_request_patcher(funcs)
467478
@patch function HTTP.request(method, url, args...; kwargs...)
468479
local r
@@ -643,6 +654,17 @@ end
643654
@test creds.access_key_id == "AKI1"
644655
end
645656
end
657+
658+
withenv(
659+
"AWS_CONTAINER_CREDENTIALS_FULL_URI" => "http://localhost:8080"
660+
) do
661+
apply(http_request_patcher([ecs_metadata_localhost])) do
662+
@test isnothing(AWS._aws_get_profile(; default=nothing))
663+
664+
creds = AWSCredentials()
665+
@test creds.access_key_id == "AKI1"
666+
end
667+
end
646668
end
647669

648670
@testset "default config credentials over EC2 instance credentials" begin
@@ -674,6 +696,16 @@ end
674696
@test creds.access_key_id == "AKI_ECS"
675697
end
676698
end
699+
700+
withenv(
701+
"AWS_CONTAINER_CREDENTIALS_FULL_URI" => "http://localhost:8080"
702+
) do
703+
p = http_request_patcher([ec2_metadata, ecs_metadata_localhost])
704+
apply(p) do
705+
creds = AWSCredentials()
706+
@test creds.access_key_id == "AKI_ECS"
707+
end
708+
end
677709
end
678710

679711
# Note: It appears that the ECS container credentials are only used when
@@ -707,34 +739,12 @@ end
707739
"InstanceProfileArn" => "Test-Arn",
708740
"RoleArn" => "Test-Arn",
709741
"Expiration" => now(UTC),
710-
"URI" => "/Test-URI/",
711742
"Security-Credentials" => "Test-Security-Credentials",
712743
"Test-SSO-Profile" => "sso-test",
713744
"Test-SSO-start-url" => "https://test-sso.com/start",
714745
"Test-SSO-Role" => "SSORoleName",
715746
)
716747

717-
_http_request_patch = @patch function HTTP.request(method::String, url; kwargs...)
718-
security_credentials = test_values["Security-Credentials"]
719-
uri = test_values["URI"]
720-
url = string(url)
721-
722-
metadata_uri = "http://169.254.169.254/latest/meta-data"
723-
if url == "$metadata_uri/iam/info"
724-
instance_profile_arn = test_values["InstanceProfileArn"]
725-
return HTTP.Response("{\"InstanceProfileArn\": \"$instance_profile_arn\"}")
726-
elseif url == "$metadata_uri/iam/security-credentials/"
727-
return HTTP.Response(test_values["Security-Credentials"])
728-
elseif url == "$metadata_uri/iam/security-credentials/$security_credentials" ||
729-
url == "http://169.254.170.2$uri"
730-
my_dict = JSON.json(test_values)
731-
response = HTTP.Response(my_dict)
732-
return response
733-
else
734-
return nothing
735-
end
736-
end
737-
738748
@testset "~/.aws/config - Default Profile" begin
739749
mktemp() do config_file, config_io
740750
write(
@@ -902,15 +912,32 @@ end
902912
secret_key = "secret-key-$(randstring(6))"
903913
session_token = "session-token-$(randstring(6))"
904914
session_name = "$role_name-session"
905-
patch = Patches._assume_role_patch(
915+
916+
assume_role_patch = Patches._assume_role_patch(
906917
"AssumeRole";
907918
access_key=access_key,
908919
secret_key=secret_key,
909920
session_token=session_token,
910921
role_arn=role_arn,
911922
)
923+
ec2_metadata_patch = @patch function HTTP.request(method::String, url; kwargs...)
924+
url = string(url)
925+
security_credentials = test_values["Security-Credentials"]
926+
927+
metadata_uri = "http://169.254.169.254/latest/meta-data"
928+
if url == "$metadata_uri/iam/info"
929+
json = JSON.json("InstanceProfileArn" => test_values["InstanceProfileArn"])
930+
return HTTP.Response(200, json)
931+
elseif url == "$metadata_uri/iam/security-credentials/"
932+
return HTTP.Response(200, security_credentials)
933+
elseif url == "$metadata_uri/iam/security-credentials/$security_credentials"
934+
return HTTP.Response(200, JSON.json(test_values))
935+
else
936+
return HTTP.Response(404)
937+
end
938+
end
912939

913-
apply([patch, _http_request_patch]) do
940+
apply([assume_role_patch, ec2_metadata_patch]) do
914941
result = ec2_instance_credentials("default")
915942
@test result.access_key_id == test_values["AccessKeyId"]
916943
@test result.secret_key == test_values["SecretAccessKey"]
@@ -947,14 +974,36 @@ end
947974
end
948975

949976
@testset "Instance - ECS" begin
950-
withenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" => test_values["URI"]) do
951-
apply(_http_request_patch) do
977+
expiration = floor(now(UTC), Second)
978+
rel_uri_json = Dict(
979+
"AccessKeyId" => "AKI_REL_ECS",
980+
"SecretAccessKey" => "SAK_REL_ECS",
981+
"Token" => "TOK_REL_ECS",
982+
"Expiration" => Dates.format(expiration, dateformat"yyyy-mm-dd\THH:MM:SS\Z"),
983+
"RoleArn" => "ROLE_REL_ECS",
984+
)
985+
986+
rel_uri_patch = @patch function HTTP.request(::String, url, headers=[]; kwargs...)
987+
url = string(url)
988+
989+
@test url == "http://169.254.170.2/get-credentials"
990+
@test isempty(headers)
991+
992+
if url == "http://169.254.170.2/get-credentials"
993+
return HTTP.Response(200, JSON.json(rel_uri_json))
994+
else
995+
return HTTP.Response(404)
996+
end
997+
end
998+
999+
withenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" => "/get-credentials") do
1000+
apply(rel_uri_patch) do
9521001
result = ecs_instance_credentials()
953-
@test result.access_key_id == test_values["AccessKeyId"]
954-
@test result.secret_key == test_values["SecretAccessKey"]
955-
@test result.token == test_values["Token"]
956-
@test result.user_arn == test_values["RoleArn"]
957-
@test result.expiry == test_values["Expiration"]
1002+
@test result.access_key_id == rel_uri_json["AccessKeyId"]
1003+
@test result.secret_key == rel_uri_json["SecretAccessKey"]
1004+
@test result.token == rel_uri_json["Token"]
1005+
@test result.user_arn == rel_uri_json["RoleArn"]
1006+
@test result.expiry == expiration
9581007
@test result.renew == ecs_instance_credentials
9591008
end
9601009
end
@@ -971,6 +1020,55 @@ end
9711020
# Internally throws a `ConnectError` exception
9721021
@test ecs_instance_credentials() === nothing
9731022
end
1023+
1024+
full_uri_json = Dict(
1025+
"AccessKeyId" => "AKI_FULL_ECS",
1026+
"SecretAccessKey" => "SAK_FULL_ECS",
1027+
"Token" => "TOK_FULL_ECS",
1028+
"Expiration" => Dates.format(expiration, dateformat"yyyy-mm-dd\THH:MM:SS\Z"),
1029+
"RoleArn" => "ROLE_FULL_ECS",
1030+
)
1031+
1032+
full_uri_patch = @patch function HTTP.request(::String, url, headers=[]; kwargs...)
1033+
url = string(url)
1034+
authorization = http_header(headers, "Authorization")
1035+
1036+
@test url == "http://localhost/get-credentials"
1037+
@test authorization == "Basic abcd"
1038+
1039+
if url == "http://localhost/get-credentials" && authorization == "Basic abcd"
1040+
return HTTP.Response(200, JSON.json(full_uri_json))
1041+
else
1042+
return HTTP.Response(403)
1043+
end
1044+
end
1045+
1046+
withenv(
1047+
"AWS_CONTAINER_CREDENTIALS_FULL_URI" => "http://localhost/get-credentials",
1048+
"AWS_CONTAINER_AUTHORIZATION_TOKEN" => "Basic abcd",
1049+
) do
1050+
apply(full_uri_patch) do
1051+
result = ecs_instance_credentials()
1052+
@test result.access_key_id == full_uri_json["AccessKeyId"]
1053+
@test result.secret_key == full_uri_json["SecretAccessKey"]
1054+
@test result.token == full_uri_json["Token"]
1055+
@test result.user_arn == full_uri_json["RoleArn"]
1056+
@test result.expiry == expiration
1057+
@test result.renew == ecs_instance_credentials
1058+
end
1059+
end
1060+
1061+
# `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` should be preferred over
1062+
# `AWS_CONTAINER_CREDENTIALS_FULL_URI`.
1063+
withenv(
1064+
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI" => "/get-credentials",
1065+
"AWS_CONTAINER_CREDENTIALS_FULL_URI" => "http://localhost/get-credentials",
1066+
) do
1067+
apply(rel_uri_patch) do
1068+
result = ecs_instance_credentials()
1069+
@test result.access_key_id == rel_uri_json["AccessKeyId"]
1070+
end
1071+
end
9741072
end
9751073

9761074
@testset "Web Identity File" begin

0 commit comments

Comments
 (0)