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
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.