Add support for specifying rpc_secret_file, metrics_token_file and admin_token_file using environment variables #643

Merged
lx merged 4 commits from networkException/garage:token-file-env into main-0.8.x 2023-10-19 09:33:12 +00:00
5 changed files with 57 additions and 25 deletions

View file

@ -276,7 +276,7 @@ Compression is done synchronously, setting a value too high will add latency to
This value can be different between nodes, compression is done by the node which receive the
API call.
### `rpc_secret`, `rpc_secret_file` or `GARAGE_RPC_SECRET` (env)
### `rpc_secret`, `rpc_secret_file` or `GARAGE_RPC_SECRET`, `GARAGE_RPC_SECRET_FILE` (env)
Garage uses a secret key, called an RPC secret, that is shared between all
nodes of the cluster in order to identify these nodes and allow them to
@ -288,6 +288,9 @@ Since Garage `v0.8.2`, the RPC secret can also be stored in a file whose path is
given in the configuration variable `rpc_secret_file`, or specified as an
environment variable `GARAGE_RPC_SECRET`.
Since Garage `v0.9.0`, you can also specify the path of a file storing the secret
as the `GARAGE_RPC_SECRET_FILE` environment variable.
### `rpc_bind_addr`
The address and port on which to bind for inter-cluster communcations
@ -465,7 +468,7 @@ See [administration API reference](@/documentation/reference-manual/admin-api.md
Alternatively, since `v0.8.5`, a path can be used to create a unix socket. Note that for security reasons,
the socket will have 0220 mode. Make sure to set user and group permissions accordingly.
### `metrics_token`, `metrics_token_file` or `GARAGE_METRICS_TOKEN` (env)
### `metrics_token`, `metrics_token_file` or `GARAGE_METRICS_TOKEN`, `GARAGE_METRICS_TOKEN_FILE` (env)
The token for accessing the Metrics endpoint. If this token is not set, the
Metrics endpoint can be accessed without access control.
@ -475,8 +478,9 @@ You can use any random string for this value. We recommend generating a random t
`metrics_token` was introduced in Garage `v0.7.2`.
`metrics_token_file` and the `GARAGE_METRICS_TOKEN` environment variable are supported since Garage `v0.8.2`.
`GARAGE_METRICS_TOKEN_FILE` is supported since `v0.9.0`.
### `admin_token`, `admin_token_file` or `GARAGE_ADMIN_TOKEN` (env)
### `admin_token`, `admin_token_file` or `GARAGE_ADMIN_TOKEN`, `GARAGE_ADMIN_TOKEN_FILE` (env)
The token for accessing all of the other administration endpoints. If this
token is not set, access to these endpoints is disabled entirely.
@ -486,6 +490,7 @@ You can use any random string for this value. We recommend generating a random t
`admin_token` was introduced in Garage `v0.7.2`.
`admin_token_file` and the `GARAGE_ADMIN_TOKEN` environment variable are supported since Garage `v0.8.2`.
`GARAGE_ADMIN_TOKEN_FILE` is supported since `v0.9.0`.
### `trace_sink`

View file

@ -25,7 +25,7 @@ use structopt::StructOpt;
use netapp::util::parse_and_resolve_peer_addr;
use netapp::NetworkKey;
use garage_util::config::Config;
use garage_util::config::{read_secret_file, Config};
use garage_util::error::*;
use garage_rpc::system::*;
@ -70,15 +70,30 @@ pub struct Secrets {
#[structopt(short = "s", long = "rpc-secret", env = "GARAGE_RPC_SECRET")]
pub rpc_secret: Option<String>,
/// Metrics API authentication token, replaces admin.metrics_token in config.toml when
/// RPC secret network key, used to replace rpc_secret in config.toml and rpc-secret
/// when running the daemon or doing admin operations
#[structopt(long = "rpc-secret-file", env = "GARAGE_RPC_SECRET_FILE")]
pub rpc_secret_file: Option<String>,
/// Admin API authentication token, replaces admin.admin_token in config.toml when
/// running the Garage daemon
#[structopt(long = "admin-token", env = "GARAGE_ADMIN_TOKEN")]
pub admin_token: Option<String>,
/// Admin API authentication token file path, replaces admin.admin_token in config.toml
/// and admin-token when running the Garage daemon
#[structopt(long = "admin-token-file", env = "GARAGE_ADMIN_TOKEN_FILE")]
pub admin_token_file: Option<String>,
/// Metrics API authentication token, replaces admin.metrics_token in config.toml when
/// running the Garage daemon
#[structopt(long = "metrics-token", env = "GARAGE_METRICS_TOKEN")]
pub metrics_token: Option<String>,
/// Metrics API authentication token file path, replaces admin.metrics_token in config.toml
/// and metrics-token when running the Garage daemon
#[structopt(long = "metrics-token-file", env = "GARAGE_METRICS_TOKEN_FILE")]
pub metrics_token_file: Option<String>,
}
#[tokio::main]
@ -256,15 +271,24 @@ async fn cli_command(opt: Opt) -> Result<(), Error> {
}
}
fn fill_secrets(mut config: Config, secrets: Secrets) -> Config {
fn fill_secrets(mut config: Config, secrets: Secrets) -> Result<Config, Error> {
if secrets.rpc_secret.is_some() {
config.rpc_secret = secrets.rpc_secret;
} else if secrets.rpc_secret_file.is_some() {
config.rpc_secret = Some(read_secret_file(&secrets.rpc_secret_file.unwrap())?);
}
if secrets.admin_token.is_some() {
config.admin.admin_token = secrets.admin_token;
} else if secrets.admin_token_file.is_some() {
config.admin.admin_token = Some(read_secret_file(&secrets.admin_token_file.unwrap())?);
}
if secrets.metrics_token.is_some() {
config.admin.metrics_token = secrets.metrics_token;
} else if secrets.metrics_token_file.is_some() {
config.admin.metrics_token = Some(read_secret_file(&secrets.metrics_token_file.unwrap())?);
}
config
Ok(config)
}

View file

@ -20,7 +20,7 @@ pub async fn offline_repair(
}
info!("Loading configuration...");
let config = fill_secrets(read_config(config_file)?, secrets);
let config = fill_secrets(read_config(config_file)?, secrets)?;
info!("Initializing Garage main data store...");
let garage = Garage::new(config)?;

View file

@ -29,7 +29,7 @@ async fn wait_from(mut chan: watch::Receiver<bool>) {
pub async fn run_server(config_file: PathBuf, secrets: Secrets) -> Result<(), Error> {
info!("Loading configuration...");
let config = fill_secrets(read_config(config_file)?, secrets);
let config = fill_secrets(read_config(config_file)?, secrets)?;
// ---- Initialize Garage internals ----

View file

@ -238,6 +238,24 @@ pub fn read_config(config_file: PathBuf) -> Result<Config, Error> {
Ok(parsed_config)
}
pub fn read_secret_file(file_path: &String) -> Result<String, Error> {
#[cfg(unix)]
if std::env::var("GARAGE_ALLOW_WORLD_READABLE_SECRETS").as_deref() != Ok("true") {
Review

This check will actually fail on more recent versions of systemd, as they switched from plain chmods to ACLs, and it's not trivial to determine if something is readable by others or not.

I think it'd make more sense to drop this check to become a warning.

This check will actually *fail* on more recent versions of systemd, as they switched from plain chmods to ACLs, and it's not trivial to determine if something is readable by others or not. I think it'd make more sense to drop this check to become a warning.
Review
Upstream systemd issue: https://github.com/systemd/systemd/issues/29435
Review

workaround in #663

workaround in https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/663
use std::os::unix::fs::MetadataExt;
let metadata = std::fs::metadata(file_path)?;
if metadata.mode() & 0o077 != 0 {
return Err(format!("File {} is world-readable! (mode: 0{:o}, expected 0600)\nRefusing to start until this is fixed, or environment variable GARAGE_ALLOW_WORLD_READABLE_SECRETS is set to true.", file_path, metadata.mode()).into());
}
}
let mut file = std::fs::OpenOptions::new().read(true).open(file_path)?;
let mut secret_buf = String::new();
file.read_to_string(&mut secret_buf)?;
// trim_end: allows for use case such as `echo "$(openssl rand -hex 32)" > somefile`.
// also editors sometimes add a trailing newline
Ok(String::from(secret_buf.trim_end()))
}
fn secret_from_file(
secret: &mut Option<String>,
secret_file: &Option<String>,
@ -250,22 +268,7 @@ fn secret_from_file(
(Some(_), Some(_)) => {
return Err(format!("only one of `{}` and `{}_file` can be set", name, name).into());
}
(None, Some(file_path)) => {
#[cfg(unix)]
if std::env::var("GARAGE_ALLOW_WORLD_READABLE_SECRETS").as_deref() != Ok("true") {
use std::os::unix::fs::MetadataExt;
let metadata = std::fs::metadata(file_path)?;
if metadata.mode() & 0o077 != 0 {
return Err(format!("File {} is world-readable! (mode: 0{:o}, expected 0600)\nRefusing to start until this is fixed, or environment variable GARAGE_ALLOW_WORLD_READABLE_SECRETS is set to true.", file_path, metadata.mode()).into());
}
}
let mut file = std::fs::OpenOptions::new().read(true).open(file_path)?;
let mut secret_buf = String::new();
file.read_to_string(&mut secret_buf)?;
// trim_end: allows for use case such as `echo "$(openssl rand -hex 32)" > somefile`.
// also editors sometimes add a trailing newline
*secret = Some(String::from(secret_buf.trim_end()));
}
(None, Some(file_path)) => *secret = Some(read_secret_file(file_path)?),
}
Ok(())
}