From 4a0ceabb614728496296c56a876c6bdd25f8087e Mon Sep 17 00:00:00 2001 From: Scott Chamberlain Date: Thu, 7 Mar 2024 06:34:23 -0800 Subject: [PATCH 1/7] #29 add secrets manager interface --- .gitignore | 4 + DESCRIPTION | 4 +- NAMESPACE | 12 ++ R/globals.R | 5 + R/onload.R | 1 + R/secrets_manager.R | 279 ++++++++++++++++++++++++++ R/sixtyfour-package.R | 1 + _pkgdown.yml | 3 + man/aws_secrets_all.Rd | 19 ++ man/aws_secrets_create.Rd | 61 ++++++ man/aws_secrets_delete.Rd | 36 ++++ man/aws_secrets_get.Rd | 40 ++++ man/aws_secrets_list.Rd | 26 +++ man/aws_secrets_pwd.Rd | 21 ++ man/aws_secrets_rotate.Rd | 46 +++++ man/aws_secrets_update.Rd | 55 +++++ man/construct_db_secret.Rd | 36 ++++ tests/testthat/helper-vcr.R | 9 + tests/testthat/test-secrets_manager.R | 84 ++++++++ 19 files changed, 741 insertions(+), 1 deletion(-) create mode 100644 R/secrets_manager.R create mode 100644 man/aws_secrets_all.Rd create mode 100644 man/aws_secrets_create.Rd create mode 100644 man/aws_secrets_delete.Rd create mode 100644 man/aws_secrets_get.Rd create mode 100644 man/aws_secrets_list.Rd create mode 100644 man/aws_secrets_pwd.Rd create mode 100644 man/aws_secrets_rotate.Rd create mode 100644 man/aws_secrets_update.Rd create mode 100644 man/construct_db_secret.Rd create mode 100644 tests/testthat/test-secrets_manager.R diff --git a/.gitignore b/.gitignore index ecf9281..f98f503 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ .DS_Store docs inst/doc +tests/fixtures/aws_db_rds_create.yml +tests/fixtures/aws_db_redshift_create.yml +tests/fixtures/aws_secret*.yml +tests/fixtures/aws_db_rds_create.yml diff --git a/DESCRIPTION b/DESCRIPTION index 6ffe1ef..951b467 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -28,7 +28,9 @@ Imports: s3fs (>= 0.1.3), cli, glue, - memoise + memoise, + uuid, + jsonlite Suggests: knitr, rmarkdown, diff --git a/NAMESPACE b/NAMESPACE index 7a0b70e..23dc521 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -39,6 +39,14 @@ export(aws_role) export(aws_role_create) export(aws_role_delete) export(aws_roles) +export(aws_secrets_all) +export(aws_secrets_create) +export(aws_secrets_delete) +export(aws_secrets_get) +export(aws_secrets_list) +export(aws_secrets_pwd) +export(aws_secrets_rotate) +export(aws_secrets_update) export(aws_user) export(aws_user_access_key) export(aws_user_add_to_group) @@ -56,11 +64,15 @@ importFrom(cli,cli_progress_update) importFrom(cli,pb_spin) importFrom(dplyr,bind_rows) importFrom(dplyr,filter) +importFrom(dplyr,last_col) importFrom(dplyr,mutate) importFrom(dplyr,pull) +importFrom(dplyr,relocate) importFrom(fs,file_exists) importFrom(fs,fs_bytes) importFrom(glue,glue) +importFrom(jsonlite,fromJSON) +importFrom(jsonlite,toJSON) importFrom(lubridate,as_datetime) importFrom(magrittr,"%>%") importFrom(paws,costexplorer) diff --git a/R/globals.R b/R/globals.R index a570ec6..9242c1c 100644 --- a/R/globals.R +++ b/R/globals.R @@ -6,6 +6,11 @@ utils::globalVariables(c( "PolicyName", # "Arn", # ".", # + ".", # + "arn", # + "created_date", # + "secret_raw", # + "secret_str", # "PasswordLastUsed", # "CreateDate", # NULL diff --git a/R/onload.R b/R/onload.R index bbb4987..8291e01 100644 --- a/R/onload.R +++ b/R/onload.R @@ -8,4 +8,5 @@ env64 <- new.env() # iam and costexplorer services env64$iam <- paws::iam() env64$costexplorer <- paws::costexplorer() + env64$secretsmanager <- paws::secretsmanager() } diff --git a/R/secrets_manager.R b/R/secrets_manager.R new file mode 100644 index 0000000..000aa6d --- /dev/null +++ b/R/secrets_manager.R @@ -0,0 +1,279 @@ +#' List secrets +#' @export +#' @param ... parameters passed on to the `paws` method +#' @note see +#' for available parameters +#' @return (list) list with secrets +#' @examples \dontrun{ +#' aws_secrets_list() +#' } +aws_secrets_list <- function(...) { + env64$secretsmanager$list_secrets(...) +} + +#' Get all secret values +#' @importFrom dplyr relocate last_col +#' @export +#' @return (list) list with secrets +#' @autoglobal +#' @examples \dontrun{ +#' aws_secrets_list() +#' } +aws_secrets_all <- function() { + tmp <- env64$secretsmanager$list_secrets() %>% + .$SecretList %>% + purrr::map(function(x) aws_secrets_get(x$Name)) + + new_secrets <- list() + for (i in seq_along(tmp)) { + new_secrets[[i]] <- c( + list( + name = tmp[[i]]$Name, + arn = tmp[[i]]$ARN, + created_date = tmp[[i]]$CreatedDate + ), + jsonlite::fromJSON(tmp[[i]]$SecretString) + ) + } + Filter(function(x) length(x$host) > 0, new_secrets) %>% + bind_rows() %>% + relocate(arn, created_date, .after = last_col()) +} + +check_secret <- function(secret) { + if (!inherits(secret, c("raw", "character"))) { + stop("`secret` must be of class character or raw", call. = FALSE) + } +} + +#' Get a random password +#' +#' @export +#' @param ... named parameters passed on to `get_random_password` +#' +#' @examples \dontrun{ +#' aws_secrets_pwd() +#' aws_secrets_pwd(ExcludeNumbers = TRUE) +#' } +aws_secrets_pwd <- function(...) { + env64$secretsmanager$get_random_password( + PasswordLength = 40L, + ExcludePunctuation = TRUE, + ... + )$RandomPassword +} + +#' Create a secret +#' +#' This function does not create your database username and/or password. +#' Instead, it creates a "secret", which is typically a combination +#' of credentials (username + password + other metadata) +#' +#' @export +#' @param name (character) The name of the new secret. required +#' @param secret (character/raw) The text or raw data to encrypt and store +#' in this new version of the secret. AWS recommends for text to use a JSON +#' structure of key/value pairs for your secret value (see examples below). +#' required +#' @param description (character) The description of the secret. optional +#' @param ... further named parameters passed on to `create_secret` +#' +#' @return (list) with fields: +#' - ARN +#' - Name +#' - VersionId +#' - ReplicationStatus +#' @details Note that we autogenerate a random UUID to pass to the +#' `ClientRequestToken` parameter of the `paws` function `create_secret` +#' used internally in this function. +#' +#' This function creates a new secret. See [aws_secrets_update()] to +#' update an existing secret. This function fails if you call it with +#' an existing secret with the same name or ARN +#' @examples \dontrun{ +#' # Text secret +#' x <- aws_secrets_create( +#' name = "MyTestDatabaseSecret", +#' secret = '{"username":"david","password":"EXAMPLE-PASSWORD"}', +#' description = "My test database secret as a string" +#' ) +#' +#' # Raw secret +#' x <- aws_secrets_create( +#' name = "MyRawDatabaseSecret", +#' secret = charToRaw('{"username":"david","password":"EXAMPLE-PASSWORD"}'), +#' description = "My test database secret as raw" +#' ) +#' } +aws_secrets_create <- function(name, secret, description = NULL, ...) { + check_secret(secret) + secret_str <- secret_raw <- NULL + if (rlang::is_raw(secret)) secret_raw <- secret + if (rlang::is_character(secret)) secret_str <- secret + env64$secretsmanager$create_secret( + Name = name, + ClientRequestToken = uuid::UUIDgenerate(), + Description = description, + SecretBinary = secret_raw, + SecretString = secret_str, ... + ) +} + +#' Update a secret +#' @export +#' @inheritParams aws_secrets_create +#' @param id (character) The name or ARN of the secret. required +#' @param ... further named parameters passed on to `put_secret_value` +#' +#' @return (list) with fields: +#' - ARN +#' - Name +#' - VersionId +#' - VersionStages +#' @autoglobal +#' @details Note that we autogenerate a random UUID to pass to the +#' `ClientRequestToken` parameter of the `paws` function used internally +#' @examples \dontrun{ +#' # Create a secret +#' aws_secrets_create( +#' name = "TheSecret", +#' secret = '{"username":"jane","password":"cat"}', +#' description = "A string" +#' ) +#' +#' aws_secrets_get("TheSecret") +#' +#' # Update the secret +#' aws_secrets_update( +#' id = "TheSecret", +#' secret = '{"username":"jane","password":"kitten"}' +#' ) +#' +#' aws_secrets_get("TheSecret") +#' } +aws_secrets_update <- function(id, secret, ...) { + check_secret(secret) + secret_str <- secret_raw <- NULL + if (rlang::is_raw(secret)) secret_raw <- secret + if (rlang::is_character(secret)) secret_str <- secret + env64$secretsmanager$put_secret_value( + SecretId = id, + ClientRequestToken = uuid::UUIDgenerate(), + SecretBinary = secret_raw, + SecretString = secret_str, ... + ) +} + +#' Get a secret +#' @export +#' @inheritParams aws_secrets_update +#' @param ... further named parameters passed on to `get_secret_value` +#' +#' @return (list) with fields: +#' - ARN +#' - Name +#' - VersionId +#' - SecretBinary +#' - SecretString +#' - VersionStages +#' - CreatedDate +#' @examples \dontrun{ +#' # Does exist +#' aws_secrets_get(id = "MyTestDatabaseSecret") +#' +#' # Does not exist +#' # aws_secrets_get(id = "DoesntExist") +#' #> Error: ResourceNotFoundException (HTTP 400). Secrets Manager +#' #> can't find the specified secret. +#' } +aws_secrets_get <- function(id, ...) { + env64$secretsmanager$get_secret_value(SecretId = id, ...) +} + +#' Delete a secret +#' @export +#' @inheritParams aws_secrets_update +#' @param ... further named parameters passed on to `delete_secret` +#' +#' @return (list) with fields: +#' - ARN +#' - Name +#' - DeletionDate +#' @examples \dontrun{ +#' # Does exist +#' aws_secrets_delete(id = "MyTestDatabaseSecret") +#' +#' # Does not exist +#' # aws_secrets_get(id = "DoesntExist") +#' #> Error: ResourceNotFoundException (HTTP 400). Secrets Manager +#' #> can't find the specified secret. +#' } +aws_secrets_delete <- function(id, ...) { + env64$secretsmanager$delete_secret(SecretId = id, ...) +} + +#' Rotate a secret +#' @export +#' @inheritParams aws_secrets_update +#' @inherit aws_secrets_update details +#' @param lambda_arn (character) The ARN of the Lambda rotation function. +#' Only supply for secrets that use a Lambda rotation function to rotate +#' @param rules (list) asdfadf +#' @param immediately (logical) whether to rotate the secret immediately or not. +#' default: `TRUE` +#' @references +#' @autoglobal +#' @return (list) with fields: +#' - ARN +#' - Name +#' - VersionId +#' @examples \dontrun{ +#' aws_secrets_rotate(id = "MyTestDatabaseSecret") +#' aws_secrets_rotate(id = "MyTestDatabaseSecret", rules = list( +#' Duration = "2h", +#' ScheduleExpression = "cron(0 16 1,15 * ? *)" +#' )) +#' } +aws_secrets_rotate <- function( + id, lambda_arn = NULL, rules = NULL, + immediately = TRUE) { + env64$secretsmanager$rotate_secret( + SecretId = id, + ClientRequestToken = uuid::UUIDgenerate(), + RotationLambdaARN = secret_raw, + RotationRules = secret_str, + RotateImmediately = immediately + ) +} + +#' Construct a database secret string or raw version of it +#' +#' @param engine,host,username,password,dbname,port supply parameters to +#' go into either a json string or raw version of the json string +#' @param as (character) one of "string" or "raw" +#' @keywords internal +#' @references +#' # nolint +#' @examples \dontrun{ +#' construct_db_secret("redshift", dbname = "hello", port = 5439) +#' construct_db_secret("mariadb", dbname = "world", port = 3306) +#' construct_db_secret("postgresql", dbname = "bears", port = 5432, as = "raw") +#' } +construct_db_secret <- function( + engine, host = "", username = "", + password = "", dbname = "", port = "", as = "string") { + dat <- list( + "engine" = engine, + "host" = host, + "username" = username, + "password" = password, + "dbname" = dbname, + "port" = port + ) + json_dat <- jsonlite::toJSON(dat, auto_unbox = TRUE) + switch(as, + string = as.character(json_dat), + raw = charToRaw(json_dat), + stop("`as` must be one of 'string' or 'raw'", call. = FALSE) + ) +} diff --git a/R/sixtyfour-package.R b/R/sixtyfour-package.R index 3dbf5d6..dd4e34f 100644 --- a/R/sixtyfour-package.R +++ b/R/sixtyfour-package.R @@ -12,5 +12,6 @@ #' @importFrom paws s3 iam costexplorer #' @importFrom s3fs s3_file_system #' @importFrom glue glue +#' @importFrom jsonlite toJSON fromJSON ## usethis namespace: end NULL diff --git a/_pkgdown.yml b/_pkgdown.yml index 247d366..f80b48b 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -39,3 +39,6 @@ reference: - title: IAM contents: - starts_with("aws_iam") + - title: Secrets manager + contents: + - starts_with("aws_secret") diff --git a/man/aws_secrets_all.Rd b/man/aws_secrets_all.Rd new file mode 100644 index 0000000..59955c7 --- /dev/null +++ b/man/aws_secrets_all.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/secrets_manager.R +\name{aws_secrets_all} +\alias{aws_secrets_all} +\title{Get all secret values} +\usage{ +aws_secrets_all() +} +\value{ +(list) list with secrets +} +\description{ +Get all secret values +} +\examples{ +\dontrun{ +aws_secrets_list() +} +} diff --git a/man/aws_secrets_create.Rd b/man/aws_secrets_create.Rd new file mode 100644 index 0000000..cc79151 --- /dev/null +++ b/man/aws_secrets_create.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/secrets_manager.R +\name{aws_secrets_create} +\alias{aws_secrets_create} +\title{Create a secret} +\usage{ +aws_secrets_create(name, secret, description = NULL, ...) +} +\arguments{ +\item{name}{(character) The name of the new secret. required} + +\item{secret}{(character/raw) The text or raw data to encrypt and store +in this new version of the secret. AWS recommends for text to use a JSON +structure of key/value pairs for your secret value (see examples below). +required} + +\item{description}{(character) The description of the secret. optional} + +\item{...}{further named parameters passed on to \code{create_secret} +\url{https://www.paws-r-sdk.com/docs/secretsmanager_create_secret/}} +} +\value{ +(list) with fields: +\itemize{ +\item ARN +\item Name +\item VersionId +\item ReplicationStatus +} +} +\description{ +This function does not create your database username and/or password. +Instead, it creates a "secret", which is typically a combination +of credentials (username + password + other metadata) +} +\details{ +Note that we autogenerate a random UUID to pass to the +\code{ClientRequestToken} parameter of the \code{paws} function \code{create_secret} +used internally in this function. + +This function creates a new secret. See \code{\link[=aws_secrets_update]{aws_secrets_update()}} to +update an existing secret. This function fails if you call it with +an existing secret with the same name or ARN +} +\examples{ +\dontrun{ +# Text secret +x <- aws_secrets_create( + name = "MyTestDatabaseSecret", + secret = '{"username":"david","password":"EXAMPLE-PASSWORD"}', + description = "My test database secret as a string" +) + +# Raw secret +x <- aws_secrets_create( + name = "MyRawDatabaseSecret", + secret = charToRaw('{"username":"david","password":"EXAMPLE-PASSWORD"}'), + description = "My test database secret as raw" +) +} +} diff --git a/man/aws_secrets_delete.Rd b/man/aws_secrets_delete.Rd new file mode 100644 index 0000000..f831bcf --- /dev/null +++ b/man/aws_secrets_delete.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/secrets_manager.R +\name{aws_secrets_delete} +\alias{aws_secrets_delete} +\title{Delete a secret} +\usage{ +aws_secrets_delete(id, ...) +} +\arguments{ +\item{id}{(character) The name or ARN of the secret. required} + +\item{...}{further named parameters passed on to \code{delete_secret} +\url{https://www.paws-r-sdk.com/docs/secretsmanager_delete_secret/}} +} +\value{ +(list) with fields: +\itemize{ +\item ARN +\item Name +\item DeletionDate +} +} +\description{ +Delete a secret +} +\examples{ +\dontrun{ +# Does exist +aws_secrets_delete(id = "MyTestDatabaseSecret") + +# Does not exist +# aws_secrets_get(id = "DoesntExist") +#> Error: ResourceNotFoundException (HTTP 400). Secrets Manager +#> can't find the specified secret. +} +} diff --git a/man/aws_secrets_get.Rd b/man/aws_secrets_get.Rd new file mode 100644 index 0000000..a9a341f --- /dev/null +++ b/man/aws_secrets_get.Rd @@ -0,0 +1,40 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/secrets_manager.R +\name{aws_secrets_get} +\alias{aws_secrets_get} +\title{Get a secret} +\usage{ +aws_secrets_get(id, ...) +} +\arguments{ +\item{id}{(character) The name or ARN of the secret. required} + +\item{...}{further named parameters passed on to \code{get_secret_value} +\url{https://www.paws-r-sdk.com/docs/secretsmanager_get_secret_value/}} +} +\value{ +(list) with fields: +\itemize{ +\item ARN +\item Name +\item VersionId +\item SecretBinary +\item SecretString +\item VersionStages +\item CreatedDate +} +} +\description{ +Get a secret +} +\examples{ +\dontrun{ +# Does exist +aws_secrets_get(id = "MyTestDatabaseSecret") + +# Does not exist +# aws_secrets_get(id = "DoesntExist") +#> Error: ResourceNotFoundException (HTTP 400). Secrets Manager +#> can't find the specified secret. +} +} diff --git a/man/aws_secrets_list.Rd b/man/aws_secrets_list.Rd new file mode 100644 index 0000000..82cb584 --- /dev/null +++ b/man/aws_secrets_list.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/secrets_manager.R +\name{aws_secrets_list} +\alias{aws_secrets_list} +\title{List secrets} +\usage{ +aws_secrets_list(...) +} +\arguments{ +\item{...}{parameters passed on to the \code{paws} method} +} +\value{ +(list) list with secrets +} +\description{ +List secrets +} +\note{ +see \url{https://www.paws-r-sdk.com/docs/secretsmanager_list_secrets/} +for available parameters +} +\examples{ +\dontrun{ +aws_secrets_list() +} +} diff --git a/man/aws_secrets_pwd.Rd b/man/aws_secrets_pwd.Rd new file mode 100644 index 0000000..ad11e8c --- /dev/null +++ b/man/aws_secrets_pwd.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/secrets_manager.R +\name{aws_secrets_pwd} +\alias{aws_secrets_pwd} +\title{Get a random password} +\usage{ +aws_secrets_pwd(...) +} +\arguments{ +\item{...}{named parameters passed on to \code{get_random_password} +\url{https://www.paws-r-sdk.com/docs/secretsmanager_get_random_password/}} +} +\description{ +Get a random password +} +\examples{ +\dontrun{ +aws_secrets_pwd() +aws_secrets_pwd(ExcludeNumbers = TRUE) +} +} diff --git a/man/aws_secrets_rotate.Rd b/man/aws_secrets_rotate.Rd new file mode 100644 index 0000000..898d55c --- /dev/null +++ b/man/aws_secrets_rotate.Rd @@ -0,0 +1,46 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/secrets_manager.R +\name{aws_secrets_rotate} +\alias{aws_secrets_rotate} +\title{Rotate a secret} +\usage{ +aws_secrets_rotate(id, lambda_arn = NULL, rules = NULL, immediately = TRUE) +} +\arguments{ +\item{id}{(character) The name or ARN of the secret. required} + +\item{lambda_arn}{(character) The ARN of the Lambda rotation function. +Only supply for secrets that use a Lambda rotation function to rotate} + +\item{rules}{(list) asdfadf} + +\item{immediately}{(logical) whether to rotate the secret immediately or not. +default: \code{TRUE}} +} +\value{ +(list) with fields: +\itemize{ +\item ARN +\item Name +\item VersionId +} +} +\description{ +Rotate a secret +} +\details{ +Note that we autogenerate a random UUID to pass to the +\code{ClientRequestToken} parameter of the \code{paws} function used internally +} +\examples{ +\dontrun{ +aws_secrets_rotate(id = "MyTestDatabaseSecret") +aws_secrets_rotate(id = "MyTestDatabaseSecret", rules = list( + Duration = "2h", + ScheduleExpression = "cron(0 16 1,15 * ? *)" +)) +} +} +\references{ +\url{https://www.paws-r-sdk.com/docs/secretsmanager_rotate_secret/} +} diff --git a/man/aws_secrets_update.Rd b/man/aws_secrets_update.Rd new file mode 100644 index 0000000..587f26f --- /dev/null +++ b/man/aws_secrets_update.Rd @@ -0,0 +1,55 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/secrets_manager.R +\name{aws_secrets_update} +\alias{aws_secrets_update} +\title{Update a secret} +\usage{ +aws_secrets_update(id, secret, ...) +} +\arguments{ +\item{id}{(character) The name or ARN of the secret. required} + +\item{secret}{(character/raw) The text or raw data to encrypt and store +in this new version of the secret. AWS recommends for text to use a JSON +structure of key/value pairs for your secret value (see examples below). +required} + +\item{...}{further named parameters passed on to \code{put_secret_value} +\url{https://www.paws-r-sdk.com/docs/secretsmanager_put_secret_value/}} +} +\value{ +(list) with fields: +\itemize{ +\item ARN +\item Name +\item VersionId +\item VersionStages +} +} +\description{ +Update a secret +} +\details{ +Note that we autogenerate a random UUID to pass to the +\code{ClientRequestToken} parameter of the \code{paws} function used internally +} +\examples{ +\dontrun{ +# Create a secret +aws_secrets_create( + name = "TheSecret", + secret = '{"username":"jane","password":"cat"}', + description = "A string" +) + +aws_secrets_get("TheSecret") + +# Update the secret +aws_secrets_update( + id = "TheSecret", + secret = '{"username":"jane","password":"kitten"}' +) + +aws_secrets_get("TheSecret") +} +} diff --git a/man/construct_db_secret.Rd b/man/construct_db_secret.Rd new file mode 100644 index 0000000..2dd942e --- /dev/null +++ b/man/construct_db_secret.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/secrets_manager.R +\name{construct_db_secret} +\alias{construct_db_secret} +\title{Construct a database secret string or raw version of it} +\usage{ +construct_db_secret( + engine, + host = "", + username = "", + password = "", + dbname = "", + port = "", + as = "string" +) +} +\arguments{ +\item{engine, host, username, password, dbname, port}{supply parameters to +go into either a json string or raw version of the json string} + +\item{as}{(character) one of "string" or "raw"} +} +\description{ +Construct a database secret string or raw version of it +} +\examples{ +\dontrun{ +construct_db_secret("redshift", dbname = "hello", port = 5439) +construct_db_secret("mariadb", dbname = "world", port = 3306) +construct_db_secret("postgresql", dbname = "bears", port = 5432, as = "raw") +} +} +\references{ +\url{https://docs.aws.amazon.com/secretsmanager/latest/userguide/reference_secret_json_structure.html} # nolint +} +\keyword{internal} diff --git a/tests/testthat/helper-vcr.R b/tests/testthat/helper-vcr.R index 17b12c0..86f74b9 100644 --- a/tests/testthat/helper-vcr.R +++ b/tests/testthat/helper-vcr.R @@ -14,3 +14,12 @@ invisible(vcr::vcr_configure( ) )) vcr::check_cassette_names() + +purge_secrets <- function() { + x <- aws_secrets_list() + if (length(x$SecretList) > 0) { + x$SecretList %>% + purrr::map_vec(purrr::pluck, "Name") %>% + purrr::map(aws_secrets_delete) + } +} diff --git a/tests/testthat/test-secrets_manager.R b/tests/testthat/test-secrets_manager.R new file mode 100644 index 0000000..686d64a --- /dev/null +++ b/tests/testthat/test-secrets_manager.R @@ -0,0 +1,84 @@ +skip_on_ci() + +test_that("aws_secrets_list", { + vcr::use_cassette("aws_secrets_list", { + purge_secrets() + res <- aws_secrets_list() + }) + + expect_type(res, "list") + expect_named(res, c("SecretList", "NextToken")) + expect_equal(length(res$SecretList), 0) +}) + +# Sys.sleep(5) # sleep to allow purge_secrets to finish aws side of deletion # nolint + +test_that("aws_secrets_create", { + secret_name <- "Testing6789" + + vcr::use_cassette("aws_secrets_create", { + x <- aws_secrets_create( + name = secret_name, + secret = '{"username":"bear","password":"apple"}', + description = "A note about the secret" + ) + }) + + expect_type(x, "list") + expect_named(x, c("ARN", "Name", "VersionId", "ReplicationStatus")) + expect_equal(x$Name, secret_name) + expect_match(x$ARN, "arn:aws") + expect_type(x$VersionId, "character") + + # cleanup + aws_secrets_delete(secret_name, ForceDeleteWithoutRecovery = TRUE) +}) + + +test_that("aws_secrets_get", { + vcr::use_cassette("aws_secrets_get_by_name", { + secret_name_thename <- "TestingTheThing1" + the_secret <- '{"username":"bear","password":"apple"}' + aws_secrets_create( + name = secret_name_thename, + secret = the_secret, + description = "A note about the secret" + ) + + res <- aws_secrets_get(secret_name_thename) + }) + + expect_type(res, "list") + expect_named(res, c( + "ARN", "Name", "VersionId", "SecretBinary", + "SecretString", "VersionStages", "CreatedDate" + )) + expect_equal(res$Name, secret_name_thename) + expect_match(res$ARN, "arn:aws") + expect_equal(res$SecretString, the_secret) + + vcr::use_cassette("aws_secrets_get_by_arn", { + secret_name_arn <- "TestingAnotherThing1" + the_secret <- '{"username":"deer","password":"bananas"}' + x <- aws_secrets_create( + name = secret_name_arn, + secret = the_secret, + description = "A quick note about the secret" + ) + + res <- aws_secrets_get(x$ARN) + }) + + expect_type(res, "list") + expect_named(res, c( + "ARN", "Name", "VersionId", "SecretBinary", + "SecretString", "VersionStages", "CreatedDate" + )) + expect_equal(res$Name, secret_name_arn) + expect_match(res$ARN, "arn:aws") + expect_equal(res$SecretString, the_secret) + + # cleanup + aws_secrets_delete(secret_name_thename, ForceDeleteWithoutRecovery = TRUE) + aws_secrets_delete(secret_name_arn, ForceDeleteWithoutRecovery = TRUE) +}) From f1836a018f0cbc961b0f2984b72bc719455b3a86 Mon Sep 17 00:00:00 2001 From: Scott Chamberlain Date: Thu, 7 Mar 2024 06:41:33 -0800 Subject: [PATCH 2/7] add vpcs methods - required for some secrets manager work --- NAMESPACE | 3 +++ R/ec2.R | 10 ++++++++++ R/vpcs.R | 36 ++++++++++++++++++++++++++++++++++++ _pkgdown.yml | 4 ++++ man/aws_ec2_client.Rd | 19 +++++++++++++++++++ man/aws_vpc.Rd | 37 +++++++++++++++++++++++++++++++++++++ man/aws_vpcs.Rd | 23 +++++++++++++++++++++++ 7 files changed, 132 insertions(+) create mode 100644 R/ec2.R create mode 100644 R/vpcs.R create mode 100644 man/aws_ec2_client.Rd create mode 100644 man/aws_vpc.Rd create mode 100644 man/aws_vpcs.Rd diff --git a/NAMESPACE b/NAMESPACE index 23dc521..f42f68e 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -17,6 +17,7 @@ export(aws_db_rds_create) export(aws_db_redshift_client) export(aws_db_redshift_con) export(aws_db_redshift_create) +export(aws_ec2_client) export(aws_file_attr) export(aws_file_copy) export(aws_file_delete) @@ -55,6 +56,8 @@ export(aws_user_current) export(aws_user_delete) export(aws_user_exists) export(aws_users) +export(aws_vpc) +export(aws_vpcs) export(billing) export(s3_path) export(set_s3_interface) diff --git a/R/ec2.R b/R/ec2.R new file mode 100644 index 0000000..a0ffd0b --- /dev/null +++ b/R/ec2.R @@ -0,0 +1,10 @@ +#' Get the `paws` EC2 client - primarily for usage of VPC security groups +#' @export +#' @note returns existing client if found; a new client otherwise +#' @family security groups +#' @return a list with methods for interfacing with EC2; +#' see +aws_ec2_client <- function() { + if (is.null(env64$ec2)) env64$ec2 <- paws::ec2() + return(env64$ec2) +} diff --git a/R/vpcs.R b/R/vpcs.R new file mode 100644 index 0000000..624ad97 --- /dev/null +++ b/R/vpcs.R @@ -0,0 +1,36 @@ +#' List VPCs +#' @export +#' @param ... parameters passed on to [describe_vpcs]( +#' https://www.paws-r-sdk.com/docs/ec2_describe_vpcs/) +#' @return (list) list with VPCs +#' @examplesIf interactive() +#' aws_vpcs() +#' aws_vpcs(MaxResults = 6) +aws_vpcs <- function(...) { + aws_ec2_client() + env64$ec2$describe_vpcs(...) +} + +#' Get a VPC by id +#' @export +#' @param id (character) The id of the VPC. required +#' @inheritParams aws_vpcs +#' @return (list) with fields: +#' - Vpcs (list) each VPC group +#' - NextToken (character) token for paginating +#' +#' Each element of Vpcs is a list with slots: +#' - CidrBlock +#' - DhcpOptionsId +#' - State +#' - VpcId +#' - OwnerId +#' - InstanceTenancy +#' - Ipv6CidrBlockAssociationSet +#' - CidrBlockAssociationSet +#' - IsDefault +#' - Tags +aws_vpc <- function(id, ...) { + aws_ec2_client() + aws_vpcs(VpcIds = id, ...) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index f80b48b..9fcee32 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -42,3 +42,7 @@ reference: - title: Secrets manager contents: - starts_with("aws_secret") + - title: VPCs + contents: + - aws_vpc + - aws_vpcs diff --git a/man/aws_ec2_client.Rd b/man/aws_ec2_client.Rd new file mode 100644 index 0000000..8121a89 --- /dev/null +++ b/man/aws_ec2_client.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/ec2.R +\name{aws_ec2_client} +\alias{aws_ec2_client} +\title{Get the \code{paws} EC2 client - primarily for usage of VPC security groups} +\usage{ +aws_ec2_client() +} +\value{ +a list with methods for interfacing with EC2; +see \url{https://www.paws-r-sdk.com/docs/ec2/} +} +\description{ +Get the \code{paws} EC2 client - primarily for usage of VPC security groups +} +\note{ +returns existing client if found; a new client otherwise +} +\concept{security groups} diff --git a/man/aws_vpc.Rd b/man/aws_vpc.Rd new file mode 100644 index 0000000..51c29ac --- /dev/null +++ b/man/aws_vpc.Rd @@ -0,0 +1,37 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vpcs.R +\name{aws_vpc} +\alias{aws_vpc} +\title{Get a VPC by id} +\usage{ +aws_vpc(id, ...) +} +\arguments{ +\item{id}{(character) The id of the VPC. required} + +\item{...}{parameters passed on to \href{https://www.paws-r-sdk.com/docs/ec2_describe_vpcs/}{describe_vpcs}} +} +\value{ +(list) with fields: +\itemize{ +\item Vpcs (list) each VPC group +\item NextToken (character) token for paginating +} + +Each element of Vpcs is a list with slots: +\itemize{ +\item CidrBlock +\item DhcpOptionsId +\item State +\item VpcId +\item OwnerId +\item InstanceTenancy +\item Ipv6CidrBlockAssociationSet +\item CidrBlockAssociationSet +\item IsDefault +\item Tags +} +} +\description{ +Get a VPC by id +} diff --git a/man/aws_vpcs.Rd b/man/aws_vpcs.Rd new file mode 100644 index 0000000..567da13 --- /dev/null +++ b/man/aws_vpcs.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vpcs.R +\name{aws_vpcs} +\alias{aws_vpcs} +\title{List VPCs} +\usage{ +aws_vpcs(...) +} +\arguments{ +\item{...}{parameters passed on to \href{https://www.paws-r-sdk.com/docs/ec2_describe_vpcs/}{describe_vpcs}} +} +\value{ +(list) list with VPCs +} +\description{ +List VPCs +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +aws_vpcs() +aws_vpcs(MaxResults = 6) +\dontshow{\}) # examplesIf} +} From 0ca8b7f67049b6113ab955ae53592752e4953d3a Mon Sep 17 00:00:00 2001 From: Scott Chamberlain Date: Thu, 7 Mar 2024 09:15:48 -0800 Subject: [PATCH 3/7] add vpc security groups methods --- DESCRIPTION | 3 +- NAMESPACE | 9 + R/globals.R | 1 + R/sixtyfour-package.R | 1 + R/vpc_security_groups.R | 341 +++++++++++++++++++++ _pkgdown.yml | 7 + man/aws_ec2_client.Rd | 8 + man/aws_vpc_security_group.Rd | 42 +++ man/aws_vpc_security_group_create.Rd | 63 ++++ man/aws_vpc_security_group_ingress.Rd | 52 ++++ man/aws_vpc_security_group_modify_rules.Rd | 44 +++ man/aws_vpc_security_groups.Rd | 32 ++ man/ip_address.Rd | 15 + man/ip_permissions_generator.Rd | 23 ++ 14 files changed, 640 insertions(+), 1 deletion(-) create mode 100644 R/vpc_security_groups.R create mode 100644 man/aws_vpc_security_group.Rd create mode 100644 man/aws_vpc_security_group_create.Rd create mode 100644 man/aws_vpc_security_group_ingress.Rd create mode 100644 man/aws_vpc_security_group_modify_rules.Rd create mode 100644 man/aws_vpc_security_groups.Rd create mode 100644 man/ip_address.Rd create mode 100644 man/ip_permissions_generator.Rd diff --git a/DESCRIPTION b/DESCRIPTION index 951b467..2dcd736 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -30,7 +30,8 @@ Imports: glue, memoise, uuid, - jsonlite + jsonlite, + curl Suggests: knitr, rmarkdown, diff --git a/NAMESPACE b/NAMESPACE index f42f68e..5539e3f 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -57,14 +57,21 @@ export(aws_user_delete) export(aws_user_exists) export(aws_users) export(aws_vpc) +export(aws_vpc_security_group) +export(aws_vpc_security_group_create) +export(aws_vpc_security_group_ingress) +export(aws_vpc_security_group_modify_rules) +export(aws_vpc_security_groups) export(aws_vpcs) export(billing) +export(ip_permissions_generator) export(s3_path) export(set_s3_interface) importFrom(cli,cli_inform) importFrom(cli,cli_progress_bar) importFrom(cli,cli_progress_update) importFrom(cli,pb_spin) +importFrom(curl,curl_fetch_memory) importFrom(dplyr,bind_rows) importFrom(dplyr,filter) importFrom(dplyr,last_col) @@ -87,6 +94,8 @@ importFrom(purrr,flatten) importFrom(purrr,list_rbind) importFrom(purrr,map) importFrom(purrr,map_chr) +importFrom(purrr,map_lgl) +importFrom(purrr,pluck) importFrom(rlang,":=") importFrom(rlang,has_name) importFrom(s3fs,s3_dir_info) diff --git a/R/globals.R b/R/globals.R index 9242c1c..541bc39 100644 --- a/R/globals.R +++ b/R/globals.R @@ -13,5 +13,6 @@ utils::globalVariables(c( "secret_str", # "PasswordLastUsed", # "CreateDate", # + "IpPermissions", # NULL )) diff --git a/R/sixtyfour-package.R b/R/sixtyfour-package.R index dd4e34f..7ca72d1 100644 --- a/R/sixtyfour-package.R +++ b/R/sixtyfour-package.R @@ -13,5 +13,6 @@ #' @importFrom s3fs s3_file_system #' @importFrom glue glue #' @importFrom jsonlite toJSON fromJSON +#' @importFrom curl curl_fetch_memory ## usethis namespace: end NULL diff --git a/R/vpc_security_groups.R b/R/vpc_security_groups.R new file mode 100644 index 0000000..88f7a6c --- /dev/null +++ b/R/vpc_security_groups.R @@ -0,0 +1,341 @@ +create_security_group <- function(engine) { + sg <- aws_vpc_security_group_create( + name = glue("{engine}-{paste0(sample(1:9, size = 4), collapse = '')}"), + engine = engine + ) + aws_vpc_security_group_ingress( + id = sg$GroupId, + ip_permissions = ip_permissions_generator(engine) + ) + sg$GroupId +} + +picker <- function(msg, choices, .envir = parent.frame()) { + cli::cli_inform(msg, .envir = .envir) + utils::menu(choices) +} + +# FIXME: clean this mess up! +#' @importFrom purrr map_lgl pluck +#' @autoglobal +#' @keywords internal +security_group_handler <- function(ids, engine) { + if (!is.null(ids)) { + return(ids) + } + port <- engine2port(engine) + ip <- ip_address() + sgs <- aws_vpc_security_groups() + sgsdf <- jsonlite::fromJSON( + jsonlite::toJSON(sgs$SecurityGroups, auto_unbox = TRUE) + ) + + port_df <- dplyr::filter( + sgsdf, + map_lgl(IpPermissions, ~ .$ToPort == port) + ) + if (!NROW(port_df)) { + cli::cli_alert_danger(c( + "No security groups with access for ", + "{.strong {engine}} and port {.strong {port}}" + )) + cli::cli_alert_info(c( + "Creating security group with access for ", + "{.strong {engine}} and port {.strong {port}}" + )) + trysg <- tryCatch(create_security_group(engine), error = function(e) e) + if (rlang::is_error(trysg)) { + cli::cli_alert_danger(c( + "An error occurred while creating the security group; ", + "please use paramater {.strong security_group_ids}" + )) + return(NULL) + } else { + cli::cli_alert_success("Using security group {.strong {trysg}}") + return(trysg) + } + } + + ip_df <- dplyr::filter( + port_df, + map_lgl(IpPermissions, ~ any(grepl(ip, pluck(.$IpRanges, 1, "CidrIp")))) + ) + if (!NROW(ip_df)) { + cli::cli_alert_danger(c( + "Found security groups w/ access for {.strong {engine}}, ", + "{.emph but} not with your IP address {.strong {ip}}" + )) + cli::cli_alert_info("Which security group do you want to modify?") + pick_sg_options <- + port_df %>% + glue::glue_data( + "Security Group: {GroupId}\n", + " Group Name: {GroupName}\n", + " Description: {Description}", + .trim = FALSE + ) %>% + as.character() + + picked <- picker(c( + glue("We found {length(pick_sg_options)} security groups"), + "Which security group do you want to use?" + ), pick_sg_options) + + if (picked == 0) { + cli::cli_alert_danger( + "No security group selected; please use paramater {.strong security_group_ids}" + ) + return(NULL) + } else { + picked_id <- port_df[picked, "GroupId"] + } + cli::cli_alert_info( + "Adding your IP address {.strong {ip}} to security group {.strong {picked_id}}" + ) + try_ingress <- tryCatch( + { + aws_vpc_security_group_ingress( + id = picked_id, + ip_permissions = ip_permissions_generator(engine) + ) + }, + error = function(e) e + ) + if (rlang::is_error(try_ingress)) { + cli::cli_alert_danger(c( + "An error occurred while creating the security group; ", + "please use paramater {.strong security_group_ids}" + )) + return(NULL) + } else { + cli::cli_alert_success("Using security group {.strong {picked_id}}") + return(picked_id) + } + } + + if (NROW(ip_df) == 1) { + cli::cli_alert_success(c( + "Found security group {.strong {ip_df$GroupId}} ", + "w/ access for {.strong {engine}} and your IP address {.strong {ip}}" + )) + return(ip_df$GroupId) + } else { + sgoptions <- + ip_df %>% + glue::glue_data( + "Security Group: {GroupId}\n", + " Group Name: {GroupName}\n", + " Description: {Description}", + .trim = FALSE + ) %>% + as.character() + + picked <- picker(c( + glue("We found {length(sgoptions)} matching security groups"), + "Which security group do you want to use?" + ), sgoptions) + + if (picked == 0) { + cli::cli_alert_danger(c( + "Found security group {.strong {ip_df$GroupId}} ", + "w/ access for {.strong {engine}}, {.emph but} not with your IP address {.strong {ip}}" + )) + return(NULL) + } else { + idtouse <- ip_df[picked, "GroupId"] + cli::cli_alert_success("Using security group {.strong {idtouse}}") + return(idtouse) + } + } +} + + +#' List VPC security groups +#' @export +#' @param ... named parameters passed on to [describe_security_groups]( +#' https://www.paws-r-sdk.com/docs/ec2_describe_security_groups/) +#' @return (list) list with security groups +#' @family security groups +#' @examplesIf interactive() +#' aws_vpc_security_groups() +#' aws_vpc_security_groups(MaxResults = 6) +aws_vpc_security_groups <- function(...) { + aws_ec2_client() + env64$ec2$describe_security_groups(...) +} + +#' Get a security group by ID +#' @export +#' @param id (character) The id of the security group. required +#' @inheritParams aws_vpc_security_groups +#' @family security groups +#' @return (list) with fields: +#' - SecurityGroups (list) each security group +#' - Description +#' - GroupName +#' - IpPermissions +#' - OwnerId +#' - GroupId +#' - IpPermissionsEgress +#' - Tags +#' - VpcId +#' - NextToken (character) token for paginating +aws_vpc_security_group <- function(id, ...) { + aws_ec2_client() + aws_vpc_security_groups(GroupIds = id, ...) +} + +#' Create a security group +#' @export +#' @param name (character) The name of the new secret. required +#' @param engine (character) The engine to use. default: "mariadb". required. +#' one of: mariadb, mysql, or postgres +#' @param description (character) The description of the secret. optional +#' @param vpc_id (character) a VPC id. optional. if not supplied your default +#' VPC is used. To get your VPCs, see [aws_vpcs()] +#' @param tags (character) The tags to assign to the security group. optional +#' @param ... named parameters passed on to [create_secret]( +#' https://www.paws-r-sdk.com/docs/secretsmanager_create_secret/) +#' @return (list) with fields: +#' - GroupId (character) +#' - Tags (list) +#' @family security groups +#' @examples \dontrun{ +#' # create security group +#' x <- aws_vpc_security_group_create( +#' name = "testing1", +#' description = "Testing security group creation" +#' ) +#' # add ingress +#' aws_vpc_security_group_ingress( +#' id = x$GroupId, +#' ip_permissions = ip_permissions_generator("mariadb") +#' ) +#' } +aws_vpc_security_group_create <- function( + name, engine, description = NULL, + vpc_id = NULL, tags = NULL, ...) { + aws_ec2_client() + if (is.null(description)) { + description <- glue("Access to {engine}") + } + env64$ec2$create_security_group( + Description = description, + GroupName = name, + VpcId = vpc_id, + TagSpecifications = tags, + ... + ) +} + +engine2port <- function(engine) { + switch(engine, + mariadb = 3306L, + mysql = 3306L, + postgres = 5432L, + stop(glue::glue("{engine} not currently supported")) + ) +} + +#' Ip Permissions generator +#' +#' @export +#' @param engine (character) one of mariadb, mysql, or postgres +#' @param port (character) port number. port determined from `engine` +#' if `port` not given. default: `NULL` +#' @param description (character) description. if not given, autogenerated +#' depending on value of `engine` +#' @return a list with slots: FromPort, ToPort, IpProtocol, and IpRanges +ip_permissions_generator <- function(engine, port = NULL, description = NULL) { + protocol <- "tcp" + port <- engine2port(engine) + if (is.null(description)) { + description <- glue("Access for {Sys.info()[['user']]} from sixtyfour") + } + list( + FromPort = port, + ToPort = port, + IpProtocol = protocol, + IpRanges = list( + list( + CidrIp = glue("{ip_address()}/32"), + Description = description + ) + ) + ) +} + +#' Get your IP address using +#' @return (character) ip address +#' @keywords internal +ip_address <- function() { + res <- curl::curl_fetch_memory("https://ifconfig.me/ip") + rawToChar(res$content) +} + +#' Authorize Security Group Ingress +#' @export +#' @param id (character) security group id +#' @param ip_permissions (list) list of persmissions. see link to `paws` +#' docs below or use [ip_permissions_generator()] to generate the +#' list for this parameter +#' @param ... named parameters passed on to +#' [authorize_security_group_ingress]( +#' https://www.paws-r-sdk.com/docs/ec2_authorize_security_group_ingress/) +#' @family security groups +#' @return list with slots: +#' - Return (boolean) +#' - SecurityGroupRules (list) +#' - SecurityGroupRuleId +#' - GroupId +#' - GroupOwnerId +#' - IsEgress +#' - IpProtocol +#' - FromPort +#' - ToPort +#' - CidrIpv4 +#' - CidrIpv6 +#' - PrefixListId +#' - ReferencedGroupInfo +#' - Description +#' - Tags +aws_vpc_security_group_ingress <- function( + id = NULL, + ip_permissions = NULL, ...) { + aws_ec2_client() + env64$ec2$authorize_security_group_ingress( + GroupId = id, + IpPermissions = list(ip_permissions), + ... + ) +} + +#' Modify security group rules +#' @export +#' @param id (character) security group id +#' @param rules list of rules to add/modify on the security group `id` +#' @param ... named parameters passed on to [modify_security_group_rules]( +#' https://www.paws-r-sdk.com/docs/ec2_modify_security_group_rules/) +#' @family security groups +#' @examplesIf interactive() +#' aws_vpc_security_group_modify_rules( +#' id = "someid", +#' rules = list( +#' SecurityGroupRuleId = "sgr-07de36a0521f39c8b", +#' SecurityGroupRule = list( +#' IpProtocol = "tcp", +#' FromPort = 22, +#' ToPort = 22, +#' CidrIpv4 = "3.3.3.3/32", +#' Description = "added ssh port" +#' ) +#' ) +#' ) +aws_vpc_security_group_modify_rules <- function(id, rules, ...) { + aws_ec2_client() + env64$ec2$modify_security_group_rules( + GroupId = id, + SecurityGroupRules = list(rules), + ... + ) +} diff --git a/_pkgdown.yml b/_pkgdown.yml index 9fcee32..ee6ed26 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -46,3 +46,10 @@ reference: contents: - aws_vpc - aws_vpcs + - title: VPC Security Groups + contents: + - starts_with("aws_vpc_security_group") + - ip_permissions_generator + - title: EC2 + contents: + - aws_ec2_client diff --git a/man/aws_ec2_client.Rd b/man/aws_ec2_client.Rd index 8121a89..551edd5 100644 --- a/man/aws_ec2_client.Rd +++ b/man/aws_ec2_client.Rd @@ -16,4 +16,12 @@ Get the \code{paws} EC2 client - primarily for usage of VPC security groups \note{ returns existing client if found; a new client otherwise } +\seealso{ +Other security groups: +\code{\link{aws_vpc_security_group_create}()}, +\code{\link{aws_vpc_security_group_ingress}()}, +\code{\link{aws_vpc_security_group_modify_rules}()}, +\code{\link{aws_vpc_security_groups}()}, +\code{\link{aws_vpc_security_group}()} +} \concept{security groups} diff --git a/man/aws_vpc_security_group.Rd b/man/aws_vpc_security_group.Rd new file mode 100644 index 0000000..adf2fc2 --- /dev/null +++ b/man/aws_vpc_security_group.Rd @@ -0,0 +1,42 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vpc_security_groups.R +\name{aws_vpc_security_group} +\alias{aws_vpc_security_group} +\title{Get a security group by ID} +\usage{ +aws_vpc_security_group(id, ...) +} +\arguments{ +\item{id}{(character) The id of the security group. required} + +\item{...}{named parameters passed on to \href{https://www.paws-r-sdk.com/docs/ec2_describe_security_groups/}{describe_security_groups}} +} +\value{ +(list) with fields: +\itemize{ +\item SecurityGroups (list) each security group +\itemize{ +\item Description +\item GroupName +\item IpPermissions +\item OwnerId +\item GroupId +\item IpPermissionsEgress +\item Tags +\item VpcId +} +\item NextToken (character) token for paginating +} +} +\description{ +Get a security group by ID +} +\seealso{ +Other security groups: +\code{\link{aws_ec2_client}()}, +\code{\link{aws_vpc_security_group_create}()}, +\code{\link{aws_vpc_security_group_ingress}()}, +\code{\link{aws_vpc_security_group_modify_rules}()}, +\code{\link{aws_vpc_security_groups}()} +} +\concept{security groups} diff --git a/man/aws_vpc_security_group_create.Rd b/man/aws_vpc_security_group_create.Rd new file mode 100644 index 0000000..22f0a00 --- /dev/null +++ b/man/aws_vpc_security_group_create.Rd @@ -0,0 +1,63 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vpc_security_groups.R +\name{aws_vpc_security_group_create} +\alias{aws_vpc_security_group_create} +\title{Create a security group} +\usage{ +aws_vpc_security_group_create( + name, + engine, + description = NULL, + vpc_id = NULL, + tags = NULL, + ... +) +} +\arguments{ +\item{name}{(character) The name of the new secret. required} + +\item{engine}{(character) The engine to use. default: "mariadb". required. +one of: mariadb, mysql, or postgres} + +\item{description}{(character) The description of the secret. optional} + +\item{vpc_id}{(character) a VPC id. optional. if not supplied your default +VPC is used. To get your VPCs, see \code{\link[=aws_vpcs]{aws_vpcs()}}} + +\item{tags}{(character) The tags to assign to the security group. optional} + +\item{...}{named parameters passed on to \href{https://www.paws-r-sdk.com/docs/secretsmanager_create_secret/}{create_secret}} +} +\value{ +(list) with fields: +\itemize{ +\item GroupId (character) +\item Tags (list) +} +} +\description{ +Create a security group +} +\examples{ +\dontrun{ +# create security group +x <- aws_vpc_security_group_create( + name = "testing1", + description = "Testing security group creation" +) +# add ingress +aws_vpc_security_group_ingress( + id = x$GroupId, + ip_permissions = ip_permissions_generator("mariadb") +) +} +} +\seealso{ +Other security groups: +\code{\link{aws_ec2_client}()}, +\code{\link{aws_vpc_security_group_ingress}()}, +\code{\link{aws_vpc_security_group_modify_rules}()}, +\code{\link{aws_vpc_security_groups}()}, +\code{\link{aws_vpc_security_group}()} +} +\concept{security groups} diff --git a/man/aws_vpc_security_group_ingress.Rd b/man/aws_vpc_security_group_ingress.Rd new file mode 100644 index 0000000..df5c51c --- /dev/null +++ b/man/aws_vpc_security_group_ingress.Rd @@ -0,0 +1,52 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vpc_security_groups.R +\name{aws_vpc_security_group_ingress} +\alias{aws_vpc_security_group_ingress} +\title{Authorize Security Group Ingress} +\usage{ +aws_vpc_security_group_ingress(id = NULL, ip_permissions = NULL, ...) +} +\arguments{ +\item{id}{(character) security group id} + +\item{ip_permissions}{(list) list of persmissions. see link to \code{paws} +docs below or use \code{\link[=ip_permissions_generator]{ip_permissions_generator()}} to generate the +list for this parameter} + +\item{...}{named parameters passed on to +\href{https://www.paws-r-sdk.com/docs/ec2_authorize_security_group_ingress/}{authorize_security_group_ingress}} +} +\value{ +list with slots: +\itemize{ +\item Return (boolean) +\item SecurityGroupRules (list) +\itemize{ +\item SecurityGroupRuleId +\item GroupId +\item GroupOwnerId +\item IsEgress +\item IpProtocol +\item FromPort +\item ToPort +\item CidrIpv4 +\item CidrIpv6 +\item PrefixListId +\item ReferencedGroupInfo +\item Description +\item Tags +} +} +} +\description{ +Authorize Security Group Ingress +} +\seealso{ +Other security groups: +\code{\link{aws_ec2_client}()}, +\code{\link{aws_vpc_security_group_create}()}, +\code{\link{aws_vpc_security_group_modify_rules}()}, +\code{\link{aws_vpc_security_groups}()}, +\code{\link{aws_vpc_security_group}()} +} +\concept{security groups} diff --git a/man/aws_vpc_security_group_modify_rules.Rd b/man/aws_vpc_security_group_modify_rules.Rd new file mode 100644 index 0000000..523fa17 --- /dev/null +++ b/man/aws_vpc_security_group_modify_rules.Rd @@ -0,0 +1,44 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vpc_security_groups.R +\name{aws_vpc_security_group_modify_rules} +\alias{aws_vpc_security_group_modify_rules} +\title{Modify security group rules} +\usage{ +aws_vpc_security_group_modify_rules(id, rules, ...) +} +\arguments{ +\item{id}{(character) security group id} + +\item{rules}{list of rules to add/modify on the security group \code{id}} + +\item{...}{named parameters passed on to \href{https://www.paws-r-sdk.com/docs/ec2_modify_security_group_rules/}{modify_security_group_rules}} +} +\description{ +Modify security group rules +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +aws_vpc_security_group_modify_rules( + id = "someid", + rules = list( + SecurityGroupRuleId = "sgr-07de36a0521f39c8b", + SecurityGroupRule = list( + IpProtocol = "tcp", + FromPort = 22, + ToPort = 22, + CidrIpv4 = "3.3.3.3/32", + Description = "added ssh port" + ) + ) +) +\dontshow{\}) # examplesIf} +} +\seealso{ +Other security groups: +\code{\link{aws_ec2_client}()}, +\code{\link{aws_vpc_security_group_create}()}, +\code{\link{aws_vpc_security_group_ingress}()}, +\code{\link{aws_vpc_security_groups}()}, +\code{\link{aws_vpc_security_group}()} +} +\concept{security groups} diff --git a/man/aws_vpc_security_groups.Rd b/man/aws_vpc_security_groups.Rd new file mode 100644 index 0000000..1c97b7f --- /dev/null +++ b/man/aws_vpc_security_groups.Rd @@ -0,0 +1,32 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vpc_security_groups.R +\name{aws_vpc_security_groups} +\alias{aws_vpc_security_groups} +\title{List VPC security groups} +\usage{ +aws_vpc_security_groups(...) +} +\arguments{ +\item{...}{named parameters passed on to \href{https://www.paws-r-sdk.com/docs/ec2_describe_security_groups/}{describe_security_groups}} +} +\value{ +(list) list with security groups +} +\description{ +List VPC security groups +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +aws_vpc_security_groups() +aws_vpc_security_groups(MaxResults = 6) +\dontshow{\}) # examplesIf} +} +\seealso{ +Other security groups: +\code{\link{aws_ec2_client}()}, +\code{\link{aws_vpc_security_group_create}()}, +\code{\link{aws_vpc_security_group_ingress}()}, +\code{\link{aws_vpc_security_group_modify_rules}()}, +\code{\link{aws_vpc_security_group}()} +} +\concept{security groups} diff --git a/man/ip_address.Rd b/man/ip_address.Rd new file mode 100644 index 0000000..3bd503a --- /dev/null +++ b/man/ip_address.Rd @@ -0,0 +1,15 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vpc_security_groups.R +\name{ip_address} +\alias{ip_address} +\title{Get your IP address using \url{https://ifconfig.me/ip}} +\usage{ +ip_address() +} +\value{ +(character) ip address +} +\description{ +Get your IP address using \url{https://ifconfig.me/ip} +} +\keyword{internal} diff --git a/man/ip_permissions_generator.Rd b/man/ip_permissions_generator.Rd new file mode 100644 index 0000000..55d088d --- /dev/null +++ b/man/ip_permissions_generator.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vpc_security_groups.R +\name{ip_permissions_generator} +\alias{ip_permissions_generator} +\title{Ip Permissions generator} +\usage{ +ip_permissions_generator(engine, port = NULL, description = NULL) +} +\arguments{ +\item{engine}{(character) one of mariadb, mysql, or postgres} + +\item{port}{(character) port number. port determined from \code{engine} +if \code{port} not given. default: \code{NULL}} + +\item{description}{(character) description. if not given, autogenerated +depending on value of \code{engine}} +} +\value{ +a list with slots: FromPort, ToPort, IpProtocol, and IpRanges +} +\description{ +Ip Permissions generator +} From cd9c0033a5d80a85a1b1ae390e92825fc27cd219 Mon Sep 17 00:00:00 2001 From: Scott Chamberlain Date: Thu, 7 Mar 2024 09:31:41 -0800 Subject: [PATCH 4/7] styling --- NAMESPACE | 2 +- R/vpc_security_groups.R | 15 +++++--- man/aws_ec2_client.Rd | 2 +- man/aws_vpc_security_group.Rd | 2 +- man/aws_vpc_security_group_create.Rd | 2 +- man/aws_vpc_security_group_ingress.Rd | 2 +- man/aws_vpc_security_group_modify_rules.Rd | 44 ---------------------- man/aws_vpc_security_groups.Rd | 2 +- 8 files changed, 15 insertions(+), 56 deletions(-) delete mode 100644 man/aws_vpc_security_group_modify_rules.Rd diff --git a/NAMESPACE b/NAMESPACE index 5539e3f..80beae5 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -57,10 +57,10 @@ export(aws_user_delete) export(aws_user_exists) export(aws_users) export(aws_vpc) +export(aws_vpc_sec_group_rules) export(aws_vpc_security_group) export(aws_vpc_security_group_create) export(aws_vpc_security_group_ingress) -export(aws_vpc_security_group_modify_rules) export(aws_vpc_security_groups) export(aws_vpcs) export(billing) diff --git a/R/vpc_security_groups.R b/R/vpc_security_groups.R index 88f7a6c..05dee68 100644 --- a/R/vpc_security_groups.R +++ b/R/vpc_security_groups.R @@ -23,8 +23,8 @@ security_group_handler <- function(ids, engine) { if (!is.null(ids)) { return(ids) } - port <- engine2port(engine) - ip <- ip_address() + port <- engine2port(engine) #nolint + ip <- ip_address() #nolint sgs <- aws_vpc_security_groups() sgsdf <- jsonlite::fromJSON( jsonlite::toJSON(sgs$SecurityGroups, auto_unbox = TRUE) @@ -83,14 +83,16 @@ security_group_handler <- function(ids, engine) { if (picked == 0) { cli::cli_alert_danger( - "No security group selected; please use paramater {.strong security_group_ids}" + "No security group selected; please use ", + "paramater {.strong security_group_ids}" ) return(NULL) } else { picked_id <- port_df[picked, "GroupId"] } cli::cli_alert_info( - "Adding your IP address {.strong {ip}} to security group {.strong {picked_id}}" + "Adding your IP address {.strong {ip}} to security ", + "group {.strong {picked_id}}" ) try_ingress <- tryCatch( { @@ -138,7 +140,8 @@ security_group_handler <- function(ids, engine) { if (picked == 0) { cli::cli_alert_danger(c( "Found security group {.strong {ip_df$GroupId}} ", - "w/ access for {.strong {engine}}, {.emph but} not with your IP address {.strong {ip}}" + "w/ access for {.strong {engine}},", + "{.emph but} not with your IP address {.strong {ip}}" )) return(NULL) } else { @@ -331,7 +334,7 @@ aws_vpc_security_group_ingress <- function( #' ) #' ) #' ) -aws_vpc_security_group_modify_rules <- function(id, rules, ...) { +aws_vpc_sec_group_rules <- function(id, rules, ...) { aws_ec2_client() env64$ec2$modify_security_group_rules( GroupId = id, diff --git a/man/aws_ec2_client.Rd b/man/aws_ec2_client.Rd index 551edd5..c30f1a3 100644 --- a/man/aws_ec2_client.Rd +++ b/man/aws_ec2_client.Rd @@ -18,9 +18,9 @@ returns existing client if found; a new client otherwise } \seealso{ Other security groups: +\code{\link{aws_vpc_sec_group_rules}()}, \code{\link{aws_vpc_security_group_create}()}, \code{\link{aws_vpc_security_group_ingress}()}, -\code{\link{aws_vpc_security_group_modify_rules}()}, \code{\link{aws_vpc_security_groups}()}, \code{\link{aws_vpc_security_group}()} } diff --git a/man/aws_vpc_security_group.Rd b/man/aws_vpc_security_group.Rd index adf2fc2..b343c66 100644 --- a/man/aws_vpc_security_group.Rd +++ b/man/aws_vpc_security_group.Rd @@ -34,9 +34,9 @@ Get a security group by ID \seealso{ Other security groups: \code{\link{aws_ec2_client}()}, +\code{\link{aws_vpc_sec_group_rules}()}, \code{\link{aws_vpc_security_group_create}()}, \code{\link{aws_vpc_security_group_ingress}()}, -\code{\link{aws_vpc_security_group_modify_rules}()}, \code{\link{aws_vpc_security_groups}()} } \concept{security groups} diff --git a/man/aws_vpc_security_group_create.Rd b/man/aws_vpc_security_group_create.Rd index 22f0a00..9a2dae8 100644 --- a/man/aws_vpc_security_group_create.Rd +++ b/man/aws_vpc_security_group_create.Rd @@ -55,8 +55,8 @@ aws_vpc_security_group_ingress( \seealso{ Other security groups: \code{\link{aws_ec2_client}()}, +\code{\link{aws_vpc_sec_group_rules}()}, \code{\link{aws_vpc_security_group_ingress}()}, -\code{\link{aws_vpc_security_group_modify_rules}()}, \code{\link{aws_vpc_security_groups}()}, \code{\link{aws_vpc_security_group}()} } diff --git a/man/aws_vpc_security_group_ingress.Rd b/man/aws_vpc_security_group_ingress.Rd index df5c51c..7c26e65 100644 --- a/man/aws_vpc_security_group_ingress.Rd +++ b/man/aws_vpc_security_group_ingress.Rd @@ -44,8 +44,8 @@ Authorize Security Group Ingress \seealso{ Other security groups: \code{\link{aws_ec2_client}()}, +\code{\link{aws_vpc_sec_group_rules}()}, \code{\link{aws_vpc_security_group_create}()}, -\code{\link{aws_vpc_security_group_modify_rules}()}, \code{\link{aws_vpc_security_groups}()}, \code{\link{aws_vpc_security_group}()} } diff --git a/man/aws_vpc_security_group_modify_rules.Rd b/man/aws_vpc_security_group_modify_rules.Rd deleted file mode 100644 index 523fa17..0000000 --- a/man/aws_vpc_security_group_modify_rules.Rd +++ /dev/null @@ -1,44 +0,0 @@ -% Generated by roxygen2: do not edit by hand -% Please edit documentation in R/vpc_security_groups.R -\name{aws_vpc_security_group_modify_rules} -\alias{aws_vpc_security_group_modify_rules} -\title{Modify security group rules} -\usage{ -aws_vpc_security_group_modify_rules(id, rules, ...) -} -\arguments{ -\item{id}{(character) security group id} - -\item{rules}{list of rules to add/modify on the security group \code{id}} - -\item{...}{named parameters passed on to \href{https://www.paws-r-sdk.com/docs/ec2_modify_security_group_rules/}{modify_security_group_rules}} -} -\description{ -Modify security group rules -} -\examples{ -\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} -aws_vpc_security_group_modify_rules( - id = "someid", - rules = list( - SecurityGroupRuleId = "sgr-07de36a0521f39c8b", - SecurityGroupRule = list( - IpProtocol = "tcp", - FromPort = 22, - ToPort = 22, - CidrIpv4 = "3.3.3.3/32", - Description = "added ssh port" - ) - ) -) -\dontshow{\}) # examplesIf} -} -\seealso{ -Other security groups: -\code{\link{aws_ec2_client}()}, -\code{\link{aws_vpc_security_group_create}()}, -\code{\link{aws_vpc_security_group_ingress}()}, -\code{\link{aws_vpc_security_groups}()}, -\code{\link{aws_vpc_security_group}()} -} -\concept{security groups} diff --git a/man/aws_vpc_security_groups.Rd b/man/aws_vpc_security_groups.Rd index 1c97b7f..fbc6acd 100644 --- a/man/aws_vpc_security_groups.Rd +++ b/man/aws_vpc_security_groups.Rd @@ -24,9 +24,9 @@ aws_vpc_security_groups(MaxResults = 6) \seealso{ Other security groups: \code{\link{aws_ec2_client}()}, +\code{\link{aws_vpc_sec_group_rules}()}, \code{\link{aws_vpc_security_group_create}()}, \code{\link{aws_vpc_security_group_ingress}()}, -\code{\link{aws_vpc_security_group_modify_rules}()}, \code{\link{aws_vpc_security_group}()} } \concept{security groups} From ae49ada303863d838319df8599745e194f0f33f9 Mon Sep 17 00:00:00 2001 From: Scott Chamberlain Date: Thu, 7 Mar 2024 09:32:03 -0800 Subject: [PATCH 5/7] shorten a fxn name --- man/aws_vpc_sec_group_rules.Rd | 44 ++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 man/aws_vpc_sec_group_rules.Rd diff --git a/man/aws_vpc_sec_group_rules.Rd b/man/aws_vpc_sec_group_rules.Rd new file mode 100644 index 0000000..70b6eb2 --- /dev/null +++ b/man/aws_vpc_sec_group_rules.Rd @@ -0,0 +1,44 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/vpc_security_groups.R +\name{aws_vpc_sec_group_rules} +\alias{aws_vpc_sec_group_rules} +\title{Modify security group rules} +\usage{ +aws_vpc_sec_group_rules(id, rules, ...) +} +\arguments{ +\item{id}{(character) security group id} + +\item{rules}{list of rules to add/modify on the security group \code{id}} + +\item{...}{named parameters passed on to \href{https://www.paws-r-sdk.com/docs/ec2_modify_security_group_rules/}{modify_security_group_rules}} +} +\description{ +Modify security group rules +} +\examples{ +\dontshow{if (interactive()) (if (getRversion() >= "3.4") withAutoprint else force)(\{ # examplesIf} +aws_vpc_security_group_modify_rules( + id = "someid", + rules = list( + SecurityGroupRuleId = "sgr-07de36a0521f39c8b", + SecurityGroupRule = list( + IpProtocol = "tcp", + FromPort = 22, + ToPort = 22, + CidrIpv4 = "3.3.3.3/32", + Description = "added ssh port" + ) + ) +) +\dontshow{\}) # examplesIf} +} +\seealso{ +Other security groups: +\code{\link{aws_ec2_client}()}, +\code{\link{aws_vpc_security_group_create}()}, +\code{\link{aws_vpc_security_group_ingress}()}, +\code{\link{aws_vpc_security_groups}()}, +\code{\link{aws_vpc_security_group}()} +} +\concept{security groups} From 616bd74d5702c645af338a5f291495bcc90fd529 Mon Sep 17 00:00:00 2001 From: Scott Chamberlain Date: Thu, 7 Mar 2024 09:36:12 -0800 Subject: [PATCH 6/7] add make target to scan for secrets using gitleaks --- Makefile | 7 +++++++ README.md | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/Makefile b/Makefile index 69252a9..988a172 100644 --- a/Makefile +++ b/Makefile @@ -40,3 +40,10 @@ style_file: style_package: ${RSCRIPT} -e "styler::style_pkg()" + +scan_secrets: + @echo "scanning for leaks in commits\n" + gitleaks detect --source . -v + @echo "\n\n\n" + @echo "scanning for leaks in uncommitted files\n" + gitleaks protect --source . -v diff --git a/README.md b/README.md index a5b7286..4dd418d 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,11 @@ Development version pak::pkg_install("getwilds/sixtyfour") ``` +## Scanning for secrets + +See the make target `scan_secrets` in the Makefile to scan for secrets. + + ## Code of Conduct Please note that the sixtyfour project is released with a [Contributor Code of Conduct](https://contributor-covenant.org/version/2/1/CODE_OF_CONDUCT.html). By contributing to this project, you agree to abide by its terms. From 0a29e9c8543846fce9002f8ab085ed2c13078557 Mon Sep 17 00:00:00 2001 From: Scott Chamberlain Date: Thu, 7 Mar 2024 09:51:35 -0800 Subject: [PATCH 7/7] fix pkgdown config for changed fxn name --- _pkgdown.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/_pkgdown.yml b/_pkgdown.yml index ee6ed26..746c941 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -49,6 +49,7 @@ reference: - title: VPC Security Groups contents: - starts_with("aws_vpc_security_group") + - aws_vpc_sec_group_rules - ip_permissions_generator - title: EC2 contents: