From b30c5fca22b3f8981c88b581d87fe8da6038ee4f Mon Sep 17 00:00:00 2001 From: Greg Hunt Date: Tue, 20 Sep 2022 19:26:47 +1000 Subject: [PATCH 1/5] Added AWS Secrets Manager backend and test cases. --- DESCRIPTION | 4 +- NAMESPACE | 1 + R/backend-awssecretsmanager.R | 238 ++++++++++++++++++++++++ R/default_backend.R | 15 +- keyring.Rproj | 1 + man/backend.Rd | 6 +- man/backend_awssecretsmanager.Rd | 43 +++++ man/backend_env.Rd | 1 + man/backend_file.Rd | 1 + man/backend_keyrings.Rd | 6 +- man/backend_macos.Rd | 1 + man/backend_secret_service.Rd | 7 +- man/backend_wincred.Rd | 1 + man/backends.Rd | 2 +- tests/testthat/test-awssecretsmanager.R | 102 ++++++++++ 15 files changed, 414 insertions(+), 15 deletions(-) create mode 100644 R/backend-awssecretsmanager.R create mode 100644 man/backend_awssecretsmanager.Rd create mode 100644 tests/testthat/test-awssecretsmanager.R diff --git a/DESCRIPTION b/DESCRIPTION index 5958aaa..c02152b 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -14,7 +14,7 @@ Description: Platform independent 'API' to access the operating system's License: MIT + file LICENSE URL: https://r-lib.github.io/keyring/index.html, https://github.com/r-lib/keyring#readme BugReports: https://github.com/r-lib/keyring/issues -RoxygenNote: 7.1.2 +RoxygenNote: 7.2.1 Roxygen: list(markdown = TRUE, r6 = FALSE) Imports: assertthat, @@ -31,6 +31,7 @@ Suggests: callr, covr, mockery, + paws, testthat, withr Encoding: UTF-8 @@ -40,6 +41,7 @@ Collate: 'api.R' 'assertions.R' 'backend-class.R' + 'backend-awssecretsmanager.R' 'backend-env.R' 'backend-file.R' 'backend-macos.R' diff --git a/NAMESPACE b/NAMESPACE index 2145751..15d85dc 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,6 +1,7 @@ # Generated by roxygen2: do not edit by hand export(backend) +export(backend_awssecretsmanager) export(backend_env) export(backend_file) export(backend_keyrings) diff --git a/R/backend-awssecretsmanager.R b/R/backend-awssecretsmanager.R new file mode 100644 index 0000000..ff87423 --- /dev/null +++ b/R/backend-awssecretsmanager.R @@ -0,0 +1,238 @@ + + +#' AWS Secrets Manager keyring backend +#' +#' This backend must be selected explicitly. It makes calls to the AWS +#' secretsmanager service. +#' +#' This backend does not support keyrings or user names. The call to the +#' AWS service is authenticated by either the user's ceedentials or the IAM +#' user associated with the process, for example in a docker container. +#' +#' Note that the AWS APIs provide enventual consistency, it can take +#' a noticeable amount of time, up to five minutes, for updates and deletes +#' to propagate and so code that updates, deletes and lists needs to be +#' written to tolerate that. +#' +#' +#' @family keyring backends +#' @export +#' @include backend-class.R +#' @examples +#' \dontrun{ +#' ## +#' kb <- backend_awssecretsmanager$new() +#' kb$set_with_value("service", password = "secret") +#' kb$get("service") +#' kb$delete("service") +#' } + +backend_awssecretsmanager <- R6Class( + "backend_awssecretsmanager", + inherit = backend_keyrings, + public = list( + name = "aws", + initialize = function(keyring = NULL) + b_aws_init(self, private, keyring), + + get = function(service, + username = NULL, + keyring = NULL) + b_aws_get(self, private, service, username, keyring), + get_raw = function(service, + username = NULL, + keyring = NULL) + b_aws_get_raw(self, private, service, username, keyring), + set = function(service, + username = NULL, + keyring = NULL, + prompt = "Password: ") + b_aws_set(self, private, service, username, keyring, prompt), + set_with_value = function(service, + username = NULL, + password = NULL, + keyring = NULL) + b_aws_set_with_value(self, private, service, username, password, + keyring), + set_with_raw_value = function(service, + username = NULL, + password = NULL, + keyring = NULL) + b_aws_set_with_raw_value(self, private, service, username, password, + keyring), + delete = function(service, + username = NULL, + keyring = NULL) + b_aws_delete(self, private, service, username, keyring), + list = function(service = NULL, keyring = NULL) + b_aws_list(self, private, service, keyring), + is_available = function(report_error = FALSE) + b_aws_is_available(self, private, report_error), + + has_keyring_support = function() + { + return(FALSE) + }, + + docs = function() { + modifyList(super$docs(), + list(. = "Store secrets using the AWS Secrets manager.")) + } + ), + + private = list( + keyring = NULL, + sservice = NULL, + requestToken = paste("123456789012345678901234567890", as.character(Sys.time())), + keyring_create_direct = function(keyring, password = NULL) + b_aws_keyring_create_direct(self, private, keyring, password) + ) +) + +b_aws_init <- function(self, private, keyring) { + if (!is.null(keyring)) + stop("keyring parameter is not supported by the aws secrets manager backend") + private$sservice <- paws::secretsmanager() + invisible(self) +} + +b_aws_get <- function(self, private, service, username, keyring) { + if (!is.null(username)) + stop("username parameter is not supported by the aws secrets manager backend") + if (!is.null(keyring)) + stop("keyring parameter is not supported by the aws secrets manager backend") + return(private$sservice$get_secret_value(SecretId = service,)$SecretString) +} + +b_aws_get_raw <- + function(self, private, service, username, keyring) { + if (!is.null(username)) + stop("username parameter is not supported by the aws secrets manager backend") + if (!is.null(keyring)) + stop("keyring parameter is not supported by the aws secrets manager backend") + return(private$sservice$svc$get_secret_value(SecretId = service,)$SecretBinary) + } + +b_aws_set <- + function(self, + private, + service, + username, + keyring, + prompt) { + if (!is.null(username)) + stop("username parameter is not supported by the aws secrets manager backend") + if (!is.null(keyring)) + stop("keyring parameter is not supported by the aws secrets manager backend") + username <- username %||% getOption("keyring_username") + password <- get_pass(prompt) + if (is.null(password)) + stop("No secret provided") + private$sservice$create_secret( + ClientRequestToken = private$requestToken, + Description = "", + Name = service, + SecretString = password + ) + invisible(self) + } + +b_aws_set_with_value <- + function(self, + private, + service, + username, + password, + keyring) { + if (!is.null(username)) + stop("username parameter is not supported by the aws secrets manager backend") + if (!is.null(keyring)) + stop("keyring parameter is not supported by the aws secrets manager backend") + username <- username %||% getOption("keyring_username") + keyring <- keyring %||% private$keyring + private$sservice$create_secret( + ClientRequestToken = private$requestToken, + Description = "", + Name = service, + SecretString = password + ) + invisible(self) + } + +b_aws_set_with_raw_value <- + function(self, + private, + service, + username, + password, + keyring) { + if (!is.null(username)) + stop("username parameter is not supported by the aws secrets manager backend") + if (!is.null(keyring)) + stop("keyring parameter is not supported by the aws secrets manager backend") + username <- username %||% getOption("keyring_username") + keyring <- keyring %||% private$keyring + private$sservice$create_secret( + ClientRequestToken = private$requestToken, + Description = "", + Name = service, + SecretBinaryString = password + ) + invisible(self) + } + +b_aws_delete <- + function(self, private, service, username, keyring) { + if (!is.null(username)) + stop("username parameter is not supported by the aws secrets manager backend") + if (!is.null(keyring)) + stop("keyring parameter is not supported by the aws secrets manager backend") + username <- username %||% getOption("keyring_username") + keyring <- keyring %||% private$keyring + private$sservice$delete_secret(ForceDeleteWithoutRecovery = TRUE, + SecretId = service) + invisible(self) + } + +b_aws_list <- function(self, private, service, keyring) { + if (!is.null(keyring)) + stop("keyring parameter is not supported by the aws secrets manager backend") + keyring <- keyring %||% private$keyring + if (is.null(service) || + service == "") + # missing defaults to null in calling routine + { + res = private$sservice$list_secrets() + } else + { + res = private$sservice$list_secrets(Filter = list(list( + Key = "name", Values = c(service) + ))) + } + nameList = c() + if (length(res$SecretList) > 0) + { + for (i in 1:length(res$SecretList)) + { + nameList = c(nameList, res$SecretList[[i]]$Name) + } + } + df = data.frame(service = nameList, + stringsAsFactors = FALSE) + df$username = NULL + + return(df) +} + +b_aws_is_available <- function(self, private, report_error) { + callerID = try(paws::sts()$get_caller_identity()) + if (inherits(callerID, "try-error")) { + if(report_error) + { + signalCondition("No AWS credentials to use for testing or AWS not reachable") + } + return(FALSE) + } + return(TRUE) + } + diff --git a/R/default_backend.R b/R/default_backend.R index af885c4..4f849f3 100644 --- a/R/default_backend.R +++ b/R/default_backend.R @@ -24,8 +24,8 @@ #' 1. the `keyring_keyring` option. #' - You can change this by using `options(keyring_keyring = "NEWVALUE")` #' 1. If this is not set, the `R_KEYRING_KEYRING` environment variable. -#' - Change this value with `Sys.setenv(R_KEYRING_KEYRING = "NEWVALUE")`, -#' either in your script or in your `.Renviron` file. +#' - Change this value with `Sys.setenv(R_KEYRING_KEYRING = "NEWVALUE")`, +#' either in your script or in your `.Renviron` file. #' See [base::Startup] for information about using `.Renviron` #' 1. Finally, if neither of these are set, the OS default keyring is used. #' - Usually the keyring is automatically unlocked when the user logs in. @@ -33,10 +33,10 @@ #' @param keyring Character string, the name of the keyring to use, #' or `NULL` for the default keyring. #' @return The backend object itself. -#' -#' +#' +#' #' @seealso [backend_env], [backend_file], [backend_macos], -#' [backend_secret_service], [backend_wincred] +#' [backend_secret_service], [backend_wincred], [backend_awssecretsmanager] #' #' @export #' @name backends @@ -84,7 +84,7 @@ default_backend_auto <- function() { } else if (sysname == "linux" && "secret_service" %in% names(known_backends) && backend_secret_service$new()$is_available()) { backend_secret_service - + } else if ("file" %in% names(known_backends)) { backend_file @@ -111,5 +111,6 @@ known_backends <- list( "macos" = backend_macos, "secret_service" = backend_secret_service, "env" = backend_env, - "file" = backend_file + "file" = backend_file, + "awssecretsmanager" = backend_awssecretsmanager ) diff --git a/keyring.Rproj b/keyring.Rproj index dde2c3a..6fccb25 100644 --- a/keyring.Rproj +++ b/keyring.Rproj @@ -18,3 +18,4 @@ StripTrailingWhitespace: Yes BuildType: Package PackageUseDevtools: Yes PackageInstallArgs: --no-multiarch --with-keep.source +PackageRoxygenize: rd,collate,namespace,vignette diff --git a/man/backend.Rd b/man/backend.Rd index a3095af..48ee2e1 100644 --- a/man/backend.Rd +++ b/man/backend.Rd @@ -10,7 +10,9 @@ methods. Implementing the \code{list} method is optional. Additional methods can be defined as well. } \details{ -These are the semantics of the various methods:\preformatted{get(service, username = NULL, keyring = NULL) +These are the semantics of the various methods: + +\if{html}{\out{
}}\preformatted{get(service, username = NULL, keyring = NULL) get_raw(service, username = NULL, keyring = NULL) set(service, username = NULL, keyring = NULL, prompt = "Password: ") set_with_value(service, username = NULL, password = NULL, @@ -19,7 +21,7 @@ set_with_raw_value(service, username = NULL, password = NULL, keyring = NULL) delete(service, username = NULL, keyring = NULL) list(service = NULL, keyring = NULL) -} +}\if{html}{\out{
}} What these functions do: \itemize{ diff --git a/man/backend_awssecretsmanager.Rd b/man/backend_awssecretsmanager.Rd new file mode 100644 index 0000000..1373032 --- /dev/null +++ b/man/backend_awssecretsmanager.Rd @@ -0,0 +1,43 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/backend-awssecretsmanager.R +\name{backend_awssecretsmanager} +\alias{backend_awssecretsmanager} +\title{AWS Secrets Manager keyring backend} +\description{ +This backend must be selected explicitly. It makes calls to the AWS +secretsmanager service. +} +\details{ +This backend does not support keyrings or user names. The call to the +AWS service is authenticated by either the user's ceedentials or the IAM +user associated with the process, for example in a docker container. + +See \link{backend} for the documentation of the individual methods. + +\if{html}{\out{
}}\preformatted{is_available = function(report_error = FALSE) +}\if{html}{\out{
}} + +Argument: +\itemize{ +\item \code{report_error} Whether to throw an error if the Secret Service is +not available. +} +} +\examples{ +\dontrun{ +## +kb <- backend_awssecretsmanager$new() +kb$set_with_value("service", password = "secret") +kb$get("service") +kb$delete("service") +} +} +\seealso{ +Other keyring backends: +\code{\link{backend_env}}, +\code{\link{backend_file}}, +\code{\link{backend_macos}}, +\code{\link{backend_secret_service}}, +\code{\link{backend_wincred}} +} +\concept{keyring backends} diff --git a/man/backend_env.Rd b/man/backend_env.Rd index 329a0e2..a28a678 100644 --- a/man/backend_env.Rd +++ b/man/backend_env.Rd @@ -36,6 +36,7 @@ env$delete("r-keyring-test", username = "donaldduck") } \seealso{ Other keyring backends: +\code{\link{backend_awssecretsmanager}}, \code{\link{backend_file}}, \code{\link{backend_macos}}, \code{\link{backend_secret_service}}, diff --git a/man/backend_file.Rd b/man/backend_file.Rd index b3af6b0..37b8cca 100644 --- a/man/backend_file.Rd +++ b/man/backend_file.Rd @@ -19,6 +19,7 @@ kb <- backend_file$new() } \seealso{ Other keyring backends: +\code{\link{backend_awssecretsmanager}}, \code{\link{backend_env}}, \code{\link{backend_macos}}, \code{\link{backend_secret_service}}, diff --git a/man/backend_keyrings.Rd b/man/backend_keyrings.Rd index 056b903..cde10c4 100644 --- a/man/backend_keyrings.Rd +++ b/man/backend_keyrings.Rd @@ -13,7 +13,9 @@ inherit from this class and redefine the \code{get}, \code{set}, \code{set_with_ } \details{ See \link{backend} for the first set of methods. This is the semantics of the -keyring management methods:\preformatted{keyring_create(keyring) +keyring management methods: + +\if{html}{\out{
}}\preformatted{keyring_create(keyring) keyring_list() keyring_delete(keyring = NULL) keyring_lock(keyring = NULL) @@ -21,7 +23,7 @@ keyring_unlock(keyring = NULL, password = NULL) keyring_is_locked(keyring = NULL) keyring_default() keyring_set_default(keyring = NULL) -} +}\if{html}{\out{
}} \itemize{ \item \code{keyring_create()} creates a new keyring. \item \code{keyring_list()} lists all keyrings. diff --git a/man/backend_macos.Rd b/man/backend_macos.Rd index 3e5a5da..50162b2 100644 --- a/man/backend_macos.Rd +++ b/man/backend_macos.Rd @@ -26,6 +26,7 @@ kb$delete_keyring("foobar") } \seealso{ Other keyring backends: +\code{\link{backend_awssecretsmanager}}, \code{\link{backend_env}}, \code{\link{backend_file}}, \code{\link{backend_secret_service}}, diff --git a/man/backend_secret_service.Rd b/man/backend_secret_service.Rd index 2b228c3..ad5542f 100644 --- a/man/backend_secret_service.Rd +++ b/man/backend_secret_service.Rd @@ -14,8 +14,10 @@ This backend supports multiple keyrings. See \link{backend} for the documentation of the individual methods. The \code{is_available()} method checks is a Secret Service daemon is running on the system, by trying to connect to it. It returns a logical -scalar, or throws an error, depending on its argument:\preformatted{is_available = function(report_error = FALSE) -} +scalar, or throws an error, depending on its argument: + +\if{html}{\out{
}}\preformatted{is_available = function(report_error = FALSE) +}\if{html}{\out{
}} Argument: \itemize{ @@ -37,6 +39,7 @@ kb$delete_keyring("foobar") } \seealso{ Other keyring backends: +\code{\link{backend_awssecretsmanager}}, \code{\link{backend_env}}, \code{\link{backend_file}}, \code{\link{backend_macos}}, diff --git a/man/backend_wincred.Rd b/man/backend_wincred.Rd index dcbc4ca..3246526 100644 --- a/man/backend_wincred.Rd +++ b/man/backend_wincred.Rd @@ -28,6 +28,7 @@ kb$delete_keyring("foobar") } \seealso{ Other keyring backends: +\code{\link{backend_awssecretsmanager}}, \code{\link{backend_env}}, \code{\link{backend_file}}, \code{\link{backend_macos}}, diff --git a/man/backends.Rd b/man/backends.Rd index 1d540d2..f096093 100644 --- a/man/backends.Rd +++ b/man/backends.Rd @@ -59,5 +59,5 @@ See \link[base:Startup]{base::Startup} for information about using \code{.Renvir } \seealso{ \link{backend_env}, \link{backend_file}, \link{backend_macos}, -\link{backend_secret_service}, \link{backend_wincred} +\link{backend_secret_service}, \link{backend_wincred}, \link{backend_awssecretsmanager} } diff --git a/tests/testthat/test-awssecretsmanager.R b/tests/testthat/test-awssecretsmanager.R new file mode 100644 index 0000000..cbff36f --- /dev/null +++ b/tests/testthat/test-awssecretsmanager.R @@ -0,0 +1,102 @@ +# +# the SecretsManager API is described as eventually consistent and +# immediately listing a just-created secret is not guaranteed to work, +# re-creating one that has just been deleted may not work either. +# In the list case we cannot tell the difference betwen not found and not +# propagated. This should not be an issue in the real world where +# hopefully the delays between creating and referencing a secret are +# more than the five minutes that Amazon recommends waiting becore giving up. +# +# All this means that these tests can appear somewhat flakey when delays +# appear and disappear. The list test can both work and not work in the +# same run and so have had loops added to protect them from failing. +# + +context("AWS Secrets Manager") + +test_that("set, list, get, delete", { + + skip_on_cran() + # AWS secret creation costs money, don't do it by accident + if(Sys.getenv("R_KEYRING_TEST_USE_AWS")[[1]] == "") { + skip("AWS backend not tested, environment variable R_KEYRING_TEST_USE_AWS not set") + } + callerID = try(paws::sts()$get_caller_identity()) + if (inherits(callerID, "try-error")) { + skip("No AWS credentials to use for testing") + } + + service <- random_service() + service2 <- random_service() + username <- random_username() + password <- random_password() + + kb <- backend_awssecretsmanager$new() + expect_error(kb$set_with_value(service, username, password)) + expect_silent(kb$set_with_value(service, password = password)) + expect_silent(kb$set_with_value(service2, password = password)) + sleepTime = 1 + sleepCount = 0 + repeat { + serviceName = kb$list(service)$service + if (!is.null(serviceName)) { + break + } + sleepCount = sleepCount + 1 + if (sleepCount > 6) + { + fail(message = "gave up waiting for AWS secret create to propagate while testing listing a named secret") + } + Sys.sleep(sleepTime) + sleepTime = sleepTime * 2 + } + + expect_equal(serviceName, c(service)) + + repeat { + serviceName = kb$list()$service + if (length(serviceName) >= 2) { + break + } + sleepCount = sleepCount + 1 + if (sleepCount > 6) + { + fail(message = "gave up waiting for AWS secret create to propagate while testing listing multiple secrets") + } + Sys.sleep(sleepTime) + sleepTime = sleepTime * 2 + } + + expect_gte(length(serviceName),2) + + expect_error(kb$get(service, username)) + + expect_error(kb$delete(service, username)) + + expect_silent(kb$delete(service)) + expect_silent(kb$delete(service2)) +}) + +test_that("set, get, delete, without username", { + + skip_on_cran() + # AWS secret creation costs money, don't do it by accident + if(Sys.getenv("R_KEYRING_TEST_USE_AWS")[[1]] == "") { + skip("AWS backend not tested, environment variable R_KEYRING_TEST_USE_AWS not set") + } + callerID = try(paws::sts()$get_caller_identity()) + if (inherits(callerID, "try-error")) { + skip("No AWS credentials to use for testing") + } + + service <- random_service() + password <- random_password() + + kb <- backend_env$new() + + expect_silent(kb$set_with_value(service, password = password)) + + expect_equal(kb$get(service), password) + + expect_silent(kb$delete(service)) +}) From a4f41c4da8fae24c5c5dc7f7be457b38ea25dc3a Mon Sep 17 00:00:00 2001 From: Greg Hunt Date: Wed, 21 Sep 2022 07:32:03 +1000 Subject: [PATCH 2/5] Added check for paws library (which is only identified as recommended in the DESCRIPTION file --- R/backend-awssecretsmanager.R | 8 ++++++++ man/backend_awssecretsmanager.Rd | 14 ++++---------- tests/testthat/test-awssecretsmanager.R | 5 +++++ 3 files changed, 17 insertions(+), 10 deletions(-) diff --git a/R/backend-awssecretsmanager.R b/R/backend-awssecretsmanager.R index ff87423..ac8d969 100644 --- a/R/backend-awssecretsmanager.R +++ b/R/backend-awssecretsmanager.R @@ -225,6 +225,14 @@ b_aws_list <- function(self, private, service, keyring) { } b_aws_is_available <- function(self, private, report_error) { + if(!requireNamespace("paws")) + { + if(report_error) + { + signalCondition("Paws library not available. It is required for AWS access") + } + return(FALSE) + } callerID = try(paws::sts()$get_caller_identity()) if (inherits(callerID, "try-error")) { if(report_error) diff --git a/man/backend_awssecretsmanager.Rd b/man/backend_awssecretsmanager.Rd index 1373032..914d5d5 100644 --- a/man/backend_awssecretsmanager.Rd +++ b/man/backend_awssecretsmanager.Rd @@ -12,16 +12,10 @@ This backend does not support keyrings or user names. The call to the AWS service is authenticated by either the user's ceedentials or the IAM user associated with the process, for example in a docker container. -See \link{backend} for the documentation of the individual methods. - -\if{html}{\out{
}}\preformatted{is_available = function(report_error = FALSE) -}\if{html}{\out{
}} - -Argument: -\itemize{ -\item \code{report_error} Whether to throw an error if the Secret Service is -not available. -} +Note that the AWS APIs provide enventual consistency, it can take +a noticeable amount of time, up to five minutes, for updates and deletes +to propagate and so code that updates, deletes and lists needs to be +written to tolerate that. } \examples{ \dontrun{ diff --git a/tests/testthat/test-awssecretsmanager.R b/tests/testthat/test-awssecretsmanager.R index cbff36f..5eba60e 100644 --- a/tests/testthat/test-awssecretsmanager.R +++ b/tests/testthat/test-awssecretsmanager.R @@ -14,6 +14,8 @@ context("AWS Secrets Manager") +#Sys.setenv(R_KEYRING_TEST_USE_AWS=1) + test_that("set, list, get, delete", { skip_on_cran() @@ -32,6 +34,9 @@ test_that("set, list, get, delete", { password <- random_password() kb <- backend_awssecretsmanager$new() + + expect_true(kb$is_available()) + expect_error(kb$set_with_value(service, username, password)) expect_silent(kb$set_with_value(service, password = password)) expect_silent(kb$set_with_value(service2, password = password)) From 3011212631248489cbe2fb90f9048ac4c95cd32b Mon Sep 17 00:00:00 2001 From: Greg Hunt Date: Thu, 20 Oct 2022 18:56:55 +1100 Subject: [PATCH 3/5] Add paging for large numbers of secrets --- R/backend-awssecretsmanager.R | 22 ++++++++++++++++++---- tests/testthat/test-awssecretsmanager.R | 10 +++++++--- tests/testthat/testthat-problems.rds | Bin 0 -> 4006 bytes 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 tests/testthat/testthat-problems.rds diff --git a/R/backend-awssecretsmanager.R b/R/backend-awssecretsmanager.R index ac8d969..2f196ef 100644 --- a/R/backend-awssecretsmanager.R +++ b/R/backend-awssecretsmanager.R @@ -203,18 +203,32 @@ b_aws_list <- function(self, private, service, keyring) { # missing defaults to null in calling routine { res = private$sservice$list_secrets() + secretList = res$SecretList + while(length(res$NextToken)>0) + { + res = private$sservice$list_secrets(NextToken=res$NextToken) + secretList = append(secretList, res$SecretList) + } } else { res = private$sservice$list_secrets(Filter = list(list( Key = "name", Values = c(service) ))) + secretList = res$SecretList + while(length(res$NextToken)>0) + { + res = private$sservice$list_secrets(NextToken=res$NextToken, Filter = list(list( + Key = "name", Values = c(service) + ))) + secretList = append(secretList, res$SecretList) + } } nameList = c() - if (length(res$SecretList) > 0) + if (length(secretList) > 0) { - for (i in 1:length(res$SecretList)) + for (i in 1:length(secretList)) { - nameList = c(nameList, res$SecretList[[i]]$Name) + nameList = c(nameList, secretList[[i]]$Name) } } df = data.frame(service = nameList, @@ -237,7 +251,7 @@ b_aws_is_available <- function(self, private, report_error) { if (inherits(callerID, "try-error")) { if(report_error) { - signalCondition("No AWS credentials to use for testing or AWS not reachable") + signalCondition("No AWS credentials or AWS not reachable") } return(FALSE) } diff --git a/tests/testthat/test-awssecretsmanager.R b/tests/testthat/test-awssecretsmanager.R index 5eba60e..e3556f8 100644 --- a/tests/testthat/test-awssecretsmanager.R +++ b/tests/testthat/test-awssecretsmanager.R @@ -14,7 +14,7 @@ context("AWS Secrets Manager") -#Sys.setenv(R_KEYRING_TEST_USE_AWS=1) +Sys.setenv(R_KEYRING_TEST_USE_AWS=1) test_that("set, list, get, delete", { @@ -76,6 +76,8 @@ test_that("set, list, get, delete", { expect_error(kb$get(service, username)) + expect_error(kb$list(service, username)) + expect_error(kb$delete(service, username)) expect_silent(kb$delete(service)) @@ -97,11 +99,13 @@ test_that("set, get, delete, without username", { service <- random_service() password <- random_password() - kb <- backend_env$new() + kb <- backend_awssecretsmanager$new() expect_silent(kb$set_with_value(service, password = password)) expect_equal(kb$get(service), password) - expect_silent(kb$delete(service)) + #expect_snapshot(kb$list(),"list") + +# expect_silent(kb$delete(service)) }) diff --git a/tests/testthat/testthat-problems.rds b/tests/testthat/testthat-problems.rds new file mode 100644 index 0000000000000000000000000000000000000000..230e486ca5ebd171cc0d90c28148f96d0033cd3e GIT binary patch literal 4006 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 literal 0 HcmV?d00001 From d4732bc61b9703a960ad4c9bb67a7a18d39a0c41 Mon Sep 17 00:00:00 2001 From: Greg Hunt Date: Sun, 15 Jan 2023 11:42:32 +1100 Subject: [PATCH 4/5] Update github pkgdown markup to add AWS Secrets Manager to index --- R/backend-awssecretsmanager.R | 2 +- R/package.R | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/R/backend-awssecretsmanager.R b/R/backend-awssecretsmanager.R index 2f196ef..811c2b4 100644 --- a/R/backend-awssecretsmanager.R +++ b/R/backend-awssecretsmanager.R @@ -6,7 +6,7 @@ #' secretsmanager service. #' #' This backend does not support keyrings or user names. The call to the -#' AWS service is authenticated by either the user's ceedentials or the IAM +#' AWS service is authenticated by either the user's credentials or the IAM #' user associated with the process, for example in a docker container. #' #' Note that the AWS APIs provide enventual consistency, it can take diff --git a/R/package.R b/R/package.R index 901ec3e..07d8ca0 100644 --- a/R/package.R +++ b/R/package.R @@ -5,7 +5,8 @@ #' implementations. Currently supported: #' * Keychain on macOS, #' * Credential Store on Windows, -#' * the Secret Service API on Linux, and +#' * the Secret Service API on Linux +#' * the AWS Secrets Manager, and #' * environment variables on other platforms. #' #' @section Configuring an OS-specific backend: @@ -15,8 +16,10 @@ #' - MacOS: [backend_macos] #' - Linux: [backend_secret_service] #' - Windows: [backend_wincred] -#' - Or store the secrets in environment variables on other operating +#' - Store the secrets in environment variables on other operating #' systems: [backend_env] +#' - Or in the AWS secrets Manager: +#' [backend_awssecretsmanager] #' #' @section Query secret keys in a keyring: #' From a759c3a5f32806b85fd9f31c8a45112d2f5a7641 Mon Sep 17 00:00:00 2001 From: Greg Hunt Date: Sun, 15 Jan 2023 12:08:48 +1100 Subject: [PATCH 5/5] update install text --- README.Rmd | 10 ++++++++++ README.md | 10 ++++++++++ _pkgdown.yml | 1 + 3 files changed, 21 insertions(+) diff --git a/README.Rmd b/README.Rmd index 42e0f81..69d42f7 100644 --- a/README.Rmd +++ b/README.Rmd @@ -28,6 +28,7 @@ credential store. Currently supports: * Keychain on macOS (`backend_macos`), * Credential Store on Windows (`backend_wincred`), * the Secret Service API on Linux (`backend_secret_service`), +* AWS Secrets Manager (`backend_awssecretsmanager`), * encrypted files (`backend_file`), and * environment variables (`backend_env`). @@ -55,6 +56,15 @@ This backend works best on Linux servers. Install these packages: No additional software is needed. +### AWS Secrets Manager + +The paws package must be installed from CRAN to access the AWS services +along with the AWS CLI and credentials. + +```{r eval = FALSE} +install.packages("paws") +``` + ### R package Install the package from CRAN: diff --git a/README.md b/README.md index 549b971..2555280 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,7 @@ store. Currently supports: - Keychain on macOS (`backend_macos`), - Credential Store on Windows (`backend_wincred`), - the Secret Service API on Linux (`backend_secret_service`), +- AWS Secrets Manager (`backend_awssecretsmanager`), - encrypted files (`backend_file`), and - environment variables (`backend_env`). @@ -47,6 +48,15 @@ packages: No additional software is needed. +### AWS Secrets Manager + +The paws package must be installed from CRAN to access the AWS services +along with the AWS CLI, and AWS credentials. + +``` r +install.packages("paws") +``` + ### R package Install the package from CRAN: diff --git a/_pkgdown.yml b/_pkgdown.yml index 8ab5040..3974eaa 100644 --- a/_pkgdown.yml +++ b/_pkgdown.yml @@ -20,6 +20,7 @@ reference: - backend_secret_service - backend_file - backend_env + - backend_awssecretsmanager - title: Implementing new backends contents: