Skip to content

Commit

Permalink
Merge #257
Browse files Browse the repository at this point in the history
257: Use WebIdentityTokens to create credentials r=mattBrzezinski a=mattBrzezinski

### Resolves #244 

These changes use existing credentials to assume temporary credentials via a [Web Identity File](https://docs.aws.amazon.com/credref/latest/refdocs/setting-global-web_identity_token_file.html) using [AssumeRoleWithWebIdentity](https://github.com/JuliaCloud/AWS.jl/blob/master/src/services/sts.jl#L48).

The code was taken from the issue itself, with a slight modification to ensure that the appropriate env vars are set before proceeding.

Co-authored-by: Matt Brzezinski <[email protected]>
  • Loading branch information
bors[bot] and mattBrzezinski authored Dec 3, 2020
2 parents ad5b0b7 + 11e926a commit 1e012b4
Show file tree
Hide file tree
Showing 5 changed files with 106 additions and 13 deletions.
47 changes: 45 additions & 2 deletions src/AWSCredentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ export AWSCredentials,
env_var_credentials,
localhost_is_ec2,
localhost_maybe_ec2,
localhost_is_lambda
localhost_is_lambda,
credentials_from_webtoken

"""
AWSCredentials
Expand Down Expand Up @@ -77,7 +78,7 @@ end


function Base.show(io::IO, c::AWSCredentials)
println(io,
print(io,
c.user_arn,
isempty(c.user_arn) ? "" : " ",
"(",
Expand Down Expand Up @@ -452,6 +453,48 @@ function dot_aws_config(profile=nothing)
end


function credentials_from_webtoken(profile=nothing)
token_role_arn = "AWS_ROLE_ARN"
token_role_session = "AWS_ROLE_SESSION_NAME"
token_web_identity = "AWS_WEB_IDENTITY_TOKEN_FILE"

has_all_keys =
haskey(ENV, token_role_arn) &&
haskey(ENV, token_role_session) &&
haskey(ENV, token_web_identity)

if !has_all_keys
throw(WebIdentityVarsNotSet(
"You need to set $(token_role_arn), $(token_role_session), $(token_web_identity) environment variables"
))
end

role_arn = ENV[token_role_arn]
role_session = ENV[token_role_session]
web_identity = read(ENV["AWS_WEB_IDENTITY_TOKEN_FILE"], String)

resp = AWSServices.sts(
"AssumeRoleWithWebIdentity",
Dict(
"RoleArn" => role_arn,
"RoleSessionName" => role_session,
"WebIdentityToken" => web_identity
);
aws_config=AWSConfig(profile=profile)
)

role_creds = resp["AssumeRoleWithWebIdentityResult"]["Credentials"]

return AWSCredentials(
role_creds["AccessKeyId"],
role_creds["SecretAccessKey"],
role_creds["SessionToken"];
expiry=DateTime(rstrip(role_creds["Expiration"], 'Z')),
renew=credentials_from_webtoken
)
end


"""
_aws_get_credential_details(profile::AbstractString, ini::Inifile) -> Tuple
Expand Down
7 changes: 6 additions & 1 deletion src/AWSExceptions.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ using JSON
using XMLDict
using XMLDict: XMLDictElement

export AWSException, ProtocolNotDefined, InvalidFileName, NoCredentials
export AWSException, ProtocolNotDefined, InvalidFileName, NoCredentials, WebIdentityVarsNotSet

struct ProtocolNotDefined <: Exception
message::String
Expand All @@ -22,6 +22,11 @@ struct NoCredentials <: Exception
end
Base.show(io::IO, e::NoCredentials) = println(io, e.message)

struct WebIdentityVarsNotSet <: Exception
message::String
end
Base.show(io::IO, e::WebIdentityVarsNotSet) = println(io, e.message)

struct AWSException <: Exception
code::String
message::String
Expand Down
36 changes: 27 additions & 9 deletions test/AWSCredentials.jl
Original file line number Diff line number Diff line change
Expand Up @@ -462,17 +462,35 @@ end
end
end

@testset "Credentials Not Found" begin
_http_request_patch = @patch function HTTP.request(method::String, url::String)
return nothing
end
@testset "Web Identity File" begin
mktempdir() do dir
web_identity_file = joinpath(dir, "web_identity")
write(web_identity_file, "foobar")

_cred_file_patch = @patch function dot_aws_credentials_file()
return ""
withenv(
"AWS_ROLE_ARN" => "foobar",
"AWS_ROLE_SESSION_NAME" => Patches.web_sesh_token,
"AWS_WEB_IDENTITY_TOKEN_FILE" => web_identity_file,
) do
apply(Patches._web_identity_patch) do
result = credentials_from_webtoken()

@test result.access_key_id == Patches.web_access_key
@test result.secret_key == Patches.web_secret_key
@test result.token == Patches.web_sesh_token
@test result.renew == credentials_from_webtoken
end
end
end
end

@testset "Web Identity File -- Exception" begin
@test_throws WebIdentityVarsNotSet credentials_from_webtoken()
end

_config_file_patch = @patch function dot_aws_config_file()
return ""
@testset "Credentials Not Found" begin
_http_request_patch = @patch function HTTP.request(method::String, url::String)
return nothing
end

ACCESS_KEY = "AWS_ACCESS_KEY_ID"
Expand All @@ -485,7 +503,7 @@ end
delete!(ENV, "AWS_ACCESS_KEY_ID")
delete!(ENV, "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")

apply([_http_request_patch, _cred_file_patch, _config_file_patch]) do
apply([_http_request_patch, Patches._cred_file_patch, Patches._config_file_patch]) do
@test_throws ErrorException AWSConfig()
end
finally
Expand Down
27 changes: 27 additions & 0 deletions test/patch.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
module Patches

using AWS
using Dates
using HTTP
using JSON
using GitHub
using Mocking
using OrderedCollections: LittleDict
Expand Down Expand Up @@ -42,6 +44,10 @@ body = """

response = HTTP.Messages.Response()

web_access_key = "web_identity_access_key"
web_secret_key = "web_identity_secret_key"
web_sesh_token = "web_session_token"

function _response!(; version::VersionNumber=version, status::Int64=status, headers::Array=headers, body::String=body)
response.version = version
response.status = status
Expand All @@ -55,6 +61,27 @@ _aws_http_request_patch = @patch function AWS._http_request(request::Request)
return response
end

_cred_file_patch = @patch function dot_aws_credentials_file()
return ""
end

_config_file_patch = @patch function dot_aws_config_file()
return ""
end

_web_identity_patch = @patch function AWS._http_request(request)
creds = Dict(
"AccessKeyId" => web_access_key,
"SecretAccessKey" => web_secret_key,
"SessionToken" => web_sesh_token,
"Expiration" => string(now(UTC))
)

result = Dict("AssumeRoleWithWebIdentityResult" => Dict("Credentials" => creds))

return HTTP.Response(200, ["Content-Type" => "text/json", "charset" => "utf-8"], body=json(result))
end

_github_tree_patch = @patch function tree(repo, tree_obj; kwargs...)
if tree_obj == "master"
return Tree("test-sha", HTTP.URI(), [Dict("path"=>"apis", "sha"=>"apis-sha")], false)
Expand Down
2 changes: 1 addition & 1 deletion test/runtests.jl
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using AWS
using AWS: AWSCredentials
using AWS: AWSServices
using AWS.AWSExceptions: AWSException, NoCredentials
using AWS.AWSExceptions: AWSException, NoCredentials, WebIdentityVarsNotSet
using AWS.AWSMetadataUtilities: _clean_documentation, _filter_latest_service_version,
_generate_low_level_definition, _generate_high_level_definition, _generate_high_level_definitions,
_get_aws_sdk_js_files, _get_service_and_version, _get_function_parameters, _clean_uri, _format_function_name,
Expand Down

0 comments on commit 1e012b4

Please sign in to comment.