├── .Rbuildignore ├── .github ├── .gitignore └── workflows │ └── R-CMD-check.yaml ├── .gitignore ├── DESCRIPTION ├── LICENSE ├── Makefile ├── NAMESPACE ├── NEWS.md ├── R ├── assertions.R ├── keys.R ├── package.R ├── secrets.R ├── users.R ├── utils.R └── vault.R ├── README.markdown ├── inst ├── README.Rmd ├── README.markdown ├── examples │ ├── example-github.R │ ├── example-secret.R │ └── example-travis.R ├── internals.md ├── user_keys │ ├── alice.pem │ ├── alice.pub │ ├── bob.pem │ ├── bob.pub │ ├── carl.pem │ └── carl.pub └── vignette_child │ └── child.Rmd ├── man ├── add_github_user.Rd ├── add_secret.Rd ├── add_travis_user.Rd ├── add_user.Rd ├── create_package_vault.Rd ├── delete_secret.Rd ├── delete_user.Rd ├── get_github_key.Rd ├── get_secret.Rd ├── get_travis_key.Rd ├── get_user_file.Rd ├── list_owners.Rd ├── list_secrets.Rd ├── list_users.Rd ├── local_key.Rd ├── secret-package.Rd ├── share_secret.Rd ├── share_secret_with_key.Rd ├── share_secret_with_key1.Rd ├── store_secret_with_key.Rd ├── try_get_aes_key.Rd ├── unshare_secret.Rd └── update_secret.Rd ├── tests ├── testthat.R └── testthat │ ├── helper.R │ ├── test-1-vault.R │ ├── test-2-users.R │ ├── test-3-secrets.R │ ├── test-4-travis-github.R │ ├── test-5-keys.R │ └── test-6-issues.R └── vignettes ├── secrets.Rmd ├── secrets.html └── secrets.md /.Rbuildignore: -------------------------------------------------------------------------------- 1 | ^.*\.Rproj$ 2 | ^\.Rproj\.user$ 3 | ^Makefile$ 4 | ^README.markdown$ 5 | ^.travis.yml$ 6 | ^appveyor.yml$ 7 | ^\.github$ 8 | -------------------------------------------------------------------------------- /.github/.gitignore: -------------------------------------------------------------------------------- 1 | *.html 2 | -------------------------------------------------------------------------------- /.github/workflows/R-CMD-check.yaml: -------------------------------------------------------------------------------- 1 | on: 2 | push: 3 | branches: 4 | - master 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | name: R-CMD-check 10 | 11 | jobs: 12 | R-CMD-check: 13 | runs-on: ${{ matrix.config.os }} 14 | 15 | name: ${{ matrix.config.os }} (${{ matrix.config.r }}) 16 | 17 | strategy: 18 | fail-fast: false 19 | matrix: 20 | config: 21 | - {os: macOS-latest, r: '4.0'} 22 | - {os: windows-latest, r: 'devel'} 23 | - {os: windows-latest, r: '4.0'} 24 | - {os: ubuntu-16.04, r: '4.0', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 25 | - {os: ubuntu-16.04, r: '3.6', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 26 | - {os: ubuntu-16.04, r: '3.5', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 27 | - {os: ubuntu-16.04, r: '3.4', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 28 | - {os: ubuntu-16.04, r: '3.3', rspm: "https://packagemanager.rstudio.com/cran/__linux__/xenial/latest"} 29 | 30 | env: 31 | R_REMOTES_NO_ERRORS_FROM_WARNINGS: true 32 | RSPM: ${{ matrix.config.rspm }} 33 | 34 | steps: 35 | - uses: actions/checkout@v2 36 | 37 | - uses: r-lib/actions/setup-r@master 38 | with: 39 | r-version: ${{ matrix.config.r }} 40 | 41 | - uses: r-lib/actions/setup-pandoc@master 42 | 43 | - name: Query dependencies 44 | run: | 45 | install.packages('remotes') 46 | saveRDS(remotes::dev_package_deps(dependencies = TRUE), ".github/depends.Rds", version = 2) 47 | shell: Rscript {0} 48 | 49 | - name: Cache R packages 50 | if: runner.os != 'Windows' 51 | uses: actions/cache@v1 52 | with: 53 | path: ${{ env.R_LIBS_USER }} 54 | key: ${{ runner.os }}-r-${{ matrix.config.r }}-1-${{ hashFiles('.github/depends.Rds') }} 55 | restore-keys: ${{ runner.os }}-r-${{ matrix.config.r }}-1- 56 | 57 | - name: Install system dependencies 58 | if: runner.os == 'Linux' 59 | env: 60 | RHUB_PLATFORM: linux-x86_64-ubuntu-gcc 61 | run: | 62 | Rscript -e "remotes::install_github('r-hub/sysreqs')" 63 | sysreqs=$(Rscript -e "cat(sysreqs::sysreq_commands('DESCRIPTION'))") 64 | sudo -s eval "$sysreqs" 65 | 66 | - name: Install dependencies 67 | run: | 68 | remotes::install_deps(dependencies = TRUE) 69 | remotes::install_cran("rcmdcheck") 70 | shell: Rscript {0} 71 | 72 | - name: Session info 73 | run: | 74 | options(width = 100) 75 | pkgs <- installed.packages()[, "Package"] 76 | sessioninfo::session_info(pkgs, include_base = TRUE) 77 | shell: Rscript {0} 78 | 79 | - name: Check 80 | env: 81 | _R_CHECK_CRAN_INCOMING_: false 82 | run: rcmdcheck::rcmdcheck(args = c("--no-manual", "--as-cran"), error_on = "warning", check_dir = "check") 83 | shell: Rscript {0} 84 | 85 | - name: Show testthat output 86 | if: always() 87 | run: find check -name 'testthat.Rout*' -exec cat '{}' \; || true 88 | shell: bash 89 | 90 | - name: Upload check results 91 | if: failure() 92 | uses: actions/upload-artifact@master 93 | with: 94 | name: ${{ runner.os }}-r${{ matrix.config.r }}-results 95 | path: check 96 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .Rproj.user 2 | .Rhistory 3 | .RData 4 | README.html 5 | -------------------------------------------------------------------------------- /DESCRIPTION: -------------------------------------------------------------------------------- 1 | Package: secret 2 | Title: Share Sensitive Information in R Packages 3 | Version: 1.1.0.9000 4 | Authors@R: c(person("Gábor", "Csárdi", role=c("aut", "cre"), 5 | email="csardi.gabor@gmail.com"), 6 | person("Andrie", "de Vries", role=c("aut"), 7 | email="apdevries@gmail.com")) 8 | Description: Allow sharing sensitive information, for example passwords, 9 | 'API' keys, etc., in R packages, using public key cryptography. 10 | License: MIT + file LICENSE 11 | LazyData: true 12 | URL: https://github.com/gaborcsardi/secret#readme 13 | BugReports: https://github.com/gaborcsardi/secret/issues 14 | RoxygenNote: 7.1.0 15 | Roxygen: list(markdown = TRUE) 16 | Imports: 17 | assertthat, 18 | openssl, 19 | rprojroot, 20 | curl, 21 | jsonlite 22 | Suggests: 23 | covr, 24 | mockery, 25 | testthat, 26 | knitr, 27 | rmarkdown, 28 | withr 29 | Encoding: UTF-8 30 | VignetteBuilder: knitr 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | YEAR: 2016-2017 2 | COPYRIGHT HOLDER: Gábor Csárdi, Andrie de Vries 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | all: inst/README.markdown 3 | 4 | inst/README.markdown: inst/README.Rmd inst/vignette_child/child.Rmd 5 | Rscript -e "library(knitr); knit('$<', output = '$@', quiet = TRUE)" 6 | -------------------------------------------------------------------------------- /NAMESPACE: -------------------------------------------------------------------------------- 1 | # Generated by roxygen2: do not edit by hand 2 | 3 | export(add_github_user) 4 | export(add_secret) 5 | export(add_travis_user) 6 | export(add_user) 7 | export(create_package_vault) 8 | export(create_vault) 9 | export(delete_secret) 10 | export(delete_user) 11 | export(get_github_key) 12 | export(get_secret) 13 | export(get_travis_key) 14 | export(list_owners) 15 | export(list_secrets) 16 | export(list_users) 17 | export(local_key) 18 | export(share_secret) 19 | export(unshare_secret) 20 | export(update_secret) 21 | importFrom(assertthat,"on_failure<-") 22 | importFrom(assertthat,assert_that) 23 | importFrom(assertthat,is.count) 24 | importFrom(curl,curl) 25 | importFrom(curl,curl_fetch_memory) 26 | importFrom(curl,handle_setheaders) 27 | importFrom(curl,new_handle) 28 | importFrom(jsonlite,fromJSON) 29 | importFrom(openssl,aes_cbc_decrypt) 30 | importFrom(openssl,aes_cbc_encrypt) 31 | importFrom(openssl,aes_keygen) 32 | importFrom(openssl,fingerprint) 33 | importFrom(openssl,read_key) 34 | importFrom(openssl,read_pubkey) 35 | importFrom(openssl,rsa_decrypt) 36 | importFrom(openssl,rsa_encrypt) 37 | importFrom(openssl,write_pem) 38 | importFrom(rprojroot,find_package_root_file) 39 | -------------------------------------------------------------------------------- /NEWS.md: -------------------------------------------------------------------------------- 1 | 2 | # dev 3 | 4 | # 1.1.0 5 | 6 | * New functions `get_github_key()` and `get_travis_key()` to retrieve 7 | public keys from GitHub and Travis (#23). 8 | 9 | * `add_travis_user()` works correctly again, now it uses the Travis V2 API 10 | at https://api.travis-ci.com. 11 | 12 | * `add_github_user()` now works correctly when not the first key is 13 | added (#21, @jiwalker-usgs). 14 | 15 | * `get_secret()` is now faster, because it uses fingerprint matching 16 | instead of trying to decrypt every file (#19, @jiwalker-usgs). 17 | 18 | * `list_secrets()` now works if `vault` is not specified 19 | (#22, @AlexAxthelm). 20 | 21 | # 1.0.0 22 | 23 | First public release. 24 | -------------------------------------------------------------------------------- /R/assertions.R: -------------------------------------------------------------------------------- 1 | 2 | #' @importFrom assertthat assert_that on_failure<- 3 | 4 | is_string <- function(x) { 5 | is.character(x) && length(x) == 1 && !is.na(x) 6 | } 7 | 8 | on_failure(is_string) <- function(call, env) { 9 | paste0(deparse(call$x), " is not a string (length 1 character)") 10 | } 11 | 12 | # -------------------------------------------------------------------- 13 | 14 | is_email_address <- function(x) { 15 | is_string(x) && grepl("^[-_\\.\\+@a-zA-Z0-9]+$", x) 16 | } 17 | 18 | on_failure(is_email_address) <- function(call, env) { 19 | paste0(deparse(call$x), " is not an email address") 20 | } 21 | 22 | # -------------------------------------------------------------------- 23 | 24 | is_email_addresses <- function(x) { 25 | is.character(x) && all(vapply(x, is_email_address, TRUE)) 26 | } 27 | 28 | on_failure(is_email_addresses) <- function(call, env) { 29 | paste0(deparse(call$x), " must be a vector of email addresses") 30 | } 31 | 32 | is_valid_user <- function(email, vault){ 33 | user_file <- get_user_file(vault, email) 34 | file.exists(user_file) 35 | } 36 | 37 | on_failure(is_valid_user) <- function(call, env) { 38 | paste0(deparse(call$email), " does not exist as a user") 39 | } 40 | 41 | 42 | # -------------------------------------------------------------------- 43 | 44 | is_valid_name <- function(x) { 45 | is_string(x) && nzchar(x) && grepl("^[-_\\.a-zA-Z0-9]+$", x) 46 | } 47 | 48 | on_failure(is_valid_name) <- function(call, env) { 49 | paste0( 50 | deparse(call$x), 51 | " is not a valid key. Keys may contain alphanumeric characters, ", 52 | " underscores, dashes and dots and the empty string is not a valid key." 53 | ) 54 | } 55 | 56 | # -------------------------------------------------------------------- 57 | 58 | is_valid_dir <- function(x) { 59 | is_string(x) && is_dir(x) 60 | } 61 | 62 | on_failure(is_valid_dir) <- function(call, env) { 63 | paste0( 64 | deparse(call$x), 65 | " does not exist, or is not a valid directory." 66 | ) 67 | } 68 | -------------------------------------------------------------------------------- /R/keys.R: -------------------------------------------------------------------------------- 1 | 2 | #' Read local secret key. 3 | #' 4 | #' Reads a local secret key from disk. The location of this file can be 5 | #' specified in the `USER_KEY` environment variable. 6 | #' If this environment variable does not exist, then attempts to read the 7 | #' key from: 8 | #' * `~/.ssh/id_rsa`, and 9 | #' * `~/.ssh/id_rsa.pem`. 10 | #' 11 | #' The location of the key is defined by: 12 | #' ``` 13 | #' Sys.getenv("USER_KEY") 14 | #' ``` 15 | #' 16 | #' To use a local in a different location, set an environment variable: 17 | #' ``` 18 | #' Sys.setenv(USER_KEY = "path/to/private/key") 19 | #' ``` 20 | #' 21 | #' @family secret functions 22 | #' @export 23 | #' @importFrom openssl read_key 24 | 25 | local_key <- function() { 26 | path <- Sys.getenv("USER_KEY") 27 | if (nzchar(path)) { 28 | read_key(path) 29 | } else if (file.exists(path2 <- "~/.ssh/id_rsa")) { 30 | read_key(path2) 31 | } else if (file.exists(path3 <- "~/.ssh/id_rsa.pem")) { 32 | read_key(path3) 33 | } else { 34 | stop("No suitable user key found.") 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /R/package.R: -------------------------------------------------------------------------------- 1 | 2 | #' Share Sensitive Information in R Packages. 3 | #' 4 | #' Allow sharing sensitive information, for example passwords, API keys, 5 | #' or other information in R packages, using public key cryptography. 6 | #' 7 | #' A vault is a directory, typically inside an R package, that 8 | #' stores a number of secrets. Each secret is shared among a group of 9 | #' users. Users are identified using their public keys. 10 | #' 11 | #' The package implements the following operations: 12 | #' 13 | #' * Vault: 14 | #' - Creating a vault folder: [create_vault()] 15 | #' - Creating a package vault: [create_package_vault()] 16 | #' * User management: 17 | #' - Adding a user: [add_user()], [add_github_user()]. 18 | #' - Deleting a user: [delete_user()]. 19 | #' - Listing users: [list_users()]. 20 | #' * Keys: 21 | #' - Reading local private key: [local_key()] 22 | #' * Secrets: 23 | #' - Adding a secret: [add_secret()]. 24 | #' - Retrieving a secret: [get_secret()]. 25 | #' - Updating a secret: [update_secret()]. 26 | #' - Deleting a secret: [delete_secret()]. 27 | #' - List secrets: [list_secrets()]. 28 | #' - Sharing a secret: [share_secret()]. Query or set the set of 29 | #' users that have access to a secret. 30 | #' - Unsharing a secret: [unshare_secret()] 31 | #' 32 | #' @docType package 33 | #' @keywords package 34 | #' @name secret-package 35 | #' @aliases secret 36 | #' @author Gábor Csárdi and Andrie de Vries 37 | NULL 38 | -------------------------------------------------------------------------------- /R/secrets.R: -------------------------------------------------------------------------------- 1 | 2 | #' Add a new secret to the vault. 3 | #' 4 | #' By default, the newly added secret is not shared with other 5 | #' users. See the users argument if you want to change this. 6 | #' You can also use [share_secret()] later, to specify the users that 7 | #' have access to the secret. 8 | #' 9 | #' @param name Name of the secret, a string that can contain alphanumeric 10 | #' characters, underscores, dashes and dots. 11 | #' @param value Value of the secret, an arbitrary R object that 12 | #' will be serialized using [base::serialize()]. 13 | #' @param users Email addresses of users that will have access to the 14 | #' secret. (See [add_user()]) 15 | #' @param vault Vault location (starting point to find the vault). 16 | #' To create a vault, use [create_vault()] or [create_package_vault()]. 17 | #' If this is `NULL`, then `secret` tries to find the vault automatically: 18 | #' * If the `secret.vault` option is set to path, that is used as the 19 | #' starting point. 20 | #' * Otherwise, if the `R_SECRET_VAULT` environment variable is set to a 21 | #' path, that is used as a starting point. 22 | #' * Otherwise the current working directory is used as the starting 23 | #' point. 24 | #' 25 | #' If the starting point is a vault, that is used. Otherwise, if the 26 | #' starting point is in a package tree, the `inst/vault` folder is used 27 | #' within the package. If no vault can be found, an error is thrown. 28 | #' 29 | #' @family secret functions 30 | #' @export 31 | #' @importFrom openssl aes_keygen aes_cbc_encrypt read_pubkey rsa_encrypt 32 | #' @example inst/examples/example-secret.R 33 | 34 | add_secret <- function(name, value, users, vault = NULL) { 35 | assert_that(is_valid_name(name)) 36 | assert_that(is_email_addresses(users)) 37 | vault <- find_vault(vault) 38 | assert_that(secret_does_not_exist(vault, name)) 39 | assert_that(users_exist(vault, users)) 40 | 41 | ## Create an AES key for the secret, and store it 42 | key <- aes_keygen() 43 | store_secret_with_key(name, value, key, vault) 44 | 45 | ## Share it with the specified users 46 | share_secret_with_key(name, users, aeskey = key, vault = vault) 47 | 48 | invisible() 49 | } 50 | 51 | #' Retrieve a secret from the vault. 52 | #' 53 | #' @param name Name of the secret. 54 | #' @param key The private RSA key to use. It defaults to the current 55 | #' user's default key. 56 | #' @inheritParams add_secret 57 | #' 58 | #' @family secret functions 59 | #' @export 60 | #' @importFrom openssl rsa_decrypt aes_cbc_decrypt 61 | #' @example inst/examples/example-secret.R 62 | 63 | get_secret <- function(name, key = local_key(), vault = NULL) { 64 | assert_that(is_valid_name(name)) 65 | vault <- find_vault(vault) 66 | 67 | secret_file <- get_secret_file(vault, name) 68 | if (! file.exists(secret_file)) { 69 | stop("secret ", sQuote(name), " does not exist") 70 | } 71 | 72 | ## Try to decrypt all AES encryptions, to see if user has access 73 | aeskey <- try_get_aes_key(vault, name, key) 74 | if (is.null(aeskey)) stop("Access denied to secret ", sQuote(name)) 75 | 76 | secret <- unserialize(read_raw(secret_file)) 77 | data <- aes_cbc_decrypt(secret, aeskey) 78 | unserialize(data) 79 | } 80 | 81 | #' Update a secret in the vault. 82 | #' 83 | #' @inheritParams get_secret 84 | #' @inheritParams add_secret 85 | #' 86 | #' @family secret functions 87 | #' @export 88 | #' @importFrom openssl aes_keygen 89 | 90 | update_secret <- function(name, value, key = local_key(), vault = NULL) { 91 | assert_that(is_valid_name(name)) 92 | vault <- find_vault(vault) 93 | 94 | secret_file <- get_secret_file(vault, name) 95 | if (! file.exists(secret_file)) { 96 | stop("secret ", sQuote(name), " does not exist") 97 | } 98 | 99 | ## Need a new AES key, because we might have deleted some users since 100 | ## the last value was set. These users still have the old AES key, 101 | ## but they should not have access to the new value of the secret. 102 | ## See also https://github.com/gaborcsardi/secret/issues/10 103 | aeskey <- aes_keygen() 104 | 105 | ## Store the secret 106 | store_secret_with_key(name, value, aeskey, vault) 107 | 108 | ## Give access to the same users 109 | users <- get_secret_user_emails(vault, name) 110 | share_secret_with_key(name, users, aeskey = aeskey, vault = vault) 111 | 112 | invisible() 113 | } 114 | 115 | #' Remove a secret from the vault. 116 | #' 117 | #' @param name Name of the secret to delete. 118 | #' @inheritParams add_secret 119 | #' 120 | #' @family secret functions 121 | #' @export 122 | 123 | delete_secret <- function(name, vault = NULL) { 124 | assert_that(is_valid_name(name)) 125 | vault <- find_vault(vault) 126 | 127 | secret_file <- get_secret_file(vault, name) 128 | secret_dir <- dirname(secret_file) 129 | if (!file.exists(secret_dir)) { 130 | stop("Secret ", sQuote(name), " does not exist.") 131 | } 132 | 133 | unlink(secret_dir, recursive = TRUE) 134 | 135 | invisible() 136 | } 137 | 138 | #' List all secrets. 139 | #' 140 | #' Returns a data frame with secrets and emails that these are shared with. 141 | #' The emails are in a list-column, each element of the `email` column is 142 | #' a character vector. 143 | #' 144 | #' @inheritParams add_secret 145 | #' 146 | #' @family secret functions 147 | #' @return data.frame 148 | #' @export 149 | 150 | list_secrets <- function(vault = NULL) { 151 | vault <- find_vault(vault) 152 | assert_that(is_valid_dir(vault)) 153 | 154 | secrets <- list.files( 155 | file.path(vault, "secrets"), 156 | recursive = TRUE, 157 | full.names = FALSE, 158 | pattern = ".raw$" 159 | ) 160 | secrets <- gsub(".raw$", "", dirname(secrets)) 161 | secrets <- sort(secrets) 162 | 163 | users <- lapply( 164 | file.path(vault, "secrets", secrets), 165 | dir, 166 | pattern = "\\.enc$" 167 | ) 168 | users <- lapply(users, sub, pattern = "\\.enc$", replacement = "") 169 | 170 | data.frame( 171 | secret = secrets, 172 | email = I(users), 173 | stringsAsFactors = FALSE 174 | ) 175 | } 176 | 177 | #' Share a secret among some users. 178 | #' 179 | #' Use this function to extend the set of users that have access to a 180 | #' secret. The calling user must have access to the secret as well. 181 | #' 182 | #' @param key Private key that has access to the secret. (I.e. its 183 | #' corresponding public key is among the vault users.) 184 | #' @param users addresses of users that will have access to the secret. 185 | #' (See [add_user()]). 186 | #' @inheritParams add_secret 187 | #' 188 | #' @seealso [unshare_secret()], [list_owners()] to list users that have 189 | #' access to a secret. 190 | #' 191 | #' @family secret functions 192 | #' @export 193 | 194 | share_secret <- function(name, users, key = local_key(), vault = NULL) { 195 | assert_that(is_valid_name(name)) 196 | assert_that(is_email_addresses(users)) 197 | vault <- find_vault(vault) 198 | assert_that(secret_exists(vault, name)) 199 | assert_that(users_exist(vault, users)) 200 | 201 | aeskey <- try_get_aes_key(vault, name, key) 202 | if (is.null(aeskey)) stop("Access denied to secret ", sQuote(name)) 203 | share_secret_with_key(name, users, aeskey, vault) 204 | 205 | invisible() 206 | } 207 | 208 | #' List users that have access to a secret 209 | #' 210 | #' @inheritParams add_secret 211 | #' 212 | #' @family secret functions 213 | #' @export 214 | 215 | list_owners <- function(name, vault = NULL) { 216 | assert_that(is_valid_name(name)) 217 | vault <- find_vault(vault) 218 | get_secret_user_emails(name, vault = vault) 219 | } 220 | 221 | #' Unshare a secret among some users. 222 | #' 223 | #' Use this function to restrict the set of users that have access to a 224 | #' secret. Note that users may still have access to the secret, through 225 | #' version control history, or if they have a copy of the project. They 226 | #' will not have access to future values of the secret, though. 227 | #' 228 | #' @inheritParams add_secret 229 | #' 230 | #' @seealso [share_secret()] 231 | #' 232 | #' @family secret functions 233 | #' @export 234 | 235 | unshare_secret <- function(name, users, vault = NULL) { 236 | assert_that(is_valid_name(name)) 237 | assert_that(is_email_addresses(users)) 238 | vault <- find_vault(vault) 239 | assert_that(secret_exists(vault, name)) 240 | assert_that(users_exist(vault, users)) 241 | 242 | files <- vapply(users, get_secret_user_file, "", 243 | vault = vault, name = name) 244 | files <- Filter(file.exists, files) 245 | file.remove(files) 246 | 247 | invisible() 248 | } 249 | 250 | # Internals ------------------------------------------------------------- 251 | 252 | secret_exists <- function(vault, name) { 253 | file.exists(get_secret_file(vault, name)) 254 | } 255 | 256 | on_failure(secret_exists) <- function(call, env) { 257 | paste0("Secret ", deparse(call$name), " does not exist") 258 | } 259 | 260 | secret_does_not_exist <- function(vault, name) { 261 | ! secret_exists(vault, name) 262 | } 263 | 264 | on_failure(secret_does_not_exist) <- function(call, env) { 265 | paste0("Secret ", deparse(call$name), " already exists") 266 | } 267 | 268 | 269 | #' Share a secret, its AES key is known already. 270 | #' 271 | #' @param name Name of the secret. 272 | #' @param users Email addresses of users. 273 | #' @param aeskey AES key of the secret. 274 | #' @param vault Vault directory. 275 | #' 276 | #' @keywords internal 277 | 278 | share_secret_with_key <- function(name, users, aeskey, vault) { 279 | lapply(users, share_secret_with_key1, 280 | name = name, aeskey = aeskey, vault = vault) 281 | } 282 | 283 | #' Share a secret with a single user, AES key is known. 284 | #' 285 | #' @param name Name of the secret. 286 | #' @param email Email address of the user. 287 | #' @param aeskey The AES key of the secret. 288 | #' @param vault Vault directory. 289 | #' 290 | #' @keywords internal 291 | 292 | share_secret_with_key1 <- function(name, email, aeskey, vault) { 293 | secret_user_file <- get_secret_user_file(vault, name, email) 294 | rsa_key <- get_user_key(vault, email) 295 | encaes <- rsa_encrypt(serialize(aeskey, NULL), rsa_key) 296 | create_dir(dirname(secret_user_file)) 297 | writeBin(encaes, secret_user_file) 298 | } 299 | 300 | #' Try to get the AES key of a secret, using a private RSA key. 301 | #' 302 | #' We just try the private key against all encrypted copies of the 303 | #' AES key. If none of the succeed, then we return `NULL`. Otherwise 304 | #' we return the AES key, an `aes` object, from the `openssl` package. 305 | #' 306 | #' @keywords internal 307 | 308 | try_get_aes_key <- function(vault, name, key) { 309 | file <- get_secret_user_file_for_key(vault, name, key) 310 | if (is.null(file) || !file.exists(file)) return(NULL) 311 | 312 | tryCatch( 313 | unserialize(rsa_decrypt(read_raw(file), key = key)), 314 | error = function(e) NULL 315 | ) 316 | } 317 | 318 | #' Store a secret, encrypted with its AES key. 319 | #' 320 | #' @param name Name of secret. 321 | #' @param value Value of secret. 322 | #' @param key The AES key, an `aes` object from the `openssl` package. 323 | #' @param vault Vault directory. 324 | #' 325 | #' @keywords internal 326 | 327 | store_secret_with_key <- function(name, value, key, vault) { 328 | ## Encrypt the secret with it 329 | data <- serialize(value, NULL) 330 | enc <- aes_cbc_encrypt(data, key) 331 | 332 | ## Write it out 333 | secret_file <- get_secret_file(vault, name) 334 | create_dir(dirname(secret_file)) 335 | writeBin(serialize(enc, NULL), secret_file) 336 | } 337 | -------------------------------------------------------------------------------- /R/users.R: -------------------------------------------------------------------------------- 1 | 2 | #' Add a new user to the vault. 3 | #' 4 | #' By default the new user does not have access to any secrets. 5 | #' See [add_secret()] or [share_secret()] to give them access. 6 | #' 7 | #' @param email Email address of the user. This is used to identify 8 | #' users. 9 | #' @param public_key Public key of the user. This is used to encrypt 10 | #' the secrets for the different users. It can be 11 | #' * a string containing a PEM, 12 | #' * a file name that points to a PEM file, 13 | #' * a `pubkey` object created via the `openssl` package. 14 | #' @inheritParams add_secret 15 | #' 16 | #' @family user functions 17 | #' @export 18 | #' @importFrom openssl read_pubkey write_pem 19 | #' @example inst/examples/example-secret.R 20 | 21 | add_user <- function(email, public_key, vault = NULL) { 22 | assert_that(is_email_address(email)) 23 | vault <- find_vault(vault) 24 | user_file <- get_user_file(vault, email) 25 | if (file.exists(user_file)) { 26 | stop("User ", sQuote(email), " already exists in this vault. ", 27 | "To update it, remove the old key, and add the new one.") 28 | } 29 | key <- read_pubkey(public_key) 30 | write_pem(key, path = user_file) 31 | } 32 | 33 | #' Get the SSH public key of a GitHub user 34 | #' 35 | #' @param github_user GitHub username. 36 | #' @param i Which key to get, in case the user has multiple keys. 37 | #' `get_github_key()` retrieves the first key by default. 38 | #' @return Character scalar. 39 | #' 40 | #' @importFrom curl new_handle handle_setheaders curl_fetch_memory 41 | #' @export 42 | 43 | get_github_key <- function(github_user, i = 1) { 44 | url <- paste("https://api.github.com/users", github_user, "keys", 45 | sep = "/") 46 | 47 | ## Use GitHub token from GITHUB_PATH env var, if set 48 | pat <- Sys.getenv("GITHUB_PAT", "") 49 | if (pat != "") { 50 | h <- new_handle() 51 | handle_setheaders(h, Authorization = paste("token", pat)) 52 | resp <- curl_fetch_memory(url, handle = h) 53 | } else { 54 | resp <- curl_fetch_memory(url) 55 | } 56 | 57 | k <- fromJSON(rawToChar(resp$content)) 58 | key <- k$key 59 | key[i] 60 | } 61 | 62 | #' Add a user via their GitHub username. 63 | #' 64 | #' On GitHub, a user can upload multiple keys. This function will download 65 | #' the first key by default, but you can change this 66 | #' 67 | #' @param github_user User name on GitHub. 68 | #' @param email Email address of the github user. If NULL, constructs an 69 | #' email as `github-<>` 70 | #' @param i Integer, indicating which GitHub key to use (if more than one 71 | #' GitHub key exists). 72 | #' @inheritParams add_user 73 | #' 74 | #' @family user functions 75 | #' @export 76 | #' 77 | #' @importFrom assertthat is.count 78 | #' @example inst/examples/example-github.R 79 | #' @seealso [add_travis_user()] 80 | 81 | add_github_user <- function(github_user, email = NULL, vault = NULL, 82 | i = 1) { 83 | assert_that(is.count(i)) 84 | if (missing(email) || is.null(email)){ 85 | email <- paste0("github-", github_user) 86 | } 87 | key <- get_github_key(github_user, i = i) 88 | add_user(email = email, public_key = key, vault = vault) 89 | } 90 | 91 | #' Retrieve the public key of a Travis CI repository 92 | #' 93 | #' @param travis_repo The repository slug, e.g. `gaborcsardi/secret`. 94 | #' @return Character scalar, the key. If the repository does not exist, 95 | #' or it is not user in Travis CI, an HTTP 404 error is thrown. 96 | #' 97 | #' @export 98 | #' @importFrom curl curl new_handle handle_setheaders 99 | #' @importFrom jsonlite fromJSON 100 | 101 | get_travis_key <- function(travis_repo) { 102 | url <- paste("https://api.travis-ci.com/repos", travis_repo, "key", 103 | sep = "/") 104 | handle <- new_handle() 105 | handle_setheaders(handle, Accept = "application/vnd.travis-ci.2.1+json") 106 | r <- curl(url, handle = handle) 107 | k <- fromJSON(r) 108 | k <- k$key 109 | gsub(" RSA", "", k) 110 | } 111 | 112 | 113 | #' Add a user via their Travis repo. 114 | #' 115 | #' On Travis, every repo has a private/public key pair. This function adds a 116 | #' user and downloads the public key from Travis. 117 | #' 118 | #' @param travis_repo Name of Travis repository, usually in a format 119 | #' `<>/<>` 120 | #' @inheritParams add_user 121 | #' 122 | #' @family user functions 123 | #' @export 124 | #' @example inst/examples/example-travis.R 125 | 126 | add_travis_user <- function(travis_repo, email, vault = NULL) { 127 | if (missing(email) || is.null(email)){ 128 | email <- paste0("travis-", gsub("/", "-", travis_repo)) 129 | } 130 | key <- get_travis_key(travis_repo) 131 | add_user(email = email, public_key = key, vault = vault) 132 | } 133 | 134 | 135 | #' Delete a user. 136 | #' 137 | #' It also removes access of the user to all secrets, so if the user 138 | #' is re-added again, they will not have access to any secrets. 139 | #' 140 | #' @param email Email address of the user. 141 | #' @inheritParams add_secret 142 | #' 143 | #' @family user functions 144 | #' @export 145 | 146 | delete_user <- function(email, vault = NULL) { 147 | assert_that(is_email_address(email)) 148 | vault <- find_vault(vault) 149 | 150 | ## Check if user exists 151 | assert_that(is_valid_user(email, vault)) 152 | user_file <- get_user_file(vault, email) 153 | 154 | ## Check for orphaned secrets 155 | secrets <- list_secrets(vault) 156 | if (email %in% secrets$email) { 157 | orp <- vapply(secrets$email, identical, logical(1), "bar") 158 | warning( 159 | "Deleting user ", sQuote(email), " will leave orphaned secrets: ", 160 | paste(secrets$name[orp], collapse = ", ") 161 | ) 162 | } 163 | 164 | ## Remove everything in one go. This is still not atomic, of course... 165 | mysecrets <- list_user_secrets(vault, email) 166 | file.remove(user_file, mysecrets) 167 | 168 | invisible() 169 | } 170 | 171 | #' List users 172 | #' 173 | #' @inheritParams add_secret 174 | #' 175 | #' @family user functions 176 | #' @export 177 | 178 | list_users <- function(vault = NULL) { 179 | vault <- find_vault(vault) 180 | sub( 181 | "\\.pem$", "", 182 | dir(file.path(vault, "users"), pattern = "\\.pem$") 183 | ) 184 | } 185 | 186 | # Internals ------------------------------------------------------------- 187 | 188 | users_exist <- function(vault, users) { 189 | tryCatch( 190 | { lapply(users, get_user_key, vault = vault) ; TRUE }, 191 | error = function(e) FALSE 192 | ) 193 | } 194 | 195 | on_failure(users_exist) <- function(call, env) { 196 | paste0("Secret ", deparse(call$users), " do not exist") 197 | } 198 | 199 | #' @importFrom openssl fingerprint read_pubkey 200 | 201 | lookup_user <- function(key, vault) { 202 | if (is.character(key)) { 203 | key <- tryCatch( 204 | read_key(key), 205 | error = function(e) NULL 206 | ) 207 | if (is.null(key)) return(NULL) 208 | } 209 | fp <- fingerprint(key) 210 | for (pubkey in dir(file.path(vault, "users"), pattern = "\\.pem$")) { 211 | pubkeyfile <- file.path(vault, "users", pubkey) 212 | if (as.character(fp) == as.character(fingerprint(read_pubkey(pubkeyfile)))) { 213 | user <- sub("\\.pem$", "", pubkey) 214 | return(user) 215 | } 216 | } 217 | NULL 218 | } 219 | -------------------------------------------------------------------------------- /R/utils.R: -------------------------------------------------------------------------------- 1 | 2 | create_dir <- function(path) { 3 | dir.create(path, showWarnings = FALSE, recursive = TRUE) 4 | } 5 | 6 | is_dir <- function(x) { 7 | isTRUE(file.info(x)$isdir) 8 | } 9 | 10 | read_raw <- function(path) { 11 | readBin(path, "raw", n = file.info(path)$size) 12 | } 13 | -------------------------------------------------------------------------------- /R/vault.R: -------------------------------------------------------------------------------- 1 | 2 | #' Create a vault, as a folder or in an R package. 3 | #' 4 | #' A vault is a folder that contains information about users and the secrets 5 | #' they share. You can create a vault as either a standalone folder, or 6 | #' as part of a package. 7 | #' 8 | #' @details 9 | #' 10 | #' A vault is a folder with a specific structure, containing two 11 | #' directories: `users` and `secrets`. 12 | #' 13 | #' In `users`, each file contains a public key in PEM format. The name of 14 | #' the file is the identifier of the key, an arbitrary name. We suggest 15 | #' that you use email addresses to identify public keys. See also [add_user()]. 16 | #' 17 | #' In `secrets`, each secret is stored in its own directory. 18 | #' The directory of a secret contains 19 | #' 1. the secret, encrypted with its own AES key, and 20 | #' 2. the AES key, encrypted with the public keys of all users that 21 | #' have access to the secret, each in its own file. 22 | #' 23 | #' To add a secret, see [add_secret()] 24 | #' 25 | #' @section Creating a package folder: 26 | #' 27 | #' When you create a vault in a package, this vault is stored in the 28 | #' `inst/vault` directory of the package during development. At package 29 | #' install time, this folder is copied to the `vault` folder. 30 | #' 31 | #' @param path Path to the R package. A file or directory within the 32 | #' package is fine, too. If the vault directory already exists, a message 33 | #' is given, and the function does nothing. 34 | #' @return The directory of the vault, invisibly. 35 | #' 36 | #' @importFrom rprojroot find_package_root_file 37 | #' 38 | #' @export 39 | #' @seealso [add_user()], [add_secret()] 40 | 41 | create_package_vault <- function(path = ".") { 42 | vault <- package_vault_directory(path) 43 | if (file.exists(vault)) { 44 | message("Package vault already exists in ", sQuote(vault)) 45 | } else { 46 | create_vault(vault) 47 | } 48 | invisible(vault) 49 | } 50 | 51 | #' @rdname create_package_vault 52 | #' @export 53 | #' @example inst/examples/example-secret.R 54 | create_vault <- function(path) { 55 | create_dir(path) 56 | create_dir(file.path(path, "users")) 57 | create_dir(file.path(path, "secrets")) 58 | if (! file.exists(readme <- file.path(path, "README"))) { 59 | cat("This directory is a secret vault.", file = readme) 60 | } 61 | if (! file.exists(readme <- file.path(path, "users", "README"))) { 62 | cat("This directory is part of a secret vault.", file = readme) 63 | } 64 | if (! file.exists(readme <- file.path(path, "secrets", "README"))) { 65 | cat("This directory is part of a secret vault.", file = readme) 66 | } 67 | invisible(path) 68 | } 69 | 70 | 71 | # Internals ------------------------------------------------------------- 72 | 73 | package_vault_directory <- function(path) { 74 | assert_that(is_valid_dir(path)) 75 | root <- tryCatch( 76 | find_package_root_file(path = path), 77 | error = function(e) e 78 | ) 79 | if (inherits(root, "error")) { 80 | stop("No package or package vault found", call. = FALSE) 81 | } 82 | normalizePath(file.path(root, "inst", "vault"), mustWork = FALSE) 83 | } 84 | 85 | is_vault <- function(vault) { 86 | dir.exists(vault) && 87 | dir.exists(file.path(vault, "users")) && 88 | dir.exists(file.path(vault, "secrets")) 89 | } 90 | 91 | #' @importFrom rprojroot find_package_root_file 92 | 93 | find_vault <- function(vault) { 94 | if (is.null(vault)) { 95 | vault <- getOption("secret.vault", NA_character_) 96 | if (is.na(vault)) vault <- Sys.getenv("R_SECRET_VAULT", NA_character_) 97 | if (is.na(vault)) vault <- "." 98 | } 99 | 100 | if (is_vault(vault)) { 101 | vault 102 | } else { 103 | pkgroot <- tryCatch( 104 | find_package_root_file(path = vault), 105 | error = function(e) NA_character_ 106 | ) 107 | if (is.na(pkgroot)) { 108 | "." 109 | } else { 110 | file.path(pkgroot, "inst", "vault") 111 | } 112 | } 113 | } 114 | 115 | #' Get the file of a user (email) 116 | #' 117 | #' We assume that `vault` is a proper vault directory, and `email` is a 118 | #' valid email address. 119 | #' @param vault Vault directory. 120 | #' @param email Email address (or user name, in general). 121 | #' @return The path to the user's public key. (It might not exist yet.) 122 | #' 123 | #' @keywords internal 124 | 125 | get_user_file <- function(vault, email) { 126 | file.path(vault, "users", paste0(email, ".pem")) 127 | } 128 | 129 | get_user_key <- function(vault, email) { 130 | read_pubkey(get_user_file(vault, email)) 131 | } 132 | 133 | get_secret_file <- function(vault, name) { 134 | file.path(vault, "secrets", name, "secret.raw") 135 | } 136 | 137 | get_secret_user_file <- function(vault, name, email) { 138 | file.path(vault, "secrets", name, paste0(email, ".enc")) 139 | } 140 | 141 | get_secret_user_files <- function(vault, name) { 142 | dir( 143 | file.path(vault, "secrets", name), 144 | pattern = "\\.enc$", 145 | full.names = TRUE 146 | ) 147 | } 148 | 149 | get_secret_user_file_for_key <- function(vault, name, key) { 150 | email <- lookup_user(key = key, vault = vault) 151 | get_secret_user_file(vault, name, email) 152 | } 153 | 154 | get_secret_user_emails <- function(vault, name) { 155 | sub( 156 | "\\.enc$", "", 157 | basename(get_secret_user_files(vault, name)) 158 | ) 159 | } 160 | 161 | list_user_secrets <- function(vault, email) { 162 | z <- list.files(vault, pattern = paste0("^", email, ".enc"), 163 | full.names = TRUE, recursive = TRUE) 164 | normalizePath(z, winslash = "/") 165 | } 166 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | inst/README.markdown -------------------------------------------------------------------------------- /inst/README.Rmd: -------------------------------------------------------------------------------- 1 | 2 | ```{r, setup, echo = FALSE, message = FALSE} 3 | knitr::opts_chunk$set( 4 | comment = "#", 5 | tidy = FALSE, 6 | fig.width = 8, 7 | fig.height = 8) 8 | ``` 9 | 10 | [![Linux Build Status](https://travis-ci.org/gaborcsardi/secret.svg?branch=master)](https://travis-ci.org/gaborcsardi/secret) 11 | [![Windows Build status](https://ci.appveyor.com/api/projects/status/github/gaborcsardi/secret?svg=true)](https://ci.appveyor.com/project/gaborcsardi/secret) 12 | [![](http://www.r-pkg.org/badges/version/secret)](http://www.r-pkg.org/pkg/secret) 13 | [![CRAN RStudio mirror downloads](http://cranlogs.r-pkg.org/badges/secret)](http://www.r-pkg.org/pkg/secret) 14 | [![Coverage Status](https://img.shields.io/codecov/c/github/gaborcsardi/secret/master.svg)](https://codecov.io/github/gaborcsardi/secret?branch=master) 15 | 16 | Allow sharing sensitive information, for example passwords, 'API' keys, 17 | etc., in R packages, using public key cryptography. 18 | 19 | ## Disclaimer 20 | 21 | 1. Although the package authors did what they could to make sure that 22 | the package is secure and cryptographically sound, they are not 23 | security experts. 24 | 25 | 2. Memory areas for secrets, user passwords, passphrases, private keys and 26 | other sensitive information, are not securely cleaned after use! 27 | Technically, the local R process and other processes on the same 28 | computer, may have access to them. Never use this package on a public 29 | computer or any system that you don't trust. (Actually, never typing in 30 | any password on a public computer is good security practice, in general.) 31 | 32 | 3. Use this package at your own risk! 33 | 34 | ## Installation 35 | 36 | Install the package from CRAN: 37 | 38 | ```{r, eval = FALSE} 39 | install.packages("secret") 40 | ``` 41 | 42 | ## Usage 43 | 44 | ```{r, child="vignette_child/child.Rmd"} 45 | ``` 46 | 47 | ## License 48 | 49 | MIT © Gábor Csárdi, Andrie de Vries 50 | -------------------------------------------------------------------------------- /inst/README.markdown: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | [![R build status](https://github.com/gaborcsardi/secret/workflows/R-CMD-check/badge.svg)](https://github.com/gaborcsardi/secret/actions) 6 | [![](http://www.r-pkg.org/badges/version/secret)](http://www.r-pkg.org/pkg/secret) 7 | [![CRAN RStudio mirror downloads](http://cranlogs.r-pkg.org/badges/secret)](http://www.r-pkg.org/pkg/secret) 8 | [![Coverage Status](https://img.shields.io/codecov/c/github/gaborcsardi/secret/master.svg)](https://codecov.io/github/gaborcsardi/secret?branch=master) 9 | 10 | 11 | Allow sharing sensitive information, for example passwords, 'API' keys, 12 | etc., in R packages, using public key cryptography. 13 | 14 | ## Disclaimer 15 | 16 | 1. Although the package authors did what they could to make sure that 17 | the package is secure and cryptographically sound, they are not 18 | security experts. 19 | 20 | 2. Memory areas for secrets, user passwords, passpharases, private keys and 21 | other sensitive information, are not securely cleaned after use! 22 | Technically, the local R process and other processes on the same 23 | computer, may have access to them. Never use this package on a public 24 | computer or any system that you don't trust. (Actually, never typing in 25 | any password on a public computer is good security practice, in general.) 26 | 27 | 3. Use this package at your own risk! 28 | 29 | ## Installation 30 | 31 | Install the package from CRAN: 32 | 33 | ```{r, eval = FALSE} 34 | install.packages("secret") 35 | ``` 36 | 37 | ## Usage 38 | 39 | 40 | 41 | ### Load the package: 42 | 43 | 44 | ```r 45 | library(secret) 46 | ``` 47 | 48 | ### Set up your keys: 49 | 50 | Ensure you know the location of your public and private keys. In Linux this is usually the folder `~/.ssh`, so on Windows you may want to choose the same folder. 51 | 52 | By default, the package looks for your private key at 53 | 54 | 1. `~/.ssh/id_rsa` 55 | 1. `~/.ssh/id_rsa.pem`. 56 | 57 | You can change this default by setting an environment variable `USER_KEY`: 58 | 59 | 60 | ```r 61 | # This is optional - only do this if you want to change the default location 62 | Sys.setenv(USER_KEY = "path/to/private/key") 63 | ``` 64 | 65 | Test that the package can read your key. This might fail if you don't have a key at `~/.ssh/id_rsa`, or if your private key has a pass phrase and R in running in non-interactive mode. 66 | 67 | 68 | ```r 69 | library(secret) 70 | try(local_key(), silent = TRUE) 71 | ``` 72 | 73 | ``` 74 | # Please enter private key passphrase: 75 | ``` 76 | 77 | ### Create a vault: 78 | 79 | You can create a vault by using `create_vault()` 80 | 81 | 82 | ```r 83 | vault <- file.path(tempdir(), ".vault") 84 | dir.create(vault) 85 | create_vault(vault) 86 | ``` 87 | 88 | A vault consists of two folders for: 89 | 90 | * `users`: contains user and their public keys 91 | * `secrets`: contains the encrypted secrets 92 | 93 | 94 | ```r 95 | dir(vault) 96 | ``` 97 | 98 | ``` 99 | # [1] "README" "secrets" "users" 100 | ``` 101 | 102 | Alternatively, you can create a vault in an R package: 103 | 104 | 105 | ```r 106 | pkg_root <- "/path/to/package" 107 | create_package_vault(pkg_root) 108 | ``` 109 | 110 | 111 | ### Add users to the vault: 112 | 113 | To add a user to the vault, you have to know their public key. 114 | 115 | The `secret` package contains some public and private keys you can use for demonstration purposes. 116 | 117 | 118 | ```r 119 | key_dir <- file.path(system.file(package = "secret"), "user_keys") 120 | alice_public_key <- file.path(key_dir, "alice.pub") 121 | alice_private_key <- file.path(key_dir, "alice.pem") 122 | openssl::read_pubkey(alice_public_key) 123 | ``` 124 | 125 | ``` 126 | # [2048-bit rsa public key] 127 | # md5: 1d858d316afb8b7d0efd69ec85dc7174 128 | ``` 129 | 130 | Add the public key of Alice to the vault: 131 | 132 | 133 | ```r 134 | add_user("alice", alice_public_key, vault = vault) 135 | ``` 136 | 137 | 138 | ### Add a secret using your public key. 139 | 140 | A secret can be any R object - this object will be serialised and then encrypted to the vault. 141 | 142 | 143 | ```r 144 | secret_to_keep <- c(password = "my_password") 145 | add_secret("secret_one", secret_to_keep, users = "alice", vault = vault) 146 | ``` 147 | 148 | 149 | ### Decrypt a secret by providing your private key: 150 | 151 | You can decrypt a secret if you have the private key that corresponds to the public key that was used to encrypt the secret, 152 | 153 | 154 | ```r 155 | get_secret("secret_one", key = alice_private_key, vault = vault) 156 | ``` 157 | 158 | ``` 159 | # password 160 | # "my_password" 161 | ``` 162 | 163 | 164 | ### Note for Windows users 165 | 166 | * If you use windows, you most likely created your keys using PuttyGen. Note that the key created by default from PuttyGen is not in OpenSSH format, so you have to convert your format first. To do this, use the `/Conversions/Export OpenSSH key` menu item in PuttyGen. 167 | 168 | * Note that the folder `~/.ssh` in Windows usually expands to `C:\\Users\\YOURNAME\\Documents\\.ssh`. You can find the full path by using: 169 | 170 | 171 | ```r 172 | normalizePath("~/.ssh", mustWork = FALSE) 173 | ``` 174 | 175 | ``` 176 | # [1] "/Users/gaborcsardi/.ssh" 177 | ``` 178 | 179 | 180 | 181 | 182 | ## License 183 | 184 | MIT © Gábor Csárdi, Andrie de Vries 185 | -------------------------------------------------------------------------------- /inst/examples/example-github.R: -------------------------------------------------------------------------------- 1 | \dontrun{ 2 | vault <- file.path(tempdir(), ".vault") 3 | create_vault(vault) 4 | 5 | add_github_user("hadley", vault = vault) 6 | list_users(vault = vault) 7 | delete_user("github-hadley", vault = vault) 8 | } 9 | -------------------------------------------------------------------------------- /inst/examples/example-secret.R: -------------------------------------------------------------------------------- 1 | 2 | \dontrun{ 3 | # The `secret` package contains some user keys for demonstration purposes. 4 | # In this example, Alice shares a secret with Bob using a vault. 5 | 6 | keys <- function(x){ 7 | file.path(system.file("user_keys", package = "secret"), x) 8 | } 9 | alice_public <- keys("alice.pub") 10 | alice_private <- keys("alice.pem") 11 | bob_public <- keys("bob.pub") 12 | bob_private <- keys("bob.pem") 13 | carl_private <- keys("carl.pem") 14 | 15 | # Create vault 16 | 17 | vault <- file.path(tempdir(), ".vault") 18 | if (dir.exists(vault)) unlink(vault) # ensure vault is empty 19 | create_vault(vault) 20 | 21 | # Add users with their public keys 22 | 23 | add_user("alice", public_key = alice_public, vault = vault) 24 | add_user("bob", public_key = bob_public, vault = vault) 25 | list_users(vault = vault) 26 | 27 | # Share a secret 28 | 29 | secret <- list(username = "user123", password = "Secret123!") 30 | 31 | add_secret("secret", value = secret, users = c("alice", "bob"), 32 | vault = vault) 33 | list_secrets(vault = vault) 34 | 35 | # Alice and Bob can decrypt the secret with their private keys 36 | # Note that you would not normally have access to the private key 37 | # of any of your collaborators! 38 | 39 | get_secret("secret", key = alice_private, vault = vault) 40 | get_secret("secret", key = bob_private, vault = vault) 41 | 42 | # But Carl can't decrypt the secret 43 | 44 | try( 45 | get_secret("secret", key = carl_private, vault = vault) 46 | ) 47 | 48 | # Unshare the secret 49 | 50 | unshare_secret("secret", users = "bob", vault = vault) 51 | try( 52 | get_secret("secret", key = bob_private, vault = vault) 53 | ) 54 | 55 | 56 | # Delete the secret 57 | 58 | delete_secret("secret", vault = vault) 59 | list_secrets(vault) 60 | 61 | # Delete the users 62 | 63 | delete_user("alice", vault = vault) 64 | delete_user("bob", vault = vault) 65 | list_users(vault) 66 | 67 | } 68 | -------------------------------------------------------------------------------- /inst/examples/example-travis.R: -------------------------------------------------------------------------------- 1 | 2 | \dontrun{ 3 | vault <- file.path(tempdir(), ".vault") 4 | create_vault(vault) 5 | 6 | add_travis_user("gaborcsardi/secret", vault = vault) 7 | list_users(vault = vault) 8 | delete_user("travis-gaborcsardi-secret", vault = vault) 9 | } 10 | -------------------------------------------------------------------------------- /inst/internals.md: -------------------------------------------------------------------------------- 1 | 2 | # Internals of the `secret` package 3 | 4 | ## Vaults 5 | 6 | A vault stores users and secrets. Each secret is shared among a subset 7 | of the users. The `users` subdirectory of the vault stores the users, and 8 | the `secrets` subdirectory stores the secrets. 9 | 10 | The vault directories and subdirectories may contain additional files, 11 | e.g. `README` files are added by `secret`, these are ignored. 12 | 13 | ## Users 14 | 15 | The `users` subdirectory of a vault contains its users. Each user has an 16 | id. We suggest to use the user's email address as id. Each user's public 17 | key is stored in the `.pem` file, in PEM format. 18 | 19 | `secret` can add user's via their id and public key, and can also add 20 | users from GitHub and Travis CI. 21 | 22 | ### Adding a user 23 | 24 | Simply creates the user's PEM file in the `users` directory. 25 | 26 | ### Deleting a user 27 | 28 | Removes the user's PEM file from the `users` directory, and also all 29 | `.enc` files from the subdirectories under `secrets`. A warning is 30 | given if removing the user created orphaned secrets. These are secrets 31 | that no user has access to after the deletion. 32 | 33 | Note that, after deleting a user, the secrets she had access to, are *not* 34 | re-encrypted, so with her private key, she still has access to them. 35 | (Not through the `secret` package, because the `.enc` files were removed, 36 | but the deleted user can manually decrypt the secret, with her private key.) 37 | 38 | This is deliberate, and not a security flaw, since the deleted user was 39 | already in the possession of these secrets, anyway. 40 | 41 | As soon as a secret is updated, it gets a new AES key, and the deleted user 42 | will have no access to its new value. 43 | 44 | ## Secrets 45 | 46 | The `secrets` subdirectory of a vault contains the secrets. Each secret has 47 | a name that serves as an id, and each secret is stored in its own 48 | subdirectory within `secrets`, named according to its name. Each secret has 49 | its own AES key, and is stored in the `secret.raw` file, encrypted with this 50 | key. 51 | 52 | For each user that has access to a secret, another file is stored right 53 | next to `secret.raw`. This file is named `.enc`, and it contains 54 | the AES key of the secret, encrypted with the user's public key. 55 | 56 | ### Adding a secret 57 | 58 | 1. A new AES key is generated for the secret. 59 | 2. The secret is stored, encrypted with this AES key. 60 | 3. Then the secret is shared among the specified users (see below). 61 | 62 | ### Querying a secret 63 | 64 | The input is a private key, and we try to decrypt all encrypted AES keys 65 | of the secret, with this AES key. If we fail, the private key has no access 66 | to the secret. Otherwise we use the decrypted AES key to decrypt the 67 | secret itself. 68 | 69 | ### Deleting a secret 70 | 71 | Simply removes the directory of the secret. Note that even users that have 72 | no access to a secret, can delete it. This is normal, if the user has 73 | access to the encrypted secret's files, she can delete them, anyway, 74 | without using the `secret` package. 75 | 76 | ### Changing a secret 77 | 78 | Changing a secret is equivalent to adding a new secret, except that we 79 | share the new secret with the exact users as the old one. It is necessary 80 | that we create a new AES key for the secret, whenever we change it, to 81 | ensure that users that have their access revoked cannot decrypt the new 82 | value of the secret. 83 | 84 | Note that anyone with access to the secret's files can change the contents 85 | of a secret. They can even share the new secret with the same exact users, 86 | using the users' public keys, stored in `users`. 87 | 88 | ### Sharing a secret 89 | 90 | Only a user that has access to a secret, can share it with other users. 91 | To share a secret, the sharing user must be able to decrypt it first, with 92 | her private key, and then encrypt it with the public keys of the newly 93 | added users. 94 | 95 | ### Unsharing a secret 96 | 97 | Note that after unsharing a secret among some users, they'll still have 98 | access to the current value of the secret. (Not through the `secret` 99 | package, but via accessing the files in the vault directly.) They will 100 | have no access to future values of the secret, however. 101 | 102 | ## Interruptions 103 | 104 | Operations on secrets and users are not neceassarily atomic. If an 105 | operation is interrupted, the vault might be in an inconsistent state. 106 | Our assumption is that the vault is under source code control, and rolling 107 | back to a consistent state is easy. 108 | -------------------------------------------------------------------------------- /inst/user_keys/alice.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEoAIBAAKCAQEAlRPF3mYbySLElQ4cyDonw/ePCVyIEMTSEtHKK9l2zAhxtW9K 3 | GifrrEqvhYe8Ttzvx3JPEbvgk9fBe3qMVSctK9GLWgpDVyBAI1PkZ2mVxijn123x 4 | uThLOyqXW93suXnDMT+hF1sv0HJqykWUTAVHa+lg3OtO0ui17Z3KcHY87BUZyTyh 5 | m2dZEjbpi+b/DMsqu64hTuqk0zR00jt5+QG+arj/aB7J6iDTBWc5fY7f66PHI4DG 6 | 1H2ks4/y3VARMV0OlFP6IsapjEN/lOb1YP9ErB7H9qlRN90czQgek7zwT4NbyQGi 7 | 7vSMuSZ61gZsZtMFhJSC41KUg+vZsY3RthjrTQIBJQKCAQA4aFi1BAqDdPBvwCaQ 8 | 82j+CqTTHBfO/l1TOp+GNpunJcoo+a1WAURqtHnD0n6aYW975hAUjEcjLwrwc48L 9 | dpt4XR/4jkL+Xzrc70+BEzHAmdsu61SLRbuZ2MOmOEu7zTUZjbKTN0KGOSF897uS 10 | YtzBDDJ9GsPhGchMEjDsSGoUIxTsblu/pHfDnpMMw1x5bbG6CJmn0/RqX/os4QHy 11 | awBuzntJUruQMHBMZ2kxWn4KA8XxjLuYgGyR3gbwXvj5l1YEpD+5aCwFVsss9O7/ 12 | sppFr3Nc4b1EQTX6GzsHJOQxZSBLbRNbhHM2n1UReXOuyh6q4M75b4HUgF4ohSXU 13 | DoIVAoGBAMvIdbJJYtfwj2bG0GauGmpbvxPkgIZIPYEhVItOayEDmABuRSWYDU/y 14 | zotLydRSgwNnfCV+8jhZihKDTPL8gYPXSo1I/9N+8MDEWDgOb4iJzxIwZeC3vgjk 15 | lQlQr7FiuzE/xHKW89nmOAlOVNcIkmXOoyW3Rh4VN2bXOXnfo9/jAoGBALtGx9hF 16 | P43BO0dH7bZCV/z0RgmclWG6+Uw6SyBgxPev/0tu7LSeJcf/h2FT6Cw1hIG9ovqw 17 | OARGOxcn44wzgnCMMsRvxc9s7Vy1K/yKN7azSSl1XI6U4HL4BouLmXYhGFUvnOrm 18 | IqbnJMeQXlFZc5fNw8x0QTGYhcMmGQYmBY8PAoGAMZGgFpw6ozqDvw3D/UzxqysS 19 | zXzFURh9qcnWS2YaDvMJTDZ/ha9dLx9irELeEQ0myXoJcOeHBsmr9qpQ/NWi9o5Q 20 | Z45MENmwLuOmwYb4iP7tLfAYx/VXvPlUrz0j0TOw/iREipNrv2GDQIis2lwH70AZ 21 | 2L3gn4iexf0AJJBYSzcCgYBgK0QHRieN+3F3oXoRfAOraK5X9nY5GtMLd+Fjp1Bj 22 | g+P2TbfSXwx7dWEx+qen/85sIxxXN+Vp+o0L5A0QpNRHofCOYukJpqNm9UAZ5hyc 23 | FuBhZce+1tQfXMUXOdku0rl32i38FVAQhIiCJ4phIBjDky0xo3tsfsE6srLne072 24 | bwKBgFZYXHoKYch7C4x8RgU2aBs8iB3gP8MbIAARNQpCQN7qgfp0lhLciBNhrNVD 25 | tan1RpNKvjPwp4h396mP+wRQUwSsQcba3PcLboqvkfu1FkpJMu2UWlSmeYyEW9bU 26 | 2J3PuRMYf/7/gH59IbBJzNRHDXMKKuAM/vCvCBMXBT+N9hcD 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /inst/user_keys/alice.pub: -------------------------------------------------------------------------------- 1 | ---- BEGIN SSH2 PUBLIC KEY ---- 2 | Comment: "r-secret-package-test-user1" 3 | AAAAB3NzaC1yc2EAAAABJQAAAQEAlRPF3mYbySLElQ4cyDonw/ePCVyIEMTSEtHK 4 | K9l2zAhxtW9KGifrrEqvhYe8Ttzvx3JPEbvgk9fBe3qMVSctK9GLWgpDVyBAI1Pk 5 | Z2mVxijn123xuThLOyqXW93suXnDMT+hF1sv0HJqykWUTAVHa+lg3OtO0ui17Z3K 6 | cHY87BUZyTyhm2dZEjbpi+b/DMsqu64hTuqk0zR00jt5+QG+arj/aB7J6iDTBWc5 7 | fY7f66PHI4DG1H2ks4/y3VARMV0OlFP6IsapjEN/lOb1YP9ErB7H9qlRN90czQge 8 | k7zwT4NbyQGi7vSMuSZ61gZsZtMFhJSC41KUg+vZsY3RthjrTQ== 9 | ---- END SSH2 PUBLIC KEY ---- 10 | -------------------------------------------------------------------------------- /inst/user_keys/bob.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEoQIBAAKCAQEA/H/2QxGuvIclz/8iA0a7aEwq1c1vwMTA739MLct2nNVnnxiO 3 | tPsm47XFXsOJGnBo/0sdx5zzOHOShv4kbBFzOL3ZDz4oFAMGRHtMRCUOtwMb2OP5 4 | C3uzbWiAfPWyZJDp9oUrxXNhVPVNRMzaZmhhyjqNZPFPgTEMUXevMmTInLQ+J8bO 5 | Z3EVBfThXJT67Mo0ajH7aq4VGXeRNMDTLEVjNZlUbSgvfoA4tRwNje/iKj1r5L6g 6 | B7AjV++knBdBwKLrgj0hutVENQnGsjd1QsaF9EmWDGkQE9v3jIBVkSOUg1F1d5wY 7 | wYzV7ipyyZWNDkvQR9l0z8CgE5j/QKcAe0+R8QIBJQKCAQANpg1Pu8RBisPGDcp8 8 | t7caZPtlgLnuvob4MGT7jnUdO/fDaRx4fEdKlDQuoskkBhODa9gYoLMze95oKWnA 9 | pv9PLNtM7pph8lNdpczFaclxrSQZj8hFz1XOkAbx/3FtOEP/fNHu/1FeiczTSVfp 10 | 3CD2LK2ybeifMxVsL/uh2+8rEI3tj6Dr4om0mEOSyHeXuXvesULbJLzOR3ply7nI 11 | S6j1bEaln3rMS4Qd3+pKwxmK1s/NaTCCQQ4k80z5vPKR59d08sC8sy5froLVfrBi 12 | qu2dVEtGmh80YXx8uAxnVtwRtEqMH7A6R2esyg7uHQWeaDKTGL4F4mnvHFvGrfT6 13 | NyOtAoGBAP5hs46tHRDKeiOIsCuHo7+CrZOvNtd6iKkQGqmQOwwSdNuyVlPTLb3t 14 | qMjSVW9kk3GsQhRezs1hIR68GQLz3K2TdIAqOQSV0CWGZHeWUVcWn6wAD+mfKUn3 15 | KcHiUl5bFAeycm5GAcKjewK+g6NZIX8zwh5pLknsKmM0BNTAdPXBAoGBAP4bMh6u 16 | dRCuedu4aCruPwFPtRLlT5i2xI/JbTyikqFm0V3Cy3vn3W6exNZUgRo9I/XgYoo/ 17 | LdBLOXXT1rdBhSv7PbPtqWW1HSy+Kz6LGwL/EnlyNeUrpy6SCa43Ck7gtgqk325F 18 | YhVOFVvV+QHCoqlyzZr5ta5f4foxD/el3ogxAoGANwBeLK/O7t+rtKfuvU3CikXE 19 | qk9lzbmhAfWk5mRfy0JCx7fbUGUC+KIWp/YgT2+xLVWtbDAstsjkkQYTPuieargZ 20 | MHfU+hKN3pmZLp0KqwvPfyKbqCJpys2oKeu+yEsLP+87Vhz5djEvWooOn9vrkR/y 21 | nsqiOX8rwmwcuGD2sa0CgYA9z0OK7CpQKnCrES4YR8oi6d/v1unZCdzJB3tvnSqV 22 | 9mpHPThqP1GJnD23mAO6Rjkt+E9SD14rv0VUAxiNcM1duac5mqyqCXXMnPXDGurl 23 | DZy1wdXCH2AEaLY/IiUaGvvZE1jxTyWyJ8ABlOmKznqX634exx5a2Q10NXKWL0P3 24 | nQKBgQDYpb3mdngTnlS1HKiwbOPpihPpBj0tIpiWvW2vdgRunBAUHtCdEGmMfZI4 25 | O93m85YzljJEYatSPoCxdu8B5OOEx8uWOjOi1S7U0HvOTZsCnE1Kh2UvymZiyS5F 26 | 9+Yz1uUDabx1nL5QgiDkXmBy20nUkFyGGsNb8Rt7fV2/jtDxFw== 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /inst/user_keys/bob.pub: -------------------------------------------------------------------------------- 1 | ---- BEGIN SSH2 PUBLIC KEY ---- 2 | Comment: "r-secret-package-test-user2" 3 | AAAAB3NzaC1yc2EAAAABJQAAAQEA/H/2QxGuvIclz/8iA0a7aEwq1c1vwMTA739M 4 | Lct2nNVnnxiOtPsm47XFXsOJGnBo/0sdx5zzOHOShv4kbBFzOL3ZDz4oFAMGRHtM 5 | RCUOtwMb2OP5C3uzbWiAfPWyZJDp9oUrxXNhVPVNRMzaZmhhyjqNZPFPgTEMUXev 6 | MmTInLQ+J8bOZ3EVBfThXJT67Mo0ajH7aq4VGXeRNMDTLEVjNZlUbSgvfoA4tRwN 7 | je/iKj1r5L6gB7AjV++knBdBwKLrgj0hutVENQnGsjd1QsaF9EmWDGkQE9v3jIBV 8 | kSOUg1F1d5wYwYzV7ipyyZWNDkvQR9l0z8CgE5j/QKcAe0+R8Q== 9 | ---- END SSH2 PUBLIC KEY ---- 10 | -------------------------------------------------------------------------------- /inst/user_keys/carl.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIIEowIBAAKCAQEA41m38W97Cr9ZLT1db453J3He72L4RB1Woe4oRWXPZERCoUi3 3 | ZllI0IsRHwCLRHOizMTq7ucY1mECEc4k5NhD0k8AWN1sRirWlAMX3dmhxO7X4wSa 4 | YaIIGC+HrvkM5SLG/D2miw6/4XPwf/6ECWrBQtvnXVHltZ9HItYB0kCEF2V0vtYf 5 | 7FXZe4DcifCfZPTgO48391cLpVtjz8GPoOm62QexXzWNwjnsbTvODvT+EQlYJ7lW 6 | vsIrOctmVs9pVkha19Txsys+a1gt5TrIMkL21a/fLBxEM4WwIobEFolzOmtwENa/ 7 | SwVsRdyqWxZl9npiG798FKuuit13jyt8fc0JuwIBJQKCAQBP4UCg79FI9x9VFY+I 8 | D3X5Giu75IC9/HhiaG8DoE/JSGpwBMrQ7vAR66wRz8I6pStcs+PXZfP4S5jxf8fG 9 | Bsu4mEw65gNymW38pyQPrVSDdoM0Fl/BcEgIf2cGICcgEyNRtMvP/kNq44vZ8aQD 10 | TwWh3o+PfaO8Wo6diXZC9BK8HBCsT5I0gzcYw1AjIFfzMEqH6TO3cpOHpFzQK7G6 11 | M5GET5xWsGXQqTi3UXQxw3Oa9hS7+Uvl1fzxMI5+Cy6UNBGzCZFAxJlixRAR/hfw 12 | UsinY4kDaM1CnjhRJdiziUA48Ve7ir8k5xRwdEyctaWzJPGGYgo0AlHOiXQsa9qe 13 | 2y7dAoGBAPpTou335qa3K8MIACJVBVNLb+OGfagQM9SMv6OW558WbveKBen8Us4L 14 | kl/tm3AZ0IKpVPZqG7KOYTATyvKcfqK0u4sKRj+PGwUarOxmuRl3RkRmIKauRxDT 15 | NG1n37e8cXKgGJABWHJpkSLXG/1i+IjxbSuQGYLhaIxUI63yge+ZAoGBAOiAxuCa 16 | DW6Ri6uUHkeqjX28EXJ6a+cTGK58Xi1G0cxdtiRA6tDCymlEN8D2LUTvaMHdiyXa 17 | MbxzGxbcRxmKIdSH/iu1el3+M6xIk0jkdt1anRTP+LfHo7BA+lBg6AS0hisf5IVq 18 | fb+v7NTZpqSpU8dJYqmhxqVJ7bRGcmBRAqhzAoGBAJubxiU5PFnLzxhe61OconIM 19 | THitjGGNiACqhPb88NiDkRZqjg39tu7I7El4Bq131KQ/vzFkjcIFeq8u5fDCJTS1 20 | iVZuK6r4F7cQlQGno36IafMqulnGR9oGv7mhb2RgYjJ/Mdz56t9WYSN+zDW6EB3G 21 | giH/oSfYOhIKyhIu9tM1AoGBALyEFt+fgIMp5t4lEaHkO18HML2vXmhUrDpyrTl3 22 | sQaRK+YLH0F7WAJS/MX4CQd2AelL2Js7SuvS816kxAbes6VnWHZ3d/ku+XbuvJv3 23 | gviqVdmGB+705ZXT0eBOkpwH/hUgxyARJ7AtxvHFOwIMwHgR/Pg9+wKO+BWg5x2p 24 | d8bZAoGBAManWd4/+J3bU17jQ/NiGRqPGWeDvKy7XEpZCXyOjXY4TiVDiDl/hqM6 25 | st40iOCX7dU1tx/c6xBSKcSXfPh25XbsN9sxv0ASrcQ3mcSE9L2QuLOtLiD+/LaV 26 | TZkLg6D08PCE+JE8SpB460fSQJsa71FsTQJ26RWGQXikHHdSRx/b 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /inst/user_keys/carl.pub: -------------------------------------------------------------------------------- 1 | ---- BEGIN SSH2 PUBLIC KEY ---- 2 | Comment: "rsa-key-20170309" 3 | AAAAB3NzaC1yc2EAAAABJQAAAQEA41m38W97Cr9ZLT1db453J3He72L4RB1Woe4o 4 | RWXPZERCoUi3ZllI0IsRHwCLRHOizMTq7ucY1mECEc4k5NhD0k8AWN1sRirWlAMX 5 | 3dmhxO7X4wSaYaIIGC+HrvkM5SLG/D2miw6/4XPwf/6ECWrBQtvnXVHltZ9HItYB 6 | 0kCEF2V0vtYf7FXZe4DcifCfZPTgO48391cLpVtjz8GPoOm62QexXzWNwjnsbTvO 7 | DvT+EQlYJ7lWvsIrOctmVs9pVkha19Txsys+a1gt5TrIMkL21a/fLBxEM4WwIobE 8 | FolzOmtwENa/SwVsRdyqWxZl9npiG798FKuuit13jyt8fc0Juw== 9 | ---- END SSH2 PUBLIC KEY ---- 10 | -------------------------------------------------------------------------------- /inst/vignette_child/child.Rmd: -------------------------------------------------------------------------------- 1 | 2 | ### Load the package: 3 | 4 | ```{r} 5 | library(secret) 6 | ``` 7 | 8 | ### Set up your keys: 9 | 10 | Ensure you know the location of your public and private keys. In Linux this is usually the folder `~/.ssh`, so on Windows you may want to choose the same folder. 11 | 12 | By default, the package looks for your private key at 13 | 14 | 1. `~/.ssh/id_rsa` 15 | 1. `~/.ssh/id_rsa.pem`. 16 | 17 | You can change this default by setting an environment variable `USER_KEY`: 18 | 19 | ```{r eval = FALSE} 20 | # This is optional - only do this if you want to change the default location 21 | Sys.setenv(USER_KEY = "path/to/private/key") 22 | ``` 23 | 24 | Test that the package can read your key. This might fail if you don't have a key at `~/.ssh/id_rsa`, or if your private key has a pass phrase and R in running in non-interactive mode. 25 | 26 | ```{r} 27 | library(secret) 28 | try(local_key(), silent = TRUE) 29 | ``` 30 | 31 | ### Create a vault: 32 | 33 | You can create a vault by using `create_vault()` 34 | 35 | ```{r} 36 | vault <- file.path(tempdir(), ".vault") 37 | dir.create(vault) 38 | create_vault(vault) 39 | ``` 40 | 41 | A vault consists of two folders for: 42 | 43 | * `users`: contains user and their public keys 44 | * `secrets`: contains the encrypted secrets 45 | 46 | ```{r} 47 | dir(vault) 48 | ``` 49 | 50 | Alternatively, you can create a vault in an R package: 51 | 52 | ```{r, eval = FALSE} 53 | pkg_root <- "/path/to/package" 54 | create_package_vault(pkg_root) 55 | ``` 56 | 57 | 58 | ### Add users to the vault: 59 | 60 | To add a user to the vault, you have to know their public key. 61 | 62 | The `secret` package contains some public and private keys you can use for demonstration purposes. 63 | 64 | ```{r} 65 | key_dir <- file.path(system.file(package = "secret"), "user_keys") 66 | alice_public_key <- file.path(key_dir, "alice.pub") 67 | alice_private_key <- file.path(key_dir, "alice.pem") 68 | openssl::read_pubkey(alice_public_key) 69 | ``` 70 | 71 | Add the public key of Alice to the vault: 72 | 73 | ```{r} 74 | add_user("alice", alice_public_key, vault = vault) 75 | ``` 76 | 77 | 78 | ### Add a secret using your public key. 79 | 80 | A secret can be any R object - this object will be serialised and then encrypted to the vault. 81 | 82 | ```{r} 83 | secret_to_keep <- c(password = "my_password") 84 | add_secret("secret_one", secret_to_keep, users = "alice", vault = vault) 85 | ``` 86 | 87 | 88 | ### Decrypt a secret by providing your private key: 89 | 90 | You can decrypt a secret if you have the private key that corresponds to the public key that was used to encrypt the secret, 91 | 92 | ```{r} 93 | get_secret("secret_one", key = alice_private_key, vault = vault) 94 | ``` 95 | 96 | 97 | ### Note for Windows users 98 | 99 | * If you use windows, you most likely created your keys using PuttyGen. Note that the key created by default from PuttyGen is not in OpenSSH format, so you have to convert your format first. To do this, use the `/Conversions/Export OpenSSH key` menu item in PuttyGen. 100 | 101 | * Note that the folder `~/.ssh` in Windows usually expands to `C:\\Users\\YOURNAME\\Documents\\.ssh`. You can find the full path by using: 102 | 103 | ```{r} 104 | normalizePath("~/.ssh", mustWork = FALSE) 105 | ``` 106 | 107 | 108 | 109 | -------------------------------------------------------------------------------- /man/add_github_user.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/users.R 3 | \name{add_github_user} 4 | \alias{add_github_user} 5 | \title{Add a user via their GitHub username.} 6 | \usage{ 7 | add_github_user(github_user, email = NULL, vault = NULL, i = 1) 8 | } 9 | \arguments{ 10 | \item{github_user}{User name on GitHub.} 11 | 12 | \item{email}{Email address of the github user. If NULL, constructs an 13 | email as \verb{github-<>}} 14 | 15 | \item{vault}{Vault location (starting point to find the vault). 16 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 17 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 18 | \itemize{ 19 | \item If the \code{secret.vault} option is set to path, that is used as the 20 | starting point. 21 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 22 | path, that is used as a starting point. 23 | \item Otherwise the current working directory is used as the starting 24 | point. 25 | } 26 | 27 | If the starting point is a vault, that is used. Otherwise, if the 28 | starting point is in a package tree, the \code{inst/vault} folder is used 29 | within the package. If no vault can be found, an error is thrown.} 30 | 31 | \item{i}{Integer, indicating which GitHub key to use (if more than one 32 | GitHub key exists).} 33 | } 34 | \description{ 35 | On GitHub, a user can upload multiple keys. This function will download 36 | the first key by default, but you can change this 37 | } 38 | \examples{ 39 | \dontrun{ 40 | vault <- file.path(tempdir(), ".vault") 41 | create_vault(vault) 42 | 43 | add_github_user("hadley", vault = vault) 44 | list_users(vault = vault) 45 | delete_user("github-hadley", vault = vault) 46 | } 47 | } 48 | \seealso{ 49 | \code{\link[=add_travis_user]{add_travis_user()}} 50 | 51 | Other user functions: 52 | \code{\link{add_travis_user}()}, 53 | \code{\link{add_user}()}, 54 | \code{\link{delete_user}()}, 55 | \code{\link{list_users}()} 56 | } 57 | \concept{user functions} 58 | -------------------------------------------------------------------------------- /man/add_secret.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{add_secret} 4 | \alias{add_secret} 5 | \title{Add a new secret to the vault.} 6 | \usage{ 7 | add_secret(name, value, users, vault = NULL) 8 | } 9 | \arguments{ 10 | \item{name}{Name of the secret, a string that can contain alphanumeric 11 | characters, underscores, dashes and dots.} 12 | 13 | \item{value}{Value of the secret, an arbitrary R object that 14 | will be serialized using \code{\link[base:serialize]{base::serialize()}}.} 15 | 16 | \item{users}{Email addresses of users that will have access to the 17 | secret. (See \code{\link[=add_user]{add_user()}})} 18 | 19 | \item{vault}{Vault location (starting point to find the vault). 20 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 21 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 22 | \itemize{ 23 | \item If the \code{secret.vault} option is set to path, that is used as the 24 | starting point. 25 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 26 | path, that is used as a starting point. 27 | \item Otherwise the current working directory is used as the starting 28 | point. 29 | } 30 | 31 | If the starting point is a vault, that is used. Otherwise, if the 32 | starting point is in a package tree, the \code{inst/vault} folder is used 33 | within the package. If no vault can be found, an error is thrown.} 34 | } 35 | \description{ 36 | By default, the newly added secret is not shared with other 37 | users. See the users argument if you want to change this. 38 | You can also use \code{\link[=share_secret]{share_secret()}} later, to specify the users that 39 | have access to the secret. 40 | } 41 | \examples{ 42 | 43 | \dontrun{ 44 | # The `secret` package contains some user keys for demonstration purposes. 45 | # In this example, Alice shares a secret with Bob using a vault. 46 | 47 | keys <- function(x){ 48 | file.path(system.file("user_keys", package = "secret"), x) 49 | } 50 | alice_public <- keys("alice.pub") 51 | alice_private <- keys("alice.pem") 52 | bob_public <- keys("bob.pub") 53 | bob_private <- keys("bob.pem") 54 | carl_private <- keys("carl.pem") 55 | 56 | # Create vault 57 | 58 | vault <- file.path(tempdir(), ".vault") 59 | if (dir.exists(vault)) unlink(vault) # ensure vault is empty 60 | create_vault(vault) 61 | 62 | # Add users with their public keys 63 | 64 | add_user("alice", public_key = alice_public, vault = vault) 65 | add_user("bob", public_key = bob_public, vault = vault) 66 | list_users(vault = vault) 67 | 68 | # Share a secret 69 | 70 | secret <- list(username = "user123", password = "Secret123!") 71 | 72 | add_secret("secret", value = secret, users = c("alice", "bob"), 73 | vault = vault) 74 | list_secrets(vault = vault) 75 | 76 | # Alice and Bob can decrypt the secret with their private keys 77 | # Note that you would not normally have access to the private key 78 | # of any of your collaborators! 79 | 80 | get_secret("secret", key = alice_private, vault = vault) 81 | get_secret("secret", key = bob_private, vault = vault) 82 | 83 | # But Carl can't decrypt the secret 84 | 85 | try( 86 | get_secret("secret", key = carl_private, vault = vault) 87 | ) 88 | 89 | # Unshare the secret 90 | 91 | unshare_secret("secret", users = "bob", vault = vault) 92 | try( 93 | get_secret("secret", key = bob_private, vault = vault) 94 | ) 95 | 96 | 97 | # Delete the secret 98 | 99 | delete_secret("secret", vault = vault) 100 | list_secrets(vault) 101 | 102 | # Delete the users 103 | 104 | delete_user("alice", vault = vault) 105 | delete_user("bob", vault = vault) 106 | list_users(vault) 107 | 108 | } 109 | } 110 | \seealso{ 111 | Other secret functions: 112 | \code{\link{delete_secret}()}, 113 | \code{\link{get_secret}()}, 114 | \code{\link{list_owners}()}, 115 | \code{\link{list_secrets}()}, 116 | \code{\link{local_key}()}, 117 | \code{\link{share_secret}()}, 118 | \code{\link{unshare_secret}()}, 119 | \code{\link{update_secret}()} 120 | } 121 | \concept{secret functions} 122 | -------------------------------------------------------------------------------- /man/add_travis_user.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/users.R 3 | \name{add_travis_user} 4 | \alias{add_travis_user} 5 | \title{Add a user via their Travis repo.} 6 | \usage{ 7 | add_travis_user(travis_repo, email, vault = NULL) 8 | } 9 | \arguments{ 10 | \item{travis_repo}{Name of Travis repository, usually in a format 11 | \verb{<>/<>}} 12 | 13 | \item{email}{Email address of the user. This is used to identify 14 | users.} 15 | 16 | \item{vault}{Vault location (starting point to find the vault). 17 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 18 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 19 | \itemize{ 20 | \item If the \code{secret.vault} option is set to path, that is used as the 21 | starting point. 22 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 23 | path, that is used as a starting point. 24 | \item Otherwise the current working directory is used as the starting 25 | point. 26 | } 27 | 28 | If the starting point is a vault, that is used. Otherwise, if the 29 | starting point is in a package tree, the \code{inst/vault} folder is used 30 | within the package. If no vault can be found, an error is thrown.} 31 | } 32 | \description{ 33 | On Travis, every repo has a private/public key pair. This function adds a 34 | user and downloads the public key from Travis. 35 | } 36 | \examples{ 37 | 38 | \dontrun{ 39 | vault <- file.path(tempdir(), ".vault") 40 | create_vault(vault) 41 | 42 | add_travis_user("gaborcsardi/secret", vault = vault) 43 | list_users(vault = vault) 44 | delete_user("travis-gaborcsardi-secret", vault = vault) 45 | } 46 | } 47 | \seealso{ 48 | Other user functions: 49 | \code{\link{add_github_user}()}, 50 | \code{\link{add_user}()}, 51 | \code{\link{delete_user}()}, 52 | \code{\link{list_users}()} 53 | } 54 | \concept{user functions} 55 | -------------------------------------------------------------------------------- /man/add_user.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/users.R 3 | \name{add_user} 4 | \alias{add_user} 5 | \title{Add a new user to the vault.} 6 | \usage{ 7 | add_user(email, public_key, vault = NULL) 8 | } 9 | \arguments{ 10 | \item{email}{Email address of the user. This is used to identify 11 | users.} 12 | 13 | \item{public_key}{Public key of the user. This is used to encrypt 14 | the secrets for the different users. It can be 15 | \itemize{ 16 | \item a string containing a PEM, 17 | \item a file name that points to a PEM file, 18 | \item a \code{pubkey} object created via the \code{openssl} package. 19 | }} 20 | 21 | \item{vault}{Vault location (starting point to find the vault). 22 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 23 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 24 | \itemize{ 25 | \item If the \code{secret.vault} option is set to path, that is used as the 26 | starting point. 27 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 28 | path, that is used as a starting point. 29 | \item Otherwise the current working directory is used as the starting 30 | point. 31 | } 32 | 33 | If the starting point is a vault, that is used. Otherwise, if the 34 | starting point is in a package tree, the \code{inst/vault} folder is used 35 | within the package. If no vault can be found, an error is thrown.} 36 | } 37 | \description{ 38 | By default the new user does not have access to any secrets. 39 | See \code{\link[=add_secret]{add_secret()}} or \code{\link[=share_secret]{share_secret()}} to give them access. 40 | } 41 | \examples{ 42 | 43 | \dontrun{ 44 | # The `secret` package contains some user keys for demonstration purposes. 45 | # In this example, Alice shares a secret with Bob using a vault. 46 | 47 | keys <- function(x){ 48 | file.path(system.file("user_keys", package = "secret"), x) 49 | } 50 | alice_public <- keys("alice.pub") 51 | alice_private <- keys("alice.pem") 52 | bob_public <- keys("bob.pub") 53 | bob_private <- keys("bob.pem") 54 | carl_private <- keys("carl.pem") 55 | 56 | # Create vault 57 | 58 | vault <- file.path(tempdir(), ".vault") 59 | if (dir.exists(vault)) unlink(vault) # ensure vault is empty 60 | create_vault(vault) 61 | 62 | # Add users with their public keys 63 | 64 | add_user("alice", public_key = alice_public, vault = vault) 65 | add_user("bob", public_key = bob_public, vault = vault) 66 | list_users(vault = vault) 67 | 68 | # Share a secret 69 | 70 | secret <- list(username = "user123", password = "Secret123!") 71 | 72 | add_secret("secret", value = secret, users = c("alice", "bob"), 73 | vault = vault) 74 | list_secrets(vault = vault) 75 | 76 | # Alice and Bob can decrypt the secret with their private keys 77 | # Note that you would not normally have access to the private key 78 | # of any of your collaborators! 79 | 80 | get_secret("secret", key = alice_private, vault = vault) 81 | get_secret("secret", key = bob_private, vault = vault) 82 | 83 | # But Carl can't decrypt the secret 84 | 85 | try( 86 | get_secret("secret", key = carl_private, vault = vault) 87 | ) 88 | 89 | # Unshare the secret 90 | 91 | unshare_secret("secret", users = "bob", vault = vault) 92 | try( 93 | get_secret("secret", key = bob_private, vault = vault) 94 | ) 95 | 96 | 97 | # Delete the secret 98 | 99 | delete_secret("secret", vault = vault) 100 | list_secrets(vault) 101 | 102 | # Delete the users 103 | 104 | delete_user("alice", vault = vault) 105 | delete_user("bob", vault = vault) 106 | list_users(vault) 107 | 108 | } 109 | } 110 | \seealso{ 111 | Other user functions: 112 | \code{\link{add_github_user}()}, 113 | \code{\link{add_travis_user}()}, 114 | \code{\link{delete_user}()}, 115 | \code{\link{list_users}()} 116 | } 117 | \concept{user functions} 118 | -------------------------------------------------------------------------------- /man/create_package_vault.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/vault.R 3 | \name{create_package_vault} 4 | \alias{create_package_vault} 5 | \alias{create_vault} 6 | \title{Create a vault, as a folder or in an R package.} 7 | \usage{ 8 | create_package_vault(path = ".") 9 | 10 | create_vault(path) 11 | } 12 | \arguments{ 13 | \item{path}{Path to the R package. A file or directory within the 14 | package is fine, too. If the vault directory already exists, a message 15 | is given, and the function does nothing.} 16 | } 17 | \value{ 18 | The directory of the vault, invisibly. 19 | } 20 | \description{ 21 | A vault is a folder that contains information about users and the secrets 22 | they share. You can create a vault as either a standalone folder, or 23 | as part of a package. 24 | } 25 | \details{ 26 | A vault is a folder with a specific structure, containing two 27 | directories: \code{users} and \code{secrets}. 28 | 29 | In \code{users}, each file contains a public key in PEM format. The name of 30 | the file is the identifier of the key, an arbitrary name. We suggest 31 | that you use email addresses to identify public keys. See also \code{\link[=add_user]{add_user()}}. 32 | 33 | In \code{secrets}, each secret is stored in its own directory. 34 | The directory of a secret contains 35 | \enumerate{ 36 | \item the secret, encrypted with its own AES key, and 37 | \item the AES key, encrypted with the public keys of all users that 38 | have access to the secret, each in its own file. 39 | } 40 | 41 | To add a secret, see \code{\link[=add_secret]{add_secret()}} 42 | } 43 | \section{Creating a package folder}{ 44 | 45 | 46 | When you create a vault in a package, this vault is stored in the 47 | \code{inst/vault} directory of the package during development. At package 48 | install time, this folder is copied to the \code{vault} folder. 49 | } 50 | 51 | \examples{ 52 | 53 | \dontrun{ 54 | # The `secret` package contains some user keys for demonstration purposes. 55 | # In this example, Alice shares a secret with Bob using a vault. 56 | 57 | keys <- function(x){ 58 | file.path(system.file("user_keys", package = "secret"), x) 59 | } 60 | alice_public <- keys("alice.pub") 61 | alice_private <- keys("alice.pem") 62 | bob_public <- keys("bob.pub") 63 | bob_private <- keys("bob.pem") 64 | carl_private <- keys("carl.pem") 65 | 66 | # Create vault 67 | 68 | vault <- file.path(tempdir(), ".vault") 69 | if (dir.exists(vault)) unlink(vault) # ensure vault is empty 70 | create_vault(vault) 71 | 72 | # Add users with their public keys 73 | 74 | add_user("alice", public_key = alice_public, vault = vault) 75 | add_user("bob", public_key = bob_public, vault = vault) 76 | list_users(vault = vault) 77 | 78 | # Share a secret 79 | 80 | secret <- list(username = "user123", password = "Secret123!") 81 | 82 | add_secret("secret", value = secret, users = c("alice", "bob"), 83 | vault = vault) 84 | list_secrets(vault = vault) 85 | 86 | # Alice and Bob can decrypt the secret with their private keys 87 | # Note that you would not normally have access to the private key 88 | # of any of your collaborators! 89 | 90 | get_secret("secret", key = alice_private, vault = vault) 91 | get_secret("secret", key = bob_private, vault = vault) 92 | 93 | # But Carl can't decrypt the secret 94 | 95 | try( 96 | get_secret("secret", key = carl_private, vault = vault) 97 | ) 98 | 99 | # Unshare the secret 100 | 101 | unshare_secret("secret", users = "bob", vault = vault) 102 | try( 103 | get_secret("secret", key = bob_private, vault = vault) 104 | ) 105 | 106 | 107 | # Delete the secret 108 | 109 | delete_secret("secret", vault = vault) 110 | list_secrets(vault) 111 | 112 | # Delete the users 113 | 114 | delete_user("alice", vault = vault) 115 | delete_user("bob", vault = vault) 116 | list_users(vault) 117 | 118 | } 119 | } 120 | \seealso{ 121 | \code{\link[=add_user]{add_user()}}, \code{\link[=add_secret]{add_secret()}} 122 | } 123 | -------------------------------------------------------------------------------- /man/delete_secret.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{delete_secret} 4 | \alias{delete_secret} 5 | \title{Remove a secret from the vault.} 6 | \usage{ 7 | delete_secret(name, vault = NULL) 8 | } 9 | \arguments{ 10 | \item{name}{Name of the secret to delete.} 11 | 12 | \item{vault}{Vault location (starting point to find the vault). 13 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 14 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 15 | \itemize{ 16 | \item If the \code{secret.vault} option is set to path, that is used as the 17 | starting point. 18 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 19 | path, that is used as a starting point. 20 | \item Otherwise the current working directory is used as the starting 21 | point. 22 | } 23 | 24 | If the starting point is a vault, that is used. Otherwise, if the 25 | starting point is in a package tree, the \code{inst/vault} folder is used 26 | within the package. If no vault can be found, an error is thrown.} 27 | } 28 | \description{ 29 | Remove a secret from the vault. 30 | } 31 | \seealso{ 32 | Other secret functions: 33 | \code{\link{add_secret}()}, 34 | \code{\link{get_secret}()}, 35 | \code{\link{list_owners}()}, 36 | \code{\link{list_secrets}()}, 37 | \code{\link{local_key}()}, 38 | \code{\link{share_secret}()}, 39 | \code{\link{unshare_secret}()}, 40 | \code{\link{update_secret}()} 41 | } 42 | \concept{secret functions} 43 | -------------------------------------------------------------------------------- /man/delete_user.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/users.R 3 | \name{delete_user} 4 | \alias{delete_user} 5 | \title{Delete a user.} 6 | \usage{ 7 | delete_user(email, vault = NULL) 8 | } 9 | \arguments{ 10 | \item{email}{Email address of the user.} 11 | 12 | \item{vault}{Vault location (starting point to find the vault). 13 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 14 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 15 | \itemize{ 16 | \item If the \code{secret.vault} option is set to path, that is used as the 17 | starting point. 18 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 19 | path, that is used as a starting point. 20 | \item Otherwise the current working directory is used as the starting 21 | point. 22 | } 23 | 24 | If the starting point is a vault, that is used. Otherwise, if the 25 | starting point is in a package tree, the \code{inst/vault} folder is used 26 | within the package. If no vault can be found, an error is thrown.} 27 | } 28 | \description{ 29 | It also removes access of the user to all secrets, so if the user 30 | is re-added again, they will not have access to any secrets. 31 | } 32 | \seealso{ 33 | Other user functions: 34 | \code{\link{add_github_user}()}, 35 | \code{\link{add_travis_user}()}, 36 | \code{\link{add_user}()}, 37 | \code{\link{list_users}()} 38 | } 39 | \concept{user functions} 40 | -------------------------------------------------------------------------------- /man/get_github_key.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/users.R 3 | \name{get_github_key} 4 | \alias{get_github_key} 5 | \title{Get the SSH public key of a GitHub user} 6 | \usage{ 7 | get_github_key(github_user, i = 1) 8 | } 9 | \arguments{ 10 | \item{github_user}{GitHub username.} 11 | 12 | \item{i}{Which key to get, in case the user has multiple keys. 13 | \code{get_github_key()} retrieves the first key by default.} 14 | } 15 | \value{ 16 | Character scalar. 17 | } 18 | \description{ 19 | Get the SSH public key of a GitHub user 20 | } 21 | -------------------------------------------------------------------------------- /man/get_secret.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{get_secret} 4 | \alias{get_secret} 5 | \title{Retrieve a secret from the vault.} 6 | \usage{ 7 | get_secret(name, key = local_key(), vault = NULL) 8 | } 9 | \arguments{ 10 | \item{name}{Name of the secret.} 11 | 12 | \item{key}{The private RSA key to use. It defaults to the current 13 | user's default key.} 14 | 15 | \item{vault}{Vault location (starting point to find the vault). 16 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 17 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 18 | \itemize{ 19 | \item If the \code{secret.vault} option is set to path, that is used as the 20 | starting point. 21 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 22 | path, that is used as a starting point. 23 | \item Otherwise the current working directory is used as the starting 24 | point. 25 | } 26 | 27 | If the starting point is a vault, that is used. Otherwise, if the 28 | starting point is in a package tree, the \code{inst/vault} folder is used 29 | within the package. If no vault can be found, an error is thrown.} 30 | } 31 | \description{ 32 | Retrieve a secret from the vault. 33 | } 34 | \examples{ 35 | 36 | \dontrun{ 37 | # The `secret` package contains some user keys for demonstration purposes. 38 | # In this example, Alice shares a secret with Bob using a vault. 39 | 40 | keys <- function(x){ 41 | file.path(system.file("user_keys", package = "secret"), x) 42 | } 43 | alice_public <- keys("alice.pub") 44 | alice_private <- keys("alice.pem") 45 | bob_public <- keys("bob.pub") 46 | bob_private <- keys("bob.pem") 47 | carl_private <- keys("carl.pem") 48 | 49 | # Create vault 50 | 51 | vault <- file.path(tempdir(), ".vault") 52 | if (dir.exists(vault)) unlink(vault) # ensure vault is empty 53 | create_vault(vault) 54 | 55 | # Add users with their public keys 56 | 57 | add_user("alice", public_key = alice_public, vault = vault) 58 | add_user("bob", public_key = bob_public, vault = vault) 59 | list_users(vault = vault) 60 | 61 | # Share a secret 62 | 63 | secret <- list(username = "user123", password = "Secret123!") 64 | 65 | add_secret("secret", value = secret, users = c("alice", "bob"), 66 | vault = vault) 67 | list_secrets(vault = vault) 68 | 69 | # Alice and Bob can decrypt the secret with their private keys 70 | # Note that you would not normally have access to the private key 71 | # of any of your collaborators! 72 | 73 | get_secret("secret", key = alice_private, vault = vault) 74 | get_secret("secret", key = bob_private, vault = vault) 75 | 76 | # But Carl can't decrypt the secret 77 | 78 | try( 79 | get_secret("secret", key = carl_private, vault = vault) 80 | ) 81 | 82 | # Unshare the secret 83 | 84 | unshare_secret("secret", users = "bob", vault = vault) 85 | try( 86 | get_secret("secret", key = bob_private, vault = vault) 87 | ) 88 | 89 | 90 | # Delete the secret 91 | 92 | delete_secret("secret", vault = vault) 93 | list_secrets(vault) 94 | 95 | # Delete the users 96 | 97 | delete_user("alice", vault = vault) 98 | delete_user("bob", vault = vault) 99 | list_users(vault) 100 | 101 | } 102 | } 103 | \seealso{ 104 | Other secret functions: 105 | \code{\link{add_secret}()}, 106 | \code{\link{delete_secret}()}, 107 | \code{\link{list_owners}()}, 108 | \code{\link{list_secrets}()}, 109 | \code{\link{local_key}()}, 110 | \code{\link{share_secret}()}, 111 | \code{\link{unshare_secret}()}, 112 | \code{\link{update_secret}()} 113 | } 114 | \concept{secret functions} 115 | -------------------------------------------------------------------------------- /man/get_travis_key.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/users.R 3 | \name{get_travis_key} 4 | \alias{get_travis_key} 5 | \title{Retrieve the public key of a Travis CI repository} 6 | \usage{ 7 | get_travis_key(travis_repo) 8 | } 9 | \arguments{ 10 | \item{travis_repo}{The repository slug, e.g. \code{gaborcsardi/secret}.} 11 | } 12 | \value{ 13 | Character scalar, the key. If the repository does not exist, 14 | or it is not user in Travis CI, an HTTP 404 error is thrown. 15 | } 16 | \description{ 17 | Retrieve the public key of a Travis CI repository 18 | } 19 | -------------------------------------------------------------------------------- /man/get_user_file.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/vault.R 3 | \name{get_user_file} 4 | \alias{get_user_file} 5 | \title{Get the file of a user (email)} 6 | \usage{ 7 | get_user_file(vault, email) 8 | } 9 | \arguments{ 10 | \item{vault}{Vault directory.} 11 | 12 | \item{email}{Email address (or user name, in general).} 13 | } 14 | \value{ 15 | The path to the user's public key. (It might not exist yet.) 16 | } 17 | \description{ 18 | We assume that \code{vault} is a proper vault directory, and \code{email} is a 19 | valid email address. 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/list_owners.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{list_owners} 4 | \alias{list_owners} 5 | \title{List users that have access to a secret} 6 | \usage{ 7 | list_owners(name, vault = NULL) 8 | } 9 | \arguments{ 10 | \item{name}{Name of the secret, a string that can contain alphanumeric 11 | characters, underscores, dashes and dots.} 12 | 13 | \item{vault}{Vault location (starting point to find the vault). 14 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 15 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 16 | \itemize{ 17 | \item If the \code{secret.vault} option is set to path, that is used as the 18 | starting point. 19 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 20 | path, that is used as a starting point. 21 | \item Otherwise the current working directory is used as the starting 22 | point. 23 | } 24 | 25 | If the starting point is a vault, that is used. Otherwise, if the 26 | starting point is in a package tree, the \code{inst/vault} folder is used 27 | within the package. If no vault can be found, an error is thrown.} 28 | } 29 | \description{ 30 | List users that have access to a secret 31 | } 32 | \seealso{ 33 | Other secret functions: 34 | \code{\link{add_secret}()}, 35 | \code{\link{delete_secret}()}, 36 | \code{\link{get_secret}()}, 37 | \code{\link{list_secrets}()}, 38 | \code{\link{local_key}()}, 39 | \code{\link{share_secret}()}, 40 | \code{\link{unshare_secret}()}, 41 | \code{\link{update_secret}()} 42 | } 43 | \concept{secret functions} 44 | -------------------------------------------------------------------------------- /man/list_secrets.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{list_secrets} 4 | \alias{list_secrets} 5 | \title{List all secrets.} 6 | \usage{ 7 | list_secrets(vault = NULL) 8 | } 9 | \arguments{ 10 | \item{vault}{Vault location (starting point to find the vault). 11 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 12 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 13 | \itemize{ 14 | \item If the \code{secret.vault} option is set to path, that is used as the 15 | starting point. 16 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 17 | path, that is used as a starting point. 18 | \item Otherwise the current working directory is used as the starting 19 | point. 20 | } 21 | 22 | If the starting point is a vault, that is used. Otherwise, if the 23 | starting point is in a package tree, the \code{inst/vault} folder is used 24 | within the package. If no vault can be found, an error is thrown.} 25 | } 26 | \value{ 27 | data.frame 28 | } 29 | \description{ 30 | Returns a data frame with secrets and emails that these are shared with. 31 | The emails are in a list-column, each element of the \code{email} column is 32 | a character vector. 33 | } 34 | \seealso{ 35 | Other secret functions: 36 | \code{\link{add_secret}()}, 37 | \code{\link{delete_secret}()}, 38 | \code{\link{get_secret}()}, 39 | \code{\link{list_owners}()}, 40 | \code{\link{local_key}()}, 41 | \code{\link{share_secret}()}, 42 | \code{\link{unshare_secret}()}, 43 | \code{\link{update_secret}()} 44 | } 45 | \concept{secret functions} 46 | -------------------------------------------------------------------------------- /man/list_users.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/users.R 3 | \name{list_users} 4 | \alias{list_users} 5 | \title{List users} 6 | \usage{ 7 | list_users(vault = NULL) 8 | } 9 | \arguments{ 10 | \item{vault}{Vault location (starting point to find the vault). 11 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 12 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 13 | \itemize{ 14 | \item If the \code{secret.vault} option is set to path, that is used as the 15 | starting point. 16 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 17 | path, that is used as a starting point. 18 | \item Otherwise the current working directory is used as the starting 19 | point. 20 | } 21 | 22 | If the starting point is a vault, that is used. Otherwise, if the 23 | starting point is in a package tree, the \code{inst/vault} folder is used 24 | within the package. If no vault can be found, an error is thrown.} 25 | } 26 | \description{ 27 | List users 28 | } 29 | \seealso{ 30 | Other user functions: 31 | \code{\link{add_github_user}()}, 32 | \code{\link{add_travis_user}()}, 33 | \code{\link{add_user}()}, 34 | \code{\link{delete_user}()} 35 | } 36 | \concept{user functions} 37 | -------------------------------------------------------------------------------- /man/local_key.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/keys.R 3 | \name{local_key} 4 | \alias{local_key} 5 | \title{Read local secret key.} 6 | \usage{ 7 | local_key() 8 | } 9 | \description{ 10 | Reads a local secret key from disk. The location of this file can be 11 | specified in the \code{USER_KEY} environment variable. 12 | If this environment variable does not exist, then attempts to read the 13 | key from: 14 | \itemize{ 15 | \item \verb{~/.ssh/id_rsa}, and 16 | \item \verb{~/.ssh/id_rsa.pem}. 17 | } 18 | } 19 | \details{ 20 | The location of the key is defined by:\preformatted{Sys.getenv("USER_KEY") 21 | } 22 | 23 | To use a local in a different location, set an environment variable:\preformatted{Sys.setenv(USER_KEY = "path/to/private/key") 24 | } 25 | } 26 | \seealso{ 27 | Other secret functions: 28 | \code{\link{add_secret}()}, 29 | \code{\link{delete_secret}()}, 30 | \code{\link{get_secret}()}, 31 | \code{\link{list_owners}()}, 32 | \code{\link{list_secrets}()}, 33 | \code{\link{share_secret}()}, 34 | \code{\link{unshare_secret}()}, 35 | \code{\link{update_secret}()} 36 | } 37 | \concept{secret functions} 38 | -------------------------------------------------------------------------------- /man/secret-package.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/package.R 3 | \docType{package} 4 | \name{secret-package} 5 | \alias{secret-package} 6 | \alias{secret} 7 | \title{Share Sensitive Information in R Packages.} 8 | \description{ 9 | Allow sharing sensitive information, for example passwords, API keys, 10 | or other information in R packages, using public key cryptography. 11 | } 12 | \details{ 13 | A vault is a directory, typically inside an R package, that 14 | stores a number of secrets. Each secret is shared among a group of 15 | users. Users are identified using their public keys. 16 | 17 | The package implements the following operations: 18 | \itemize{ 19 | \item Vault: 20 | \itemize{ 21 | \item Creating a vault folder: \code{\link[=create_vault]{create_vault()}} 22 | \item Creating a package vault: \code{\link[=create_package_vault]{create_package_vault()}} 23 | } 24 | \item User management: 25 | \itemize{ 26 | \item Adding a user: \code{\link[=add_user]{add_user()}}, \code{\link[=add_github_user]{add_github_user()}}. 27 | \item Deleting a user: \code{\link[=delete_user]{delete_user()}}. 28 | \item Listing users: \code{\link[=list_users]{list_users()}}. 29 | } 30 | \item Keys: 31 | \itemize{ 32 | \item Reading local private key: \code{\link[=local_key]{local_key()}} 33 | } 34 | \item Secrets: 35 | \itemize{ 36 | \item Adding a secret: \code{\link[=add_secret]{add_secret()}}. 37 | \item Retrieving a secret: \code{\link[=get_secret]{get_secret()}}. 38 | \item Updating a secret: \code{\link[=update_secret]{update_secret()}}. 39 | \item Deleting a secret: \code{\link[=delete_secret]{delete_secret()}}. 40 | \item List secrets: \code{\link[=list_secrets]{list_secrets()}}. 41 | \item Sharing a secret: \code{\link[=share_secret]{share_secret()}}. Query or set the set of 42 | users that have access to a secret. 43 | \item Unsharing a secret: \code{\link[=unshare_secret]{unshare_secret()}} 44 | } 45 | } 46 | } 47 | \author{ 48 | Gábor Csárdi and Andrie de Vries 49 | } 50 | \keyword{package} 51 | -------------------------------------------------------------------------------- /man/share_secret.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{share_secret} 4 | \alias{share_secret} 5 | \title{Share a secret among some users.} 6 | \usage{ 7 | share_secret(name, users, key = local_key(), vault = NULL) 8 | } 9 | \arguments{ 10 | \item{name}{Name of the secret, a string that can contain alphanumeric 11 | characters, underscores, dashes and dots.} 12 | 13 | \item{users}{addresses of users that will have access to the secret. 14 | (See \code{\link[=add_user]{add_user()}}).} 15 | 16 | \item{key}{Private key that has access to the secret. (I.e. its 17 | corresponding public key is among the vault users.)} 18 | 19 | \item{vault}{Vault location (starting point to find the vault). 20 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 21 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 22 | \itemize{ 23 | \item If the \code{secret.vault} option is set to path, that is used as the 24 | starting point. 25 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 26 | path, that is used as a starting point. 27 | \item Otherwise the current working directory is used as the starting 28 | point. 29 | } 30 | 31 | If the starting point is a vault, that is used. Otherwise, if the 32 | starting point is in a package tree, the \code{inst/vault} folder is used 33 | within the package. If no vault can be found, an error is thrown.} 34 | } 35 | \description{ 36 | Use this function to extend the set of users that have access to a 37 | secret. The calling user must have access to the secret as well. 38 | } 39 | \seealso{ 40 | \code{\link[=unshare_secret]{unshare_secret()}}, \code{\link[=list_owners]{list_owners()}} to list users that have 41 | access to a secret. 42 | 43 | Other secret functions: 44 | \code{\link{add_secret}()}, 45 | \code{\link{delete_secret}()}, 46 | \code{\link{get_secret}()}, 47 | \code{\link{list_owners}()}, 48 | \code{\link{list_secrets}()}, 49 | \code{\link{local_key}()}, 50 | \code{\link{unshare_secret}()}, 51 | \code{\link{update_secret}()} 52 | } 53 | \concept{secret functions} 54 | -------------------------------------------------------------------------------- /man/share_secret_with_key.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{share_secret_with_key} 4 | \alias{share_secret_with_key} 5 | \title{Share a secret, its AES key is known already.} 6 | \usage{ 7 | share_secret_with_key(name, users, aeskey, vault) 8 | } 9 | \arguments{ 10 | \item{name}{Name of the secret.} 11 | 12 | \item{users}{Email addresses of users.} 13 | 14 | \item{aeskey}{AES key of the secret.} 15 | 16 | \item{vault}{Vault directory.} 17 | } 18 | \description{ 19 | Share a secret, its AES key is known already. 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/share_secret_with_key1.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{share_secret_with_key1} 4 | \alias{share_secret_with_key1} 5 | \title{Share a secret with a single user, AES key is known.} 6 | \usage{ 7 | share_secret_with_key1(name, email, aeskey, vault) 8 | } 9 | \arguments{ 10 | \item{name}{Name of the secret.} 11 | 12 | \item{email}{Email address of the user.} 13 | 14 | \item{aeskey}{The AES key of the secret.} 15 | 16 | \item{vault}{Vault directory.} 17 | } 18 | \description{ 19 | Share a secret with a single user, AES key is known. 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/store_secret_with_key.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{store_secret_with_key} 4 | \alias{store_secret_with_key} 5 | \title{Store a secret, encrypted with its AES key.} 6 | \usage{ 7 | store_secret_with_key(name, value, key, vault) 8 | } 9 | \arguments{ 10 | \item{name}{Name of secret.} 11 | 12 | \item{value}{Value of secret.} 13 | 14 | \item{key}{The AES key, an \code{aes} object from the \code{openssl} package.} 15 | 16 | \item{vault}{Vault directory.} 17 | } 18 | \description{ 19 | Store a secret, encrypted with its AES key. 20 | } 21 | \keyword{internal} 22 | -------------------------------------------------------------------------------- /man/try_get_aes_key.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{try_get_aes_key} 4 | \alias{try_get_aes_key} 5 | \title{Try to get the AES key of a secret, using a private RSA key.} 6 | \usage{ 7 | try_get_aes_key(vault, name, key) 8 | } 9 | \description{ 10 | We just try the private key against all encrypted copies of the 11 | AES key. If none of the succeed, then we return \code{NULL}. Otherwise 12 | we return the AES key, an \code{aes} object, from the \code{openssl} package. 13 | } 14 | \keyword{internal} 15 | -------------------------------------------------------------------------------- /man/unshare_secret.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{unshare_secret} 4 | \alias{unshare_secret} 5 | \title{Unshare a secret among some users.} 6 | \usage{ 7 | unshare_secret(name, users, vault = NULL) 8 | } 9 | \arguments{ 10 | \item{name}{Name of the secret, a string that can contain alphanumeric 11 | characters, underscores, dashes and dots.} 12 | 13 | \item{users}{Email addresses of users that will have access to the 14 | secret. (See \code{\link[=add_user]{add_user()}})} 15 | 16 | \item{vault}{Vault location (starting point to find the vault). 17 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 18 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 19 | \itemize{ 20 | \item If the \code{secret.vault} option is set to path, that is used as the 21 | starting point. 22 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 23 | path, that is used as a starting point. 24 | \item Otherwise the current working directory is used as the starting 25 | point. 26 | } 27 | 28 | If the starting point is a vault, that is used. Otherwise, if the 29 | starting point is in a package tree, the \code{inst/vault} folder is used 30 | within the package. If no vault can be found, an error is thrown.} 31 | } 32 | \description{ 33 | Use this function to restrict the set of users that have access to a 34 | secret. Note that users may still have access to the secret, through 35 | version control history, or if they have a copy of the project. They 36 | will not have access to future values of the secret, though. 37 | } 38 | \seealso{ 39 | \code{\link[=share_secret]{share_secret()}} 40 | 41 | Other secret functions: 42 | \code{\link{add_secret}()}, 43 | \code{\link{delete_secret}()}, 44 | \code{\link{get_secret}()}, 45 | \code{\link{list_owners}()}, 46 | \code{\link{list_secrets}()}, 47 | \code{\link{local_key}()}, 48 | \code{\link{share_secret}()}, 49 | \code{\link{update_secret}()} 50 | } 51 | \concept{secret functions} 52 | -------------------------------------------------------------------------------- /man/update_secret.Rd: -------------------------------------------------------------------------------- 1 | % Generated by roxygen2: do not edit by hand 2 | % Please edit documentation in R/secrets.R 3 | \name{update_secret} 4 | \alias{update_secret} 5 | \title{Update a secret in the vault.} 6 | \usage{ 7 | update_secret(name, value, key = local_key(), vault = NULL) 8 | } 9 | \arguments{ 10 | \item{name}{Name of the secret.} 11 | 12 | \item{value}{Value of the secret, an arbitrary R object that 13 | will be serialized using \code{\link[base:serialize]{base::serialize()}}.} 14 | 15 | \item{key}{The private RSA key to use. It defaults to the current 16 | user's default key.} 17 | 18 | \item{vault}{Vault location (starting point to find the vault). 19 | To create a vault, use \code{\link[=create_vault]{create_vault()}} or \code{\link[=create_package_vault]{create_package_vault()}}. 20 | If this is \code{NULL}, then \code{secret} tries to find the vault automatically: 21 | \itemize{ 22 | \item If the \code{secret.vault} option is set to path, that is used as the 23 | starting point. 24 | \item Otherwise, if the \code{R_SECRET_VAULT} environment variable is set to a 25 | path, that is used as a starting point. 26 | \item Otherwise the current working directory is used as the starting 27 | point. 28 | } 29 | 30 | If the starting point is a vault, that is used. Otherwise, if the 31 | starting point is in a package tree, the \code{inst/vault} folder is used 32 | within the package. If no vault can be found, an error is thrown.} 33 | } 34 | \description{ 35 | Update a secret in the vault. 36 | } 37 | \seealso{ 38 | Other secret functions: 39 | \code{\link{add_secret}()}, 40 | \code{\link{delete_secret}()}, 41 | \code{\link{get_secret}()}, 42 | \code{\link{list_owners}()}, 43 | \code{\link{list_secrets}()}, 44 | \code{\link{local_key}()}, 45 | \code{\link{share_secret}()}, 46 | \code{\link{unshare_secret}()} 47 | } 48 | \concept{secret functions} 49 | -------------------------------------------------------------------------------- /tests/testthat.R: -------------------------------------------------------------------------------- 1 | library(testthat) 2 | library(secret) 3 | 4 | if (Sys.getenv("NOT_CRAN", "") != "") { 5 | test_check("secret") 6 | } 7 | -------------------------------------------------------------------------------- /tests/testthat/helper.R: -------------------------------------------------------------------------------- 1 | 2 | # Function to test for identical file path, after normalizing paths 3 | expect_same_filepath <- function(object, expected){ 4 | neat_path <- function(x){ normalizePath(x, winslash = "/", mustWork = FALSE) } 5 | expect_equal(neat_path(object), neat_path(expected)) 6 | } 7 | 8 | # Create a "package" in tempdir to contain a new vault 9 | make_pkg_root <- function(){ 10 | pkg_root <- normalizePath(file.path(tempdir(), "secret_test"), 11 | winslash = "/", 12 | mustWork = FALSE 13 | ) 14 | if (dir.exists(pkg_root)) unlink(pkg_root, recursive = TRUE) 15 | dir.create(pkg_root, showWarnings = FALSE) 16 | writeLines("Package: secret_test", file.path(pkg_root, "DESCRIPTION")) 17 | pkg_root 18 | } 19 | -------------------------------------------------------------------------------- /tests/testthat/test-1-vault.R: -------------------------------------------------------------------------------- 1 | 2 | context("vault") 3 | 4 | pkg_root <- make_pkg_root() 5 | 6 | test_that("Can create a vault in a package", { 7 | proj_root <- rprojroot::find_package_root_file(path = pkg_root) 8 | expect_is(proj_root, "character") 9 | expect_equal(basename(proj_root), "secret_test") 10 | 11 | res <- create_package_vault(pkg_root) 12 | expect_equal(basename(res), "vault") 13 | 14 | expect_equal( 15 | dir(res), 16 | c("README", "secrets", "users") 17 | ) 18 | 19 | expect_same_filepath( 20 | find_vault(pkg_root), 21 | file.path(tempdir(), "secret_test", "inst", "vault") 22 | ) 23 | 24 | expect_equal( 25 | list_users(pkg_root), 26 | character(0) 27 | ) 28 | 29 | }) 30 | 31 | test_that("errors & messages", { 32 | 33 | expect_message( 34 | create_package_vault(pkg_root), 35 | "Package vault already exists" 36 | ) 37 | 38 | dir.create(tmp <- tempfile()) 39 | expect_error( 40 | package_vault_directory(tmp), 41 | "No package or package vault found" 42 | ) 43 | }) 44 | 45 | test_that("finding the vault", { 46 | 47 | mockery::stub(find_vault, "is_vault", TRUE) 48 | 49 | withr::with_options( 50 | list("secret.vault" = NULL), 51 | withr::with_envvar( 52 | c("R_SECRET_VAULT" = "/foo/bar"), 53 | expect_equal(find_vault(NULL), "/foo/bar") 54 | ) 55 | ) 56 | 57 | withr::with_envvar( 58 | c("R_SECRET_VAULT" = NA_character_), 59 | withr::with_options( 60 | list("secret.vault" = "/foo/foobar"), 61 | expect_equal(find_vault(NULL), "/foo/foobar") 62 | ) 63 | ) 64 | }) 65 | 66 | test_that("finding the vault 2", { 67 | dir.create(tmp <- tempfile()) 68 | withr::with_dir( 69 | tmp, 70 | withr::with_options( 71 | list("secret.vault" = NULL), 72 | withr::with_envvar( 73 | c("R_SECRET_VAULT" = NA_character_), 74 | expect_equal(find_vault(NULL), ".") 75 | ) 76 | ) 77 | ) 78 | }) 79 | -------------------------------------------------------------------------------- /tests/testthat/test-2-users.R: -------------------------------------------------------------------------------- 1 | 2 | pkg_root <- make_pkg_root() 3 | create_package_vault(pkg_root) 4 | 5 | ({ 6 | alice <- "alice" 7 | bob <- "bob" 8 | user_keys_dir <- file.path(system.file(package = "secret"), "user_keys") 9 | key <- function(x)file.path(user_keys_dir, x) 10 | alice_public_key <- key("alice.pub") 11 | alice_private_key <- key("alice.pem") 12 | bob_public_key <- key("bob.pub") 13 | bob_private_key <- key("bob.pem") 14 | carl_private_key <- key("carl.pem") 15 | }) 16 | 17 | context("users") 18 | 19 | test_that("can add and delete users", { 20 | expect_equal( 21 | basename( 22 | add_user(alice, alice_public_key, vault = pkg_root) 23 | ), 24 | "alice.pem" 25 | ) 26 | 27 | expect_equal( 28 | list_users(pkg_root), 29 | "alice" 30 | ) 31 | 32 | expect_error( 33 | delete_user(bob, vault = pkg_root), 34 | "does not exist" 35 | ) 36 | 37 | expect_null( 38 | delete_user(alice, vault = pkg_root) 39 | ) 40 | expect_equal( 41 | list_users(pkg_root), 42 | character(0) 43 | ) 44 | }) 45 | 46 | test_that("error messages", { 47 | unlink(pkg_root, recursive = TRUE) 48 | pkg_root <- make_pkg_root() 49 | create_package_vault(pkg_root) 50 | add_user(alice, alice_public_key, vault = pkg_root) 51 | 52 | expect_error( 53 | add_user(alice, alice_public_key, vault = pkg_root), 54 | paste0("User .*", alice, ".* already exists in this vault") 55 | ) 56 | }) 57 | -------------------------------------------------------------------------------- /tests/testthat/test-3-secrets.R: -------------------------------------------------------------------------------- 1 | 2 | pkg_root <- make_pkg_root() 3 | create_package_vault(pkg_root) 4 | 5 | ({ 6 | alice <- "alice" 7 | bob <- "bob" 8 | carl <- "carl" 9 | user_keys_dir <- file.path(system.file(package = "secret"), "user_keys") 10 | key <- function(x)file.path(user_keys_dir, x) 11 | alice_public_key <- key("alice.pub") 12 | alice_private_key <- key("alice.pem") 13 | bob_public_key <- key("bob.pub") 14 | bob_private_key <- key("bob.pem") 15 | carl_public_key <- key("carl.pub") 16 | carl_private_key <- key("carl.pem") 17 | }) 18 | 19 | as_dataframe <- function(...)data.frame(..., stringsAsFactors = FALSE) 20 | 21 | context("secrets") 22 | 23 | secret_to_keep <- list(a = 1, b = letters) 24 | 25 | test_that("can add secrets", { 26 | 27 | add_user(alice, alice_public_key, vault = pkg_root) 28 | 29 | expect_null( 30 | add_secret("secret_one", secret_to_keep, users = alice, vault = pkg_root) 31 | ) 32 | 33 | expect_error( 34 | add_secret("secret_one", secret_to_keep, users = alice, vault = pkg_root), 35 | "Secret name already exists" 36 | ) 37 | 38 | expect_equal( 39 | list_secrets(pkg_root), 40 | as_dataframe(secret = "secret_one", email = I(list("alice"))) 41 | ) 42 | }) 43 | 44 | 45 | test_that("alice can decrypt secret", { 46 | # Error on public key 47 | expect_error( 48 | get_secret("secret_one", key = alice_public_key, vault = pkg_root), 49 | "Access denied to secret" 50 | ) 51 | # Success on private key 52 | expect_equal( 53 | get_secret("secret_one", key = alice_private_key, vault = pkg_root), 54 | secret_to_keep 55 | ) 56 | }) 57 | 58 | 59 | test_that("bob can not decrypt secret", { 60 | expect_error( 61 | get_secret("secret_one", key = bob_public_key, vault = pkg_root), 62 | "Access denied to secret" 63 | ) 64 | expect_error( 65 | get_secret("secret_one", key = bob_private_key, vault = pkg_root), 66 | "Access denied to secret" 67 | ) 68 | }) 69 | 70 | 71 | test_that("add second secret shared by multiple users", { 72 | expect_equal( 73 | basename( 74 | add_user(bob, bob_public_key, vault = pkg_root) 75 | ), 76 | "bob.pem" 77 | ) 78 | expect_null( 79 | add_secret("secret_two", iris, users = c(alice, bob), vault = pkg_root) 80 | ) 81 | expect_equal( 82 | list_secrets(pkg_root), 83 | as_dataframe( 84 | secret = c("secret_one", "secret_two"), 85 | email = I(list("alice", c("alice", "bob"))) 86 | ) 87 | ) 88 | expect_error( 89 | # alice can not decrypt with public key 90 | get_secret("secret_two", key = alice_public_key, vault = pkg_root) 91 | ) 92 | expect_equal( 93 | # alice can decrypt with private key 94 | get_secret("secret_two", key = alice_private_key, vault = pkg_root), 95 | iris 96 | ) 97 | 98 | expect_error( 99 | # bob can not decrypt with public key 100 | get_secret("secret_two", key = bob_public_key, vault = pkg_root) 101 | ) 102 | expect_equal( 103 | # bob can decrypt with private key 104 | get_secret("secret_two", key = bob_private_key, vault = pkg_root), 105 | iris 106 | ) 107 | expect_error( 108 | # carl can not decrypt with private key 109 | get_secret("secret_two", key = carl_private_key, vault = pkg_root) 110 | ) 111 | 112 | # delete user and try to access secret 113 | expect_warning( 114 | delete_user(alice, vault = pkg_root), 115 | "will leave orphaned secrets" 116 | ) 117 | 118 | expect_true( 119 | any(vapply(list_secrets(vault = pkg_root)$email, length, integer(1)) == 0) 120 | ) 121 | delete_secret("secret_one", vault = pkg_root) 122 | 123 | # User 1 should not be able to access the secret 124 | expect_error( 125 | get_secret("secret_two", key = alice_private_key, vault = pkg_root), 126 | "Access denied to secret" 127 | ) 128 | 129 | # user 2 should still see the secret 130 | expect_equal( 131 | get_secret("secret_two", key = bob_private_key, vault = pkg_root), 132 | iris 133 | ) 134 | 135 | 136 | expect_null( 137 | delete_secret("secret_two", vault = pkg_root) 138 | ) 139 | 140 | expect_equal( 141 | list_secrets(pkg_root), 142 | as_dataframe( 143 | secret = character(0), 144 | email = I(list()) 145 | ) 146 | ) 147 | expect_equal( 148 | list_users(pkg_root), 149 | "bob" 150 | ) 151 | }) 152 | 153 | 154 | # share-secret -------------------------------------------------------- 155 | 156 | test_that("use share_secret() to share between alice and bob", { 157 | add_user(alice, alice_public_key, vault = pkg_root) 158 | expect_null( 159 | add_secret("secret_3", mtcars, users = c(alice), vault = pkg_root) 160 | ) 161 | expect_equal( 162 | list_secrets(pkg_root), 163 | as_dataframe( 164 | secret = "secret_3", 165 | email = I(list("alice")) 166 | ) 167 | ) 168 | 169 | expect_null( 170 | share_secret("secret_3", users = c(alice, bob), 171 | key = alice_private_key, vault = pkg_root) 172 | ) 173 | 174 | expect_equal( 175 | list_owners("secret_3", vault = pkg_root), 176 | c(alice, bob) 177 | ) 178 | 179 | expect_equal( 180 | # alice can decrypt with private key 181 | get_secret("secret_3", key = alice_private_key, vault = pkg_root), 182 | mtcars 183 | ) 184 | 185 | expect_equal( 186 | # bob can decrypt with private key 187 | get_secret("secret_3", key = bob_private_key, vault = pkg_root), 188 | mtcars 189 | ) 190 | 191 | expect_equal( 192 | list_secrets(pkg_root), 193 | as_dataframe( 194 | secret = "secret_3", 195 | email = I(list(c("alice", "bob"))) 196 | ) 197 | ) 198 | expect_equal( 199 | list_users(pkg_root), 200 | c("alice", "bob") 201 | ) 202 | }) 203 | 204 | 205 | test_that("unshare a secret", { 206 | expect_null( 207 | unshare_secret("secret_3", users = bob, vault = pkg_root) 208 | ) 209 | expect_error( 210 | # bob can no longer decrypt with private key 211 | get_secret("secret_3", key = bob_private_key, vault = pkg_root) 212 | ) 213 | }) 214 | 215 | test_that("udpate a secret", { 216 | expect_equal( 217 | get_secret("secret_3", key = alice_private_key, vault = pkg_root), 218 | mtcars 219 | ) 220 | expect_null( 221 | update_secret("secret_3", value = "foo", key = alice_private_key, 222 | vault = pkg_root) 223 | ) 224 | expect_equal( 225 | get_secret("secret_3", key = alice_private_key, vault = pkg_root), 226 | "foo" 227 | ) 228 | expect_equal( 229 | list_secrets(pkg_root), 230 | as_dataframe( 231 | secret = c("secret_3"), 232 | email = I(list("alice")) 233 | ) 234 | ) 235 | 236 | }) 237 | 238 | test_that("error messages", { 239 | unlink(pkg_root, recursive = TRUE) 240 | pkg_root <- make_pkg_root() 241 | create_package_vault(pkg_root) 242 | add_user(alice, alice_public_key, vault = pkg_root) 243 | 244 | expect_error( 245 | get_secret("blah", key = alice_private_key, vault = pkg_root), 246 | "[sS]ecret .*blah.* does not exist" 247 | ) 248 | 249 | expect_error( 250 | update_secret("blah", "v", key = alice_private_key, vault = pkg_root), 251 | "[sS]ecret .*blah.* does not exist" 252 | ) 253 | 254 | expect_error( 255 | delete_secret("blah", vault = pkg_root), 256 | "[sS]ecret .*blah.* does not exist" 257 | ) 258 | }) 259 | -------------------------------------------------------------------------------- /tests/testthat/test-4-travis-github.R: -------------------------------------------------------------------------------- 1 | 2 | pkg_root <- make_pkg_root() 3 | create_package_vault(pkg_root) 4 | 5 | context("travis and github") 6 | 7 | travis_key <- paste0( 8 | '{ "key":\n', 9 | '"-----BEGIN PUBLIC KEY-----\\n', 10 | 'MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAlhKjkvKRZN/sI2X/Fcy2\\n', 11 | 'wqmd8Y2Tey2ZCjuVei/sVOVB+7CazyfCDbR/QB6R7WFXrJ/4gqQsQyBTXldvc+QY\\n', 12 | 'VJ4lhK9m+yRdJACsOWY4ZaYy2iOe+ilsPdNce3igxUQDppqfX7F2RaPHb8ogwJnV\\n', 13 | 'gLEYPhcGH0e7YweAMUQfASiAHyzOCj/SYKkQYSEimd7B09SfrEzLsCb5njSJVdQ1\\n', 14 | 'xqmxTkjJOo27yPB4Y4CLpXcRqoLi+ju0vcxURU6sH+iwldap1pKMYLmZtHRcAdhh\\n', 15 | 'TDs3SYX72iQ3f3C2O/WK6DGCS7+iQdO63/q9qfx1wP+kpZXVob7bekuN1Av3DomQ\\n', 16 | 'HpmnkGXCf7ud9DdSV4Z+ecJkvvi0UY9DOz5vpz0DiEV4Y9wqmrz9xkNgNw0mHQSy\\n', 17 | 'aSZbM/4MemeOIgN2bHVqXGgE09eZIYmVmvBVqdRg0rtTKicCU9EwsGfqpbcs49Uy\\n', 18 | 'e3gK7zsNbCC6X8+bKHUgHdaaPtY/eVydHd/iHthi44Xdo+t3ykbF9/JqprUssnMU\\n', 19 | 'iVK8MTsNPkv1HUnja9zLGzcmHbrpNDEdu4ASqC1A7XKUaU807aDT6XjcLObcTH7R\\n', 20 | '15RwzGF/e1Q10xUMyCfC5zvwTXXoV9IB4po6/vDEXR6nZCHnB3HibUaDTjLFFJE4\\n', 21 | '59bGPpJEle95EpUBqR76ShsCAwEAAQ==\\n', 22 | '-----END PUBLIC KEY-----\\n', 23 | '", "fingerprint":"8a:63:d6:f1:6a:2f:ed:e1:36:65:61:b8:16:65:2f:16"}' 24 | ) 25 | 26 | test_that("can add travis user", { 27 | 28 | ## This is a bit cumbersome, because we don't call the mocked function 29 | ## directly... 30 | mockery::stub( 31 | add_travis_user, "get_travis_key", function(travis_repo) { 32 | mockery::stub(get_travis_key, "curl", travis_key) 33 | get_travis_key(travis_repo) 34 | } 35 | ) 36 | 37 | expect_equal( 38 | basename( 39 | add_travis_user("gaborcsardi/secret", vault = pkg_root) 40 | ), 41 | "travis-gaborcsardi-secret.pem" 42 | ) 43 | exp_file <- file.path( 44 | pkg_root, "inst", "vault", "users", "travis-gaborcsardi-secret.pem" 45 | ) 46 | expect_true(file.exists(exp_file)) 47 | }) 48 | 49 | github_key <- paste0( 50 | '[{ "id": 6261674,\n', 51 | '"key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDhylC70CzZoGpOrEBRX', 52 | 'dKmm+YjSGXiYvTg/b7+gvt28hEuwXXT53mWEWJvQWjIdgzeEBI6sO0uVS4BC7qWe0', 53 | 'TGQ8eXdF1htnXpVOsA4ZHrRjTIAttLesFPvdKUwq43eL6xC2umzCWLk21fEJbdECE', 54 | 'tkShP7AZL+/5uVX1AWAd4gllF4NX/N0MwW7x+jjbptl/bFV33zPev++0ZXAM5FyTG', 55 | 'CC5T46BwhQwHNxVeU3nd3rl15S4PLcIyv6knzz9IA0HPAlviUjxjRtP5eEZkYJRI9', 56 | '8t5pIKt5yQGKBQN2qm1fvcZ1EpfM4HkpKm7JKN8jYyY48+JsJCGyKQvQVqK5jDN"', 57 | '}]' 58 | ) 59 | 60 | test_that("can add github user",{ 61 | 62 | mockery::stub( 63 | add_github_user, "get_github_key", function(github_user, i = 1) { 64 | mockery::stub( 65 | get_github_key, 66 | "curl_fetch_memory", 67 | list(content = charToRaw(github_key)) 68 | ) 69 | get_github_key(github_user, i) 70 | } 71 | ) 72 | 73 | withr::with_envvar( 74 | c(GITHUB_PAT = NA_character_), 75 | expect_equal( 76 | basename( 77 | add_github_user("gaborcsardi", vault = pkg_root) 78 | ), 79 | "github-gaborcsardi.pem" 80 | ) 81 | ) 82 | 83 | exp_file <- file.path( 84 | pkg_root, "inst", "vault", "users", "github-gaborcsardi.pem" 85 | ) 86 | expect_true( 87 | file.exists(exp_file) 88 | ) 89 | }) 90 | 91 | test_that("can add github user with PAT",{ 92 | 93 | tryCatch( 94 | delete_user("github-gaborcsardi", vault = pkg_root), 95 | error = function(e) e 96 | ) 97 | 98 | mockery::stub( 99 | add_github_user, "get_github_key", function(github_user, i = 1) { 100 | mockery::stub( 101 | get_github_key, 102 | "curl_fetch_memory", 103 | list(content = charToRaw(github_key)) 104 | ) 105 | get_github_key(github_user, i) 106 | } 107 | ) 108 | 109 | withr::with_envvar( 110 | c("GITHUB_PAT" = "dummy-pat"), 111 | expect_equal( 112 | basename( 113 | add_github_user("gaborcsardi", vault = pkg_root) 114 | ), 115 | "github-gaborcsardi.pem" 116 | ) 117 | ) 118 | exp_file <- file.path( 119 | pkg_root, "inst", "vault", "users", "github-gaborcsardi.pem" 120 | ) 121 | expect_true( 122 | file.exists(exp_file) 123 | ) 124 | }) 125 | -------------------------------------------------------------------------------- /tests/testthat/test-5-keys.R: -------------------------------------------------------------------------------- 1 | 2 | context("local key") 3 | 4 | test_that("can read local key", { 5 | 6 | mockery::stub(local_key, "Sys.getenv", "sdfqrtafgaetsgsfgqr") 7 | ## Error message is system language dependent 8 | expect_error(local_key()) 9 | 10 | pth <- system.file("user_keys/alice.pem", package = "secret") 11 | mockery::stub(local_key, "Sys.getenv", pth) 12 | 13 | expect_is(local_key(), "key") 14 | }) 15 | 16 | test_that("can read local key when setting env variable", { 17 | 18 | pth <- system.file("user_keys/alice.pem", package = "secret") 19 | z <- withr::with_envvar( 20 | c(USER_KEY = pth), 21 | local_key() 22 | ) 23 | expect_is(z, "key") 24 | expect_is(z, "rsa") 25 | 26 | withr::with_envvar( 27 | c(USER_KEY = "path/does/not/exist"), 28 | ## Error message is system language dependent 29 | expect_error(local_key()) 30 | ) 31 | }) 32 | 33 | test_that("local key paths are used", { 34 | 35 | mockery::stub(local_key, "file.exists", function(...) { 36 | as.list(...) == "~/.ssh/id_rsa" 37 | }) 38 | mockery::stub(local_key, "read_key", function(file, password, der) { 39 | file 40 | }) 41 | 42 | withr::with_envvar( 43 | c(USER_KEY = NA_character_), 44 | expect_equal(local_key(), "~/.ssh/id_rsa") 45 | ) 46 | 47 | mockery::stub(local_key, "file.exists", function(...) { 48 | as.list(...) == "~/.ssh/id_rsa.pem" 49 | }) 50 | mockery::stub(local_key, "read_key", function(file, password, der) { 51 | file 52 | }) 53 | 54 | withr::with_envvar( 55 | c(USER_KEY = NA_character_), 56 | expect_equal(local_key(), "~/.ssh/id_rsa.pem") 57 | ) 58 | 59 | mockery::stub(local_key, "file.exists", function(...) { 60 | rep(FALSE, length(list(...))) 61 | }) 62 | withr::with_envvar( 63 | c(USER_KEY = NA_character_), 64 | expect_error(local_key(), "No suitable user key found") 65 | ) 66 | }) 67 | -------------------------------------------------------------------------------- /tests/testthat/test-6-issues.R: -------------------------------------------------------------------------------- 1 | 2 | context("Issues") 3 | 4 | pkg_root <- make_pkg_root() 5 | create_package_vault(pkg_root) 6 | secret_to_keep <- list(a = 1, b = letters) 7 | secret_to_keep2 <- list(a = 2, b = LETTERS) 8 | 9 | ({ 10 | alice <- "alice" 11 | bob <- "bob" 12 | user_keys_dir <- file.path(system.file(package = "secret"), "user_keys") 13 | key <- function(x) file.path(user_keys_dir, x) 14 | alice_public_key <- key("alice.pub") 15 | alice_private_key <- key("alice.pem") 16 | bob_public_key <- key("bob.pub") 17 | bob_private_key <- key("bob.pem") 18 | }) 19 | 20 | test_that("update a secret, deleted user has no access", { 21 | add_user(alice, alice_public_key, vault = pkg_root) 22 | add_user(bob, bob_public_key, vault = pkg_root) 23 | add_secret( 24 | "secret_one", 25 | secret_to_keep, 26 | users = c(alice, bob), 27 | vault = pkg_root 28 | ) 29 | 30 | expect_equal( 31 | get_secret("secret_one", key = alice_private_key, vault = pkg_root), 32 | secret_to_keep 33 | ) 34 | expect_equal( 35 | get_secret("secret_one", key = bob_private_key, vault = pkg_root), 36 | secret_to_keep 37 | ) 38 | 39 | ## Bob get's the AES key of the secret, e.g. from the history of the repo 40 | aes <- try_get_aes_key( 41 | vault = find_vault(pkg_root), 42 | key = bob_private_key, 43 | name = "secret_one" 44 | ) 45 | 46 | delete_user(bob, vault = pkg_root) 47 | 48 | update_secret( 49 | "secret_one", 50 | value = secret_to_keep2, 51 | key = alice_private_key, 52 | vault = pkg_root 53 | ) 54 | 55 | expect_error( 56 | get_secret("secret_one", key = bob_private_key, vault = pkg_root), 57 | "Access denied to secret" 58 | ) 59 | 60 | secret_file <- get_secret_file( 61 | vault = find_vault(pkg_root), 62 | name = "secret_one" 63 | ) 64 | 65 | ## The old AES key of the secret is not good any more. 66 | secret <- unserialize(read_raw(secret_file)) 67 | expect_error( 68 | openssl::aes_cbc_decrypt(secret, aes), 69 | "OpenSSL error" 70 | ) 71 | }) 72 | 73 | test_that("missing vault arguments", { 74 | if (!(alice %in% list_users(pkg_root))){ 75 | add_user(alice, alice_public_key, vault = pkg_root) 76 | } 77 | 78 | if (!("secret_one" %in% list_secrets(pkg_root)$secret)){ 79 | add_secret( 80 | "secret_one", 81 | secret_to_keep, 82 | users = c(alice, bob), 83 | vault = pkg_root 84 | ) 85 | } 86 | 87 | withr::with_envvar(c("R_SECRET_VAULT" = pkg_root),{ 88 | expect_equal( 89 | list_secrets(pkg_root), 90 | list_secrets() # Note the missing location argument 91 | ) 92 | expect_equal( 93 | list_users(pkg_root), 94 | list_users() # Note the missing location argument 95 | ) 96 | }) 97 | 98 | withr::with_options(c("secret.vault" = pkg_root),{ 99 | expect_equal( 100 | list_secrets(pkg_root), 101 | list_secrets() # Note the missing location argument 102 | ) 103 | expect_equal( 104 | list_users(pkg_root), 105 | list_users() # Note the missing location argument 106 | ) 107 | }) 108 | 109 | }) 110 | -------------------------------------------------------------------------------- /vignettes/secrets.Rmd: -------------------------------------------------------------------------------- 1 | --- 2 | title: "secret: Share Sensitive Information in R Packages" 3 | date: "`r Sys.Date()`" 4 | output: 5 | 6 | html_document: 7 | keep_md: yes 8 | vignette: > 9 | %\VignetteIndexEntry{Share Sensitive Information in R Packages} 10 | %\VignetteEngine{knitr::rmarkdown} 11 | --- 12 | 13 | ## Usage 14 | 15 | ```{r, child="../inst/vignette_child/child.Rmd"} 16 | ``` -------------------------------------------------------------------------------- /vignettes/secrets.md: -------------------------------------------------------------------------------- 1 | # secret: Share Sensitive Information in R Packages 2 | `r Sys.Date()` 3 | 4 | ## Usage 5 | 6 | 7 | ### Set up your keys: 8 | 9 | Ensure you know the location of your public and private keys. In Linux this is usually the folder `~/.ssh`, so on Windows you may want to choose the same folder. 10 | 11 | By default, the package looks for your private key at 12 | 13 | 1. `~/.ssh/id_rsa` 14 | 1. `~/.ssh/id_rsa.pem`. 15 | 16 | You can change this default by setting an environment variable `USER_KEY`: 17 | 18 | 19 | ```r 20 | # This is optional - only do this if you want to change the default location 21 | Sys.setenv(USER_KEY = "path/to/private/key") 22 | ``` 23 | 24 | Test that the package can read your key: 25 | 26 | 27 | ```r 28 | library(secret) 29 | local_key() 30 | ``` 31 | 32 | ``` 33 | ## [1024-bit rsa private key] 34 | ## md5: 7794640c6bebe1e52a28caf792ea2896 35 | ``` 36 | 37 | 38 | ### Load the package: 39 | 40 | 41 | ```r 42 | library(secret) 43 | ``` 44 | 45 | 46 | ### Create a vault: 47 | 48 | You can create a vault by using `create_vault()` 49 | 50 | 51 | ```r 52 | vault <- file.path(tempdir(), ".vault") 53 | dir.create(vault) 54 | create_vault(vault) 55 | ``` 56 | 57 | A vault consists of two folders for: 58 | 59 | * `users`: contains user and their public keys 60 | * `secrets`: contains the encrypted secrets 61 | 62 | 63 | ```r 64 | dir(vault) 65 | ``` 66 | 67 | ``` 68 | ## [1] "README" "secrets" "users" 69 | ``` 70 | 71 | Alternatively, you can create a vault in an R package: 72 | 73 | 74 | ```r 75 | pkg_root <- "/path/to/package" 76 | create_package_vault(pkg_root) 77 | ``` 78 | 79 | 80 | ### Add users to the vault: 81 | 82 | To add a user to the vault, you have to know their public key. 83 | 84 | The `secret` package contains some public and private keys you can use for demonstration purposes. 85 | 86 | 87 | ```r 88 | key_dir <- file.path(system.file(package = "secret"), "user_keys") 89 | alice_public_key <- file.path(key_dir, "alice.pub") 90 | alice_private_key <- file.path(key_dir, "alice.pem") 91 | openssl::read_pubkey(alice_public_key) 92 | ``` 93 | 94 | ``` 95 | ## [2048-bit rsa public key] 96 | ## md5: 1d858d316afb8b7d0efd69ec85dc7174 97 | ``` 98 | 99 | Add the public key of Alice to the vault: 100 | 101 | 102 | ```r 103 | add_user("alice", alice_public_key, vault = vault) 104 | ``` 105 | 106 | 107 | ### Add a secret using your public key. 108 | 109 | A secret can be any R object - this object will be serialised and then encrypted to the vault. 110 | 111 | 112 | ```r 113 | secret_to_keep <- c(password = "my_password") 114 | add_secret("secret_one", secret_to_keep, users = "alice", vault = vault) 115 | ``` 116 | 117 | 118 | ### Decrypt a secret by providing your private key: 119 | 120 | You can decrypt a secret if you have the private key that corresponds to the public key that was used to encrypt the secret, 121 | 122 | 123 | ```r 124 | get_secret("secret_one", key = alice_private_key, vault = vault) 125 | ``` 126 | 127 | ``` 128 | ## password 129 | ## "my_password" 130 | ``` 131 | 132 | 133 | ### Note for Windows users 134 | 135 | * If you use windows, you most likely created your keys using PuttyGen. Note that the key created by default from PuttyGen is not in OpenSSH format, so you have to convert your format first. To do this, use the `/Conversions/Export OpenSSH key` menu item in PuttyGen. 136 | 137 | * Note that the folder `~/.ssh` in Windows usually expands to `C:\\Users\\YOURNAME\\Documents\\.ssh`. You can find the full path by using: 138 | 139 | 140 | ```r 141 | normalizePath("~/.ssh", mustWork = FALSE) 142 | ``` 143 | 144 | ``` 145 | ## [1] "C:\\Users\\adevries\\Documents\\.ssh" 146 | ``` 147 | 148 | 149 | 150 | --------------------------------------------------------------------------------