Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

GHArgle #1

Merged
merged 42 commits into from
Feb 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
abf74bd
v1 gha token
igrave Feb 7, 2025
4a86308
docs
igrave Feb 7, 2025
956c56d
pass parameters
igrave Feb 7, 2025
169feaa
no path
igrave Feb 7, 2025
2d25dcf
logic
igrave Feb 7, 2025
1b904a6
token
igrave Feb 7, 2025
5ac9f27
audience
igrave Feb 7, 2025
dfdcb28
prefix
igrave Feb 7, 2025
02681ee
delete line
igrave Feb 7, 2025
39eba31
audiences
igrave Feb 7, 2025
04892f4
envir?
igrave Feb 8, 2025
ab30a74
no params yet
igrave Feb 8, 2025
37ad4c2
in params
igrave Feb 8, 2025
ae521ba
debug
igrave Feb 9, 2025
02c589b
json
igrave Feb 9, 2025
4cc5f9a
print
igrave Feb 9, 2025
a39de58
no scope
igrave Feb 9, 2025
01097c8
req
igrave Feb 9, 2025
7c55091
old
igrave Feb 9, 2025
40039d7
httr::
igrave Feb 9, 2025
5802c01
same
igrave Feb 9, 2025
d6d80a0
url param
igrave Feb 9, 2025
cde26ee
scopes
igrave Feb 9, 2025
97275f9
add email
igrave Feb 9, 2025
6699057
step 1
igrave Feb 9, 2025
d2ea3b7
httr::get
igrave Feb 9, 2025
64a628f
scope scopes
igrave Feb 9, 2025
d432d32
print?
igrave Feb 9, 2025
62b4054
more scopes
igrave Feb 9, 2025
a6eb072
is it the encoding?
igrave Feb 9, 2025
674a276
nope
igrave Feb 9, 2025
4f0ec8b
use same in GET
igrave Feb 9, 2025
432bc8a
compare
igrave Feb 9, 2025
2fa77d6
query to dots
igrave Feb 9, 2025
ea68edb
trying to align
igrave Feb 9, 2025
b3ec0bf
revert back to original
igrave Feb 9, 2025
15ca690
use form encoding
igrave Feb 9, 2025
330df8f
reduce params
igrave Feb 9, 2025
12e4893
more cleaning
igrave Feb 9, 2025
1b36284
delete
igrave Feb 9, 2025
4030f39
docs, add_header
igrave Feb 9, 2025
060be3b
example
igrave Feb 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,4 @@ Config/testthat/edition: 3
Encoding: UTF-8
Language: en-US
Roxygen: list(markdown = TRUE)
RoxygenNote: 7.2.3
RoxygenNote: 7.3.2
2 changes: 2 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ export(credentials_app_default)
export(credentials_byo_oauth2)
export(credentials_external_account)
export(credentials_gce)
export(credentials_github_actions)
export(credentials_service_account)
export(credentials_user_oauth2)
export(field_mask)
Expand All @@ -46,6 +47,7 @@ export(local_cred_funs)
export(local_gargle_verbosity)
export(oauth_app_from_json)
export(oauth_external_token)
export(oauth_gha_token)
export(request_build)
export(request_develop)
export(request_make)
Expand Down
32 changes: 19 additions & 13 deletions R/credentials_external_account.R
Original file line number Diff line number Diff line change
Expand Up @@ -228,17 +228,21 @@ detect_aws_ec2 <- function() {
}

init_oauth_external_account <- function(params) {
credential_source <- params$credential_source
if (!identical(credential_source$environment_id, "aws1")) {
gargle_abort("
{.pkg gargle}'s workload identity federation flow only supports AWS at \\
this time.")
if (params$github_actions) {
serialized_subject_token <- gha_subject_token(params)
} else {
credential_source <- params$credential_source
if (!identical(credential_source$environment_id, "aws1")) {
gargle_abort("
{.pkg gargle}'s workload identity federation flow only supports AWS at \\
this time.")
}
subject_token <- aws_subject_token(
credential_source = credential_source,
audience = params$audience
)
serialized_subject_token <- serialize_subject_token(subject_token)
}
subject_token <- aws_subject_token(
credential_source = credential_source,
audience = params$audience
)
serialized_subject_token <- serialize_subject_token(subject_token)

federated_access_token <- fetch_federated_access_token(
params = params,
Expand All @@ -248,7 +252,8 @@ init_oauth_external_account <- function(params) {
fetch_wif_access_token(
federated_access_token,
impersonation_url = params[["service_account_impersonation_url"]],
scope = params[["scope"]]
scope = params[["scope"]],
lifetime = params[["lifetime"]]
)
}

Expand Down Expand Up @@ -378,13 +383,14 @@ fetch_federated_access_token <- function(params,
# https://cloud.google.com/iam/docs/creating-short-lived-service-account-credentials#sa-credentials-oauth
fetch_wif_access_token <- function(federated_access_token,
impersonation_url,
scope = "https://www.googleapis.com/auth/cloud-platform") {
scope = "https://www.googleapis.com/auth/cloud-platform",
lifetime = "3600s") {
req <- list(
method = "POST",
url = impersonation_url,
# https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/generateAccessToken
# takes scope as an **array**, not a space delimited string
body = list(scope = scope),
body = list(scope = scope, lifetime = lifetime),
token = httr::add_headers(
Authorization = paste("Bearer", federated_access_token$access_token)
)
Expand Down
130 changes: 130 additions & 0 deletions R/credentials_github_actions.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#' Get a token using Github Actions
#'
#' @description

#' `r lifecycle::badge('experimental')`
#'
#' @inheritParams token_fetch

#' @param project_id The google cloud project id
#' @param workload_identity_provider The workload identity provider
#' @param service_account The service account email address
#' @param lifetime Lifespan of token in seconds as a string `"300s"`
#' @param scopes Requested scopes for the access token
#'

#' @seealso There is some setup required in GCP to enable this auth flow.
#' This function reimplements the `google-github-actions/auth`. The
#' documentation for that workflow provides instructions on the setup steps.

#' * <https://github.com/google-github-actions/auth?tab=readme-ov-file#indirect-wif>

#' @return A [WifToken()] or `NULL`.
#' @family credential functions
#' @export
#' @examples
#' \dontrun{
#' credentials_github_actions(
#' project_id = "project-id-12345",
#' workload_identity_provider = "projects/123456789/locations/global/workloadIdentityPools/my-pool/providers/my-provider"
#' service_account = "my-service-account@my-project.iam.gserviceaccount.com",
#' scopes = "https://www.googleapis.com/auth/drive.file"
#' )
#' }
credentials_github_actions <- function(
project_id,
workload_identity_provider,
service_account,
lifetime = "300s",
scopes = "https://www.googleapis.com/auth/drive.file",
...) {
gargle_debug("trying {.fun credentials_github_actions}")
if (!detect_github_actions() || is.null(scopes)) {
return(NULL)
}

scopes <- normalize_scopes(add_email_scope(scopes))

token <- oauth_gha_token(
project_id = project_id,
workload_identity_provider = workload_identity_provider,
service_account = service_account,
lifetime = lifetime,
scopes = scopes,
...
)

if (is.null(token$credentials$access_token) ||
!nzchar(token$credentials$access_token)) {
NULL
} else {
gargle_debug("service account email: {.email {token_email(token)}}")
token
}
}

#' Generate OAuth token for an external account on Github Actions
#'
#' @inheritParams credentials_github_actions
#' @param universe Set the domain for the endpoints
#'
#' @keywords internal
#' @export
oauth_gha_token <- function(project_id,
workload_identity_provider,
service_account,
lifetime,
scopes = "https://www.googleapis.com/auth/drive.file",
id_token_url = Sys.getenv("ACTIONS_ID_TOKEN_REQUEST_URL"),
id_token_request_token = Sys.getenv("ACTIONS_ID_TOKEN_REQUEST_TOKEN")) {
if (id_token_url == "" || id_token_request_token == "") {
gargle_abort(paste0(
"GitHub Actions did not inject $ACTIONS_ID_TOKEN_REQUEST_TOKEN or ",
"$ACTIONS_ID_TOKEN_REQUEST_URL into this job. This most likely means the ",
"GitHub Actions workflow permissions are incorrect, or this job is being ",
"run from a fork. For more information, please see ",
"https://docs.github.com/en/actions/security-guides/automatic-token-authentication#permissions-for-the-github_token"
))
}

params <- list(
scopes = scopes, # this is $scopes but WifToken$new() copies it to $scope
lifetime = lifetime,
id_token_url = id_token_url,
id_token_request_token = id_token_request_token,
github_actions = TRUE,
token_url = "https://sts.googleapis.com/v1/token",
audience = paste0("//iam.googleapis.com/", workload_identity_provider),
oidc_token_audience = paste0("https://iam.googleapis.com/", workload_identity_provider),
subject_token_type = "urn:ietf:params:oauth:token-type:jwt",
service_account_impersonation_url = paste0(
"https://iamcredentials.googleapis.com/v1/projects/-/serviceAccounts/",
service_account,
":generateAccessToken"
),
as_header = TRUE
)
WifToken$new(params = params)
}


detect_github_actions <- function() {
if (Sys.getenv("GITHUB_ACTIONS") == "true") {
return(TRUE)
}
gargle_debug("Environment variable GITHUB_ACTIONS is not 'true'")
FALSE
}

gha_subject_token <- function(params) {
gargle_debug("gha_subject_token")

req <- list(
method = "GET",
url = params[["id_token_url"]],
token = httr::add_headers(Authorization = paste("Bearer", params$id_token_request_token))
)
query_audience <- list(audience = params$oidc_token_audience)
resp <- request_make(req, query = query_audience)
response_process(resp)$value
}
1 change: 1 addition & 0 deletions man/credentials_app_default.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/credentials_byo_oauth2.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/credentials_external_account.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/credentials_gce.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions man/credentials_github_actions.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/credentials_service_account.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/credentials_user_oauth2.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

33 changes: 33 additions & 0 deletions man/oauth_gha_token.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions man/token_fetch.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.