Compare commits

...

7 Commits

Author SHA1 Message Date
Alex
4b3dee2ca3 Merge pull request 's3 api: add missing CORS headers to PostObject responses (fix #609)' (#656) from fix-cors-post-object into main-0.8.x
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/656
2023-10-26 09:17:14 +00:00
Alex Auvolat
ac04934dae s3 api: add missing CORS headers to PostObject responses (fix #609) 2023-10-20 10:37:48 +02:00
Alex
0215b11402 Merge pull request 'Add support for specifying rpc_secret_file, metrics_token_file and admin_token_file using environment variables' (#643) from networkException/garage:token-file-env into main-0.8.x
Reviewed-on: https://git.deuxfleurs.fr/Deuxfleurs/garage/pulls/643
2023-10-19 09:33:12 +00:00
networkException
8599051c49
garage: support specifying token / secret as environment variables
this patch adds support for specifying the `rpc_secret_file`,
`metrics_token_file` and `admin_token_file` as environment variables.
2023-10-19 03:39:02 +02:00
networkException
4a19ee94bb
garage: fix admin-token description 2023-10-19 03:31:50 +02:00
networkException
c99cb58d71
util: move reading secret file into seperate helper
this patch moves the logic to read a secret file (and check for correct
permissions) from `secret_from_file` into a new `read_secret_file`
helper.
2023-10-19 03:29:48 +02:00
networkException
5feb6a1f64
docs: add documentation for specifying token / secret file as environment variables 2023-10-19 03:28:44 +02:00
6 changed files with 67 additions and 28 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 This value can be different between nodes, compression is done by the node which receive the
API call. 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 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 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 given in the configuration variable `rpc_secret_file`, or specified as an
environment variable `GARAGE_RPC_SECRET`. 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` ### `rpc_bind_addr`
The address and port on which to bind for inter-cluster communcations 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, 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. 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 The token for accessing the Metrics endpoint. If this token is not set, the
Metrics endpoint can be accessed without access control. 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` was introduced in Garage `v0.7.2`.
`metrics_token_file` and the `GARAGE_METRICS_TOKEN` environment variable are supported since Garage `v0.8.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 The token for accessing all of the other administration endpoints. If this
token is not set, access to these endpoints is disabled entirely. 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` was introduced in Garage `v0.7.2`.
`admin_token_file` and the `GARAGE_ADMIN_TOKEN` environment variable are supported since Garage `v0.8.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` ### `trace_sink`

View File

@ -15,6 +15,7 @@ use serde::Deserialize;
use garage_model::garage::Garage; use garage_model::garage::Garage;
use crate::s3::cors::*;
use crate::s3::error::*; use crate::s3::error::*;
use crate::s3::put::{get_headers, save_stream}; use crate::s3::put::{get_headers, save_stream};
use crate::s3::xml as s3_xml; use crate::s3::xml as s3_xml;
@ -242,7 +243,7 @@ pub async fn handle_post_object(
let etag = format!("\"{}\"", md5); let etag = format!("\"{}\"", md5);
let resp = if let Some(mut target) = params let mut resp = if let Some(mut target) = params
.get("success_action_redirect") .get("success_action_redirect")
.and_then(|h| h.to_str().ok()) .and_then(|h| h.to_str().ok())
.and_then(|u| url::Url::parse(u).ok()) .and_then(|u| url::Url::parse(u).ok())
@ -262,8 +263,7 @@ pub async fn handle_post_object(
} else { } else {
let path = head let path = head
.uri .uri
.into_parts() .path_and_query()
.path_and_query
.map(|paq| paq.path().to_string()) .map(|paq| paq.path().to_string())
.unwrap_or_else(|| "/".to_string()); .unwrap_or_else(|| "/".to_string());
let authority = head let authority = head
@ -308,6 +308,13 @@ pub async fn handle_post_object(
} }
}; };
let matching_cors_rule =
find_matching_cors_rule(&bucket, &Request::from_parts(head, Body::empty()))?;
if let Some(rule) = matching_cors_rule {
add_cors_headers(&mut resp, rule)
.ok_or_internal_error("Invalid bucket CORS configuration")?;
}
Ok(resp) Ok(resp)
} }

View File

@ -25,7 +25,7 @@ use structopt::StructOpt;
use netapp::util::parse_and_resolve_peer_addr; use netapp::util::parse_and_resolve_peer_addr;
use netapp::NetworkKey; use netapp::NetworkKey;
use garage_util::config::Config; use garage_util::config::{read_secret_file, Config};
use garage_util::error::*; use garage_util::error::*;
use garage_rpc::system::*; use garage_rpc::system::*;
@ -70,15 +70,30 @@ pub struct Secrets {
#[structopt(short = "s", long = "rpc-secret", env = "GARAGE_RPC_SECRET")] #[structopt(short = "s", long = "rpc-secret", env = "GARAGE_RPC_SECRET")]
pub rpc_secret: Option<String>, 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 /// running the Garage daemon
#[structopt(long = "admin-token", env = "GARAGE_ADMIN_TOKEN")] #[structopt(long = "admin-token", env = "GARAGE_ADMIN_TOKEN")]
pub admin_token: Option<String>, 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 /// Metrics API authentication token, replaces admin.metrics_token in config.toml when
/// running the Garage daemon /// running the Garage daemon
#[structopt(long = "metrics-token", env = "GARAGE_METRICS_TOKEN")] #[structopt(long = "metrics-token", env = "GARAGE_METRICS_TOKEN")]
pub metrics_token: Option<String>, 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] #[tokio::main]
@ -259,15 +274,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() { if secrets.rpc_secret.is_some() {
config.rpc_secret = secrets.rpc_secret; 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() { if secrets.admin_token.is_some() {
config.admin.admin_token = secrets.admin_token; 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() { if secrets.metrics_token.is_some() {
config.admin.metrics_token = secrets.metrics_token; 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..."); 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..."); info!("Initializing Garage main data store...");
let garage = Garage::new(config)?; 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> { pub async fn run_server(config_file: PathBuf, secrets: Secrets) -> Result<(), Error> {
info!("Loading configuration..."); info!("Loading configuration...");
let config = fill_secrets(read_config(config_file)?, secrets); let config = fill_secrets(read_config(config_file)?, secrets)?;
// ---- Initialize Garage internals ---- // ---- Initialize Garage internals ----

View File

@ -238,6 +238,24 @@ pub fn read_config(config_file: PathBuf) -> Result<Config, Error> {
Ok(parsed_config) 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") {
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( fn secret_from_file(
secret: &mut Option<String>, secret: &mut Option<String>,
secret_file: &Option<String>, secret_file: &Option<String>,
@ -250,22 +268,7 @@ fn secret_from_file(
(Some(_), Some(_)) => { (Some(_), Some(_)) => {
return Err(format!("only one of `{}` and `{}_file` can be set", name, name).into()); return Err(format!("only one of `{}` and `{}_file` can be set", name, name).into());
} }
(None, Some(file_path)) => { (None, Some(file_path)) => *secret = Some(read_secret_file(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()));
}
} }
Ok(()) Ok(())
} }