diff --git a/DESCRIPTION b/DESCRIPTION index 21ad545..e8aa3b3 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,23 +1,29 @@ Package: aws.signature Type: Package Title: Amazon Web Services Request Signatures -Version: 0.6.0 +Version: 0.6.1 Date: 2020-06-01 Authors@R: c(person("Thomas J.", "Leeper", role = c("aut"), email = "thosjleeper@gmail.com", comment = c(ORCID = "0000-0003-4097-6326")), person("Jonathan", "Stott", email = "jonathan.stott@magairports.com", role = c("cre", "aut")), - person("Mike", "Kaminsky", email = "kaminsky.michael@gmail.com", role = "ctb") + person("Mike", "Kaminsky", email = "kaminsky.michael@gmail.com", role = "ctb"), + person("Mark", "Douthwaite", email = "mark.douthwaite@peak.ai", role = "ctb"), + person("Jason", "Gofford", email = "jason.gofford@peak.ai", role = "ctb"), + person("Luke", "Dyer", email = "luke.dyer@peak.ai", role = "ctb") ) Description: Generates version 2 and version 4 request signatures for Amazon Web Services ('AWS') Application Programming Interfaces ('APIs') and provides a mechanism for retrieving credentials from environment variables, 'AWS' credentials files, and 'EC2' instance metadata. For use on 'EC2' instances, users will need to install the suggested package 'aws.ec2metadata' . License: GPL (>= 2) Imports: digest, - base64enc + base64enc, + jsonlite, + curl Suggests: + devtools, testthat (>= 2.1.0), aws.ec2metadata (>= 0.1.6) URL: https://github.com/cloudyr/aws.signature BugReports: https://github.com/cloudyr/aws.signature/issues -RoxygenNote: 7.1.0 +RoxygenNote: 7.1.2 diff --git a/NAMESPACE b/NAMESPACE index 58b4cdc..a5b75c8 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,5 +1,6 @@ # Generated by roxygen2: do not edit by hand +export(assume_role_with_web_identity) export(canonical_request) export(default_credentials_file) export(locate_credentials) diff --git a/NEWS.md b/NEWS.md index fbb2ac2..8f791c5 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,7 @@ +# aws.signature 0.6.1 + +* Add support for [assuming roles from web identities](https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRoleWithWebIdentity.html) ([#62](https://github.com/cloudyr/aws.signature/issues/62)). + # aws.signature 0.6.0 * Allow connection to empty regions (h/t @namelessjon, @lawremi) diff --git a/R/assume_role.R b/R/assume_role.R new file mode 100644 index 0000000..fba0956 --- /dev/null +++ b/R/assume_role.R @@ -0,0 +1,56 @@ +#' @rdname assume_role_with_web_identity +#' @title Assume Role with AWS Web Identity +#' @description Assume a role from a provided Web Identity Token and ARN using AWS Secure Token Service (STS). +#' @param role_arn A character string containing the AWS Role Amazon Resource Name (ARN). This specifies the permissions you have to access other AWS services. +#' @param token_file A character string containing a path to a Web Identity Token file. +#' @param base_url The AWS STS endpoint to use to retrieve your credentials from. +#' @param session_name A character string optionally specifying the name. +#' @param duration The expiry time on the retrieved credentials. +#' @param version The AWS STS specification version to use. +#' @param verbose A logical indicating whether to be verbose. +#' @export +assume_role_with_web_identity <- function( + role_arn, + token_file, + base_url=Sys.getenv("AWS_STS_ENDPOINT", "https://sts.amazonaws.com"), + session_name=NULL, + duration=3600, + version="2011-06-15", + verbose = getOption("verbose", FALSE) +){ + if (is.null(session_name)) { + # strip resource ID from arn and use as default session name + # https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html + session_name <- gsub("/", "-", utils::tail(strsplit(role_arn, ":")[[1]], 1)) + } + + token <- readChar(token_file, file.info(token_file)$size) + + query_params <- list( + Action="AssumeRoleWithWebIdentity", + DurationSeconds=duration, + RoleArn=role_arn, + RoleSessionName=session_name, + WebIdentityToken=token, + Version=version + ) + query_params_names <- curl::curl_escape(names(query_params)) + query_params_values <- lapply(query_params, curl::curl_escape) + query_str <- paste0(query_params_names, "=", query_params_values, collapse = "&") + query_url <- paste0(base_url, "/?", query_str) + + handle <- curl::new_handle() # need to accept json headers + curl::handle_setheaders(handle, "accept" = "application/json") + + response <- curl::curl_fetch_memory(query_url, handle = handle) + content <- jsonlite::fromJSON(rawToChar(response$content)) + + if (response$status_code == 200) { + if (isTRUE(verbose)) { + message("Successfully fetched token from web identiy provider.") + } + return(content) + } else { + stop("Failed to assume role.") + } +} diff --git a/R/locate_credentials.R b/R/locate_credentials.R index b612287..70eb969 100644 --- a/R/locate_credentials.R +++ b/R/locate_credentials.R @@ -12,7 +12,7 @@ #' @details These functions locate values of AWS credentials (access key, secret access key, session token, and region) from likely sources. The order in which these are searched is as follows: #' \enumerate{ #' \item user-supplied values passed to the function -#' \item environment variables (\env{AWS_ACCESS_KEY_ID}, \env{AWS_SECRET_ACCESS_KEY}, \env{AWS_DEFAULT_REGION}, and \env{AWS_SESSION_TOKEN}) +#' \item environment variables, first checking for default credentials (\env{AWS_ACCESS_KEY_ID}, \env{AWS_SECRET_ACCESS_KEY}, \env{AWS_DEFAULT_REGION}, and \env{AWS_SESSION_TOKEN}); then for Web Identity Provider credentials (\env{AWS_ROLE_ARN} and \env{AWS_WEB_IDENTITY_TOKEN_FILE}) #' \item an instance role (on the running ECS task from which this function is called) as identified by \code{\link[aws.ec2metadata]{metadata}}, if the aws.ec2metadata package is installed #' \item an IAM instance role (on the running EC2 instance from which this function is called) as identified by \code{\link[aws.ec2metadata]{metadata}}, if the aws.ec2metadata package is installed #' \item a profile in a local credentials dot file in the current working directory, using the profile specified by \env{AWS_PROFILE} @@ -155,7 +155,7 @@ get_ec2_role <- function(role, verbose = getOption("verbose", FALSE)) { if (inherits(out, "try-error")) { out <- NULL } - out + return(out) } credentials_to_list <- @@ -286,6 +286,7 @@ check_for_user_supplied_profile <- function(profile, file, region, session_token return(NULL) } + check_for_env_vars <- function(region, file, default_region, session_token, verbose) { if (isTRUE(verbose)) { @@ -298,7 +299,9 @@ check_for_env_vars <- function(region, file, default_region, session_token, verb secret = Sys.getenv("AWS_SECRET_ACCESS_KEY"), profile = Sys.getenv("AWS_PROFILE"), session_token = Sys.getenv("AWS_SESSION_TOKEN"), - region = Sys.getenv("AWS_DEFAULT_REGION")) + region = Sys.getenv("AWS_DEFAULT_REGION"), + arn=Sys.getenv("AWS_ROLE_ARN"), + token_file=Sys.getenv("AWS_WEB_IDENTITY_TOKEN_FILE")) if (!is_blank(env$session_token)) { session_token <- env$session_token @@ -327,6 +330,22 @@ check_for_env_vars <- function(region, file, default_region, session_token, verb # early return return(list(key = key, secret = secret, session_token = session_token, region = region)) + + } else if (!is_blank(env$arn) && !is_blank(env$token_file)){ # Web Identity Provider + if (isTRUE(verbose)) { + message("Using Environment Variables 'AWS_WEB_IDENTITY_TOKEN_FILE' and `AWS_ROLE_ARN`") + message("to assume role with Web Identity Provider") + } + response <- assume_role_with_web_identity(env$arn, env$token_file) + + creds <- response$AssumeRoleWithWebIdentityResponse$AssumeRoleWithWebIdentityResult$Credentials + key <- creds$AccessKeyId + secret <- creds$SecretAccessKey + session_token <- creds$SessionToken + + region <- find_region_with_failsafe(region = region, default_region = default_region, verbose = verbose) + + return(list(key = key, secret = secret, session_token = session_token, region = region)) } else if (!is_blank(env$profile)) { return(check_credentials_file(env$profile, file, region, default_region, verbose)) } diff --git a/man/locate_credentials.Rd b/man/locate_credentials.Rd index a698c94..9fe11b5 100644 --- a/man/locate_credentials.Rd +++ b/man/locate_credentials.Rd @@ -39,7 +39,7 @@ Locate AWS credentials from likely sources These functions locate values of AWS credentials (access key, secret access key, session token, and region) from likely sources. The order in which these are searched is as follows: \enumerate{ \item user-supplied values passed to the function - \item environment variables (\env{AWS_ACCESS_KEY_ID}, \env{AWS_SECRET_ACCESS_KEY}, \env{AWS_DEFAULT_REGION}, and \env{AWS_SESSION_TOKEN}) + \item environment variables, first checking for default credentials (\env{AWS_ACCESS_KEY_ID}, \env{AWS_SECRET_ACCESS_KEY}, \env{AWS_DEFAULT_REGION}, and \env{AWS_SESSION_TOKEN}); then for Web Identity Provider credentials (\env{AWS_ROLE_ARN} and \env{AWS_WEB_IDENTITY_TOKEN_FILE}) \item an instance role (on the running ECS task from which this function is called) as identified by \code{\link[aws.ec2metadata]{metadata}}, if the aws.ec2metadata package is installed \item an IAM instance role (on the running EC2 instance from which this function is called) as identified by \code{\link[aws.ec2metadata]{metadata}}, if the aws.ec2metadata package is installed \item a profile in a local credentials dot file in the current working directory, using the profile specified by \env{AWS_PROFILE}