How to: Manage secrets and environments
Source:vignettes/how-to/manage-secrets-and-environments.Rmd
manage-secrets-and-environments.Rmd
Managing secrets and configuration can be a challenge in applications which need to work in different development and deployment environments. Typically, secrets are credentials for an external service such as a database. Environments, on the other hand, are used to manage multiple configurations that we can be easily switched between. Rhino recommends a way for working with either one.
Secrets
Secrets are a confidential information and should not be tracked in your version control system. Therefore, a natural place for them are system environment variables. Variables set in system environment can be retrieved within your code with Sys.getenv()
.
R provides a way to easily set environment variables. Upon a session start (or restart) R reads .Renviron
file contents and sets environment variables.
.Renviron
# A comment in .Renviron file
DATABASE_PASSWORD="foobar123!"
API_KEY="75170fc230cd88f32e475ff4087f81d9"
Secrets defined via environment variables can be read and used the following way:
db_password <- Sys.getenv("DATABASE_PASSWORD")
if (db_password == "") {
# Handle unset or empty DATABASE_PASSWORD variable
}
pool <- pool::dbPool(
drv = RMySQL::MySQL(),
dbname = "...",
host = "...",
username = "admin",
password = db_password
)
Recommendations for storing secrets
- Store secrets and environment variables in
.Renviron
. - Use a separate
.Renviron
file for every environment. Swap the whole file when changing environments. - Use
CONSTANT_CASE
for variable names. - Do not track
.Renviron
file in a version control system. Store it in a secure location, e.g. a password manager. - Do not publish
.Renviron
to RStudio Connect nor shinyapps.io. Both, RStudio Connect and Shiny Apps, provide means to manage environment variables.
Environments
Having every configurable setting stored as an environment variable would result in overgrown .Renviron
files. That’s where configurable environments come in.
Everything that is not confidential can be tracked by a version control system. Rhino endorses use of config package for managing environments.
config.yml
default:
rhino_log_level: !expr Sys.getenv("RHINO_LOG_LEVEL", "INFO")
rhino_log_file: !expr Sys.getenv("RHINO_LOG_FILE", NA)
database_user: "service_account"
database_schema: "dev"
dev:
rhino_log_level: !expr Sys.getenv("RHINO_LOG_LEVEL", "DEBUG")
staging:
database_schema: "stg"
production:
database_user: "service_account_prod"
database_schema: "prod"
.Renviron
R_CONFIG_ACTIVE="dev"
You can access the configuration variables in the following way:
box::use(config)
config$get("rhino_log_level") # == "DEBUG"
config$get("database_user") # == "service_account"
config$get("rhino_log_level", config = "production") # == "INFO"
config$get("database_user", config = "production") # == "service_account_prod"
withr::with_envvar(list(RHINO_LOG_LEVEL = "ERROR"), {
config$get("rhino_log_level") # == "ERROR"
config$get("rhino_log_level", config = "production") # == "ERROR"
})
Recommendations for managing environments
- Define environments and their settings in
config.yml
. - Select config by setting
R_CONFIG_ACTIVE
variable in.Renviron
. - Make use of default values.
- Use
!expr Sys.getenv()
to make settings overridable with environment variables. - Import config with box and call as usual, i.e.
box::use(config)
andconfig$get()
. - Use
snake_case
for field names.