2021-03-20 19:38:44 +00:00
//! Contains type and functions related to Garage configuration file
2023-09-11 16:34:59 +00:00
use std ::convert ::TryFrom ;
2020-04-23 17:05:46 +00:00
use std ::io ::Read ;
use std ::net ::SocketAddr ;
use std ::path ::PathBuf ;
2021-03-18 02:12:27 +00:00
use serde ::{ de , Deserialize } ;
2020-04-23 17:05:46 +00:00
use crate ::error ::Error ;
2023-09-29 16:38:30 +00:00
use crate ::socket_address ::UnixOrTCPSocketAddress ;
2020-04-23 17:05:46 +00:00
2021-03-20 19:38:44 +00:00
/// Represent the whole configuration
2020-04-23 17:05:46 +00:00
#[ derive(Deserialize, Debug, Clone) ]
pub struct Config {
2021-03-21 23:01:44 +00:00
/// Path where to store metadata. Should be fast, but low volume
2020-04-23 17:05:46 +00:00
pub metadata_dir : PathBuf ,
2021-03-21 23:01:44 +00:00
/// Path where to store data. Can be slower, but need higher volume
2023-09-04 12:49:49 +00:00
pub data_dir : DataDirEnum ,
2020-04-23 17:05:46 +00:00
2023-06-09 14:23:21 +00:00
/// Whether to fsync after all metadata transactions (disabled by default)
#[ serde(default) ]
pub metadata_fsync : bool ,
/// Whether to fsync after all data block writes (disabled by default)
#[ serde(default) ]
pub data_fsync : bool ,
2021-05-28 10:36:22 +00:00
/// Size of data blocks to save to disk
2023-09-11 16:34:59 +00:00
#[ serde(
deserialize_with = " deserialize_capacity " ,
default = " default_block_size "
) ]
2021-05-28 10:36:22 +00:00
pub block_size : usize ,
/// Replication mode. Supported values:
/// - none, 1 -> no replication
/// - 2 -> 2-way replication
/// - 3 -> 3-way replication
// (we can add more aliases for this later)
pub replication_mode : String ,
2021-12-15 10:26:43 +00:00
/// Zstd compression level used on data blocks
#[ serde(
deserialize_with = " deserialize_compression " ,
default = " default_compression "
) ]
pub compression_level : Option < i32 > ,
2021-10-14 09:50:12 +00:00
/// RPC secret key: 32 bytes hex encoded
2023-01-04 17:28:56 +00:00
pub rpc_secret : Option < String > ,
/// Optional file where RPC secret key is read from
pub rpc_secret_file : Option < String > ,
2021-10-14 09:50:12 +00:00
2021-03-21 23:01:44 +00:00
/// Address to bind for RPC
2020-04-23 17:05:46 +00:00
pub rpc_bind_addr : SocketAddr ,
2021-10-15 09:05:09 +00:00
/// Public IP address of this node
2022-09-14 14:09:38 +00:00
pub rpc_public_addr : Option < String > ,
2020-04-23 17:05:46 +00:00
2022-09-19 18:12:19 +00:00
/// Timeout for Netapp's ping messagess
pub rpc_ping_timeout_msec : Option < u64 > ,
/// Timeout for Netapp RPC calls
pub rpc_timeout_msec : Option < u64 > ,
2022-10-18 16:38:20 +00:00
// -- Bootstraping and discovery
2021-03-21 23:01:44 +00:00
/// Bootstrap peers RPC address
2022-09-14 14:09:38 +00:00
#[ serde(default) ]
pub bootstrap_peers : Vec < String > ,
2022-10-18 16:38:20 +00:00
/// Configuration for automatic node discovery through Consul
#[ serde(default) ]
pub consul_discovery : Option < ConsulDiscoveryConfig > ,
/// Configuration for automatic node discovery through Kubernetes
2022-03-06 13:50:00 +00:00
#[ serde(default) ]
2022-10-18 16:38:20 +00:00
pub kubernetes_discovery : Option < KubernetesDiscoveryConfig > ,
2020-04-23 17:05:46 +00:00
2022-06-08 08:01:44 +00:00
// -- DB
/// Database engine to use for metadata (options: sled, sqlite, lmdb)
#[ serde(default = " default_db_engine " ) ]
pub db_engine : String ,
2021-05-03 15:27:43 +00:00
/// Sled cache size, in bytes
2023-09-11 16:34:59 +00:00
#[ serde(
deserialize_with = " deserialize_capacity " ,
default = " default_sled_cache_capacity "
) ]
pub sled_cache_capacity : usize ,
2021-05-03 15:27:43 +00:00
/// Sled flush interval in milliseconds
#[ serde(default = " default_sled_flush_every_ms " ) ]
pub sled_flush_every_ms : u64 ,
2023-09-11 16:03:20 +00:00
/// LMDB map size
2023-09-11 16:34:59 +00:00
#[ serde(deserialize_with = " deserialize_capacity " , default) ]
pub lmdb_map_size : usize ,
2023-09-11 16:03:20 +00:00
2022-06-08 08:01:44 +00:00
// -- APIs
2021-03-21 23:01:44 +00:00
/// Configuration for S3 api
2022-05-10 11:16:57 +00:00
pub s3_api : S3ApiConfig ,
/// Configuration for K2V api
pub k2v_api : Option < K2VApiConfig > ,
2020-10-31 16:28:56 +00:00
2021-03-21 23:01:44 +00:00
/// Configuration for serving files as normal web server
2022-09-07 15:54:16 +00:00
pub s3_web : Option < WebConfig > ,
2021-09-28 06:57:20 +00:00
/// Configuration for the admin API endpoint
2022-03-10 09:51:40 +00:00
#[ serde(default = " Default::default " ) ]
2022-02-22 14:25:13 +00:00
pub admin : AdminConfig ,
2020-04-23 17:05:46 +00:00
}
2023-09-04 12:49:49 +00:00
/// Value for data_dir: either a single directory or a list of dirs with attributes
#[ derive(Deserialize, Debug, Clone) ]
#[ serde(untagged) ]
pub enum DataDirEnum {
Single ( PathBuf ) ,
Multiple ( Vec < DataDir > ) ,
}
#[ derive(Deserialize, Debug, Clone) ]
pub struct DataDir {
/// Path to the data directory
pub path : PathBuf ,
/// Capacity of the drive (required if read_only is false)
#[ serde(default) ]
pub capacity : Option < String > ,
/// Whether this is a legacy read-only path (capacity should be None)
#[ serde(default) ]
pub read_only : bool ,
}
2021-03-20 19:38:44 +00:00
/// Configuration for S3 api
2020-04-24 17:46:52 +00:00
#[ derive(Deserialize, Debug, Clone) ]
2022-05-10 11:16:57 +00:00
pub struct S3ApiConfig {
2021-03-21 23:01:44 +00:00
/// Address and port to bind for api serving
2023-09-29 16:38:30 +00:00
pub api_bind_addr : Option < UnixOrTCPSocketAddress > ,
2021-03-21 23:01:44 +00:00
/// S3 region to use
2020-04-24 17:46:52 +00:00
pub s3_region : String ,
2021-11-11 10:26:02 +00:00
/// Suffix to remove from domain name to find bucket. If None,
/// vhost-style S3 request are disabled
pub root_domain : Option < String > ,
2020-04-24 17:46:52 +00:00
}
2022-05-10 11:16:57 +00:00
/// Configuration for K2V api
#[ derive(Deserialize, Debug, Clone) ]
pub struct K2VApiConfig {
/// Address and port to bind for api serving
2023-09-29 16:38:30 +00:00
pub api_bind_addr : UnixOrTCPSocketAddress ,
2022-05-10 11:16:57 +00:00
}
2021-03-20 19:38:44 +00:00
/// Configuration for serving files as normal web server
2020-10-31 16:28:56 +00:00
#[ derive(Deserialize, Debug, Clone) ]
pub struct WebConfig {
2021-03-21 23:01:44 +00:00
/// Address and port to bind for web serving
2023-09-29 16:38:30 +00:00
pub bind_addr : UnixOrTCPSocketAddress ,
2021-03-21 23:01:44 +00:00
/// Suffix to remove from domain name to find bucket
2020-11-10 08:57:07 +00:00
pub root_domain : String ,
2020-10-31 16:28:56 +00:00
}
2021-09-28 06:57:20 +00:00
/// Configuration for the admin and monitoring HTTP API
2022-03-10 09:51:40 +00:00
#[ derive(Deserialize, Debug, Clone, Default) ]
2021-09-28 06:57:20 +00:00
pub struct AdminConfig {
/// Address and port to bind for admin API serving
2023-09-29 16:38:30 +00:00
pub api_bind_addr : Option < UnixOrTCPSocketAddress > ,
2023-02-03 14:27:39 +00:00
2022-05-24 10:16:39 +00:00
/// Bearer token to use to scrape metrics
pub metrics_token : Option < String > ,
2023-02-03 14:27:39 +00:00
/// File to read metrics token from
pub metrics_token_file : Option < String > ,
2022-05-24 10:16:39 +00:00
/// Bearer token to use to access Admin API endpoints
pub admin_token : Option < String > ,
2023-02-03 14:27:39 +00:00
/// File to read admin token from
pub admin_token_file : Option < String > ,
2022-02-17 22:28:23 +00:00
/// OTLP server to where to export traces
2022-02-22 14:25:13 +00:00
pub trace_sink : Option < String > ,
2021-09-28 06:57:20 +00:00
}
2023-05-15 22:15:56 +00:00
#[ derive(Deserialize, Debug, Clone, Default) ]
#[ serde(rename_all = " lowercase " ) ]
2023-05-11 02:11:14 +00:00
pub enum ConsulDiscoveryAPI {
2023-05-15 22:15:56 +00:00
#[ default ]
2023-05-11 02:11:14 +00:00
Catalog ,
Agent ,
2023-05-10 19:20:39 +00:00
}
2022-10-18 16:38:20 +00:00
#[ derive(Deserialize, Debug, Clone) ]
pub struct ConsulDiscoveryConfig {
2023-05-11 02:11:14 +00:00
/// The consul api to use when registering: either `catalog` (the default) or `agent`
2023-05-21 03:25:57 +00:00
#[ serde(default) ]
2023-05-15 22:15:56 +00:00
pub api : ConsulDiscoveryAPI ,
2022-10-18 19:17:11 +00:00
/// Consul http or https address to connect to to discover more peers
pub consul_http_addr : String ,
2022-10-18 16:38:20 +00:00
/// Consul service name to use
pub service_name : String ,
/// CA TLS certificate to use when connecting to Consul
pub ca_cert : Option < String > ,
/// Client TLS certificate to use when connecting to Consul
pub client_cert : Option < String > ,
/// Client TLS key to use when connecting to Consul
pub client_key : Option < String > ,
2023-05-10 19:20:39 +00:00
/// /// Token to use for connecting to consul
2023-05-15 22:15:56 +00:00
pub token : Option < String > ,
2022-10-18 16:38:20 +00:00
/// Skip TLS hostname verification
#[ serde(default) ]
pub tls_skip_verify : bool ,
2023-05-10 19:20:39 +00:00
/// Additional tags to add to the service
2023-05-05 22:18:24 +00:00
#[ serde(default) ]
pub tags : Vec < String > ,
2023-05-10 19:20:39 +00:00
/// Additional service metadata to add
2023-05-09 01:29:47 +00:00
#[ serde(default) ]
pub meta : Option < std ::collections ::HashMap < String , String > > ,
2022-10-18 16:38:20 +00:00
}
#[ derive(Deserialize, Debug, Clone) ]
pub struct KubernetesDiscoveryConfig {
/// Kubernetes namespace the service discovery resources are be created in
pub namespace : String ,
/// Service name to filter for in k8s custom resources
pub service_name : String ,
/// Skip creation of the garagenodes CRD
#[ serde(default) ]
pub skip_crd : bool ,
}
2022-06-08 08:01:44 +00:00
fn default_db_engine ( ) -> String {
2023-05-17 12:30:53 +00:00
" lmdb " . into ( )
2022-06-08 08:01:44 +00:00
}
2023-09-11 16:34:59 +00:00
fn default_sled_cache_capacity ( ) -> usize {
2021-05-03 15:27:43 +00:00
128 * 1024 * 1024
}
fn default_sled_flush_every_ms ( ) -> u64 {
2000
}
2020-04-23 17:05:46 +00:00
fn default_block_size ( ) -> usize {
1048576
}
2021-03-20 19:38:44 +00:00
/// Read and parse configuration
2020-04-23 17:05:46 +00:00
pub fn read_config ( config_file : PathBuf ) -> Result < Config , Error > {
let mut file = std ::fs ::OpenOptions ::new ( )
. read ( true )
. open ( config_file . as_path ( ) ) ? ;
let mut config = String ::new ( ) ;
file . read_to_string ( & mut config ) ? ;
2023-01-04 17:28:56 +00:00
let mut parsed_config : Config = toml ::from_str ( & config ) ? ;
2023-02-03 14:27:39 +00:00
secret_from_file (
& mut parsed_config . rpc_secret ,
2023-02-06 11:23:55 +00:00
& parsed_config . rpc_secret_file ,
2023-02-03 14:27:39 +00:00
" rpc_secret " ,
) ? ;
secret_from_file (
& mut parsed_config . admin . metrics_token ,
2023-02-06 11:23:55 +00:00
& parsed_config . admin . metrics_token_file ,
2023-02-03 14:27:39 +00:00
" admin.metrics_token " ,
) ? ;
secret_from_file (
& mut parsed_config . admin . admin_token ,
2023-02-06 11:23:55 +00:00
& parsed_config . admin . admin_token_file ,
2023-02-03 14:27:39 +00:00
" admin.admin_token " ,
) ? ;
Ok ( parsed_config )
}
fn secret_from_file (
secret : & mut Option < String > ,
2023-02-06 11:23:55 +00:00
secret_file : & Option < String > ,
2023-02-03 14:27:39 +00:00
name : & 'static str ,
) -> Result < ( ) , Error > {
match ( & secret , & secret_file ) {
( _ , None ) = > {
2023-01-07 12:49:03 +00:00
// no-op
}
( Some ( _ ) , Some ( _ ) ) = > {
2023-02-03 14:27:39 +00:00
return Err ( format! ( " only one of ` {} ` and ` {} _file` can be set " , name , name ) . into ( ) ) ;
2023-01-07 12:49:03 +00:00
}
2023-02-03 14:27:39 +00:00
( 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 ;
2023-05-09 19:49:34 +00:00
let metadata = std ::fs ::metadata ( file_path ) ? ;
2023-02-03 14:27:39 +00:00
if metadata . mode ( ) & 0o077 ! = 0 {
return Err ( format! ( " File {} is world-readable! (mode: 0 {:o} , expected 0600) \n Refusing 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 ) ? ;
2023-01-04 17:28:56 +00:00
// trim_end: allows for use case such as `echo "$(openssl rand -hex 32)" > somefile`.
// also editors sometimes add a trailing newline
2023-02-03 14:27:39 +00:00
* secret = Some ( String ::from ( secret_buf . trim_end ( ) ) ) ;
2023-01-04 17:28:56 +00:00
}
2023-02-03 14:27:39 +00:00
}
Ok ( ( ) )
2020-04-23 17:05:46 +00:00
}
2021-03-18 02:12:27 +00:00
2021-12-15 10:26:43 +00:00
fn default_compression ( ) -> Option < i32 > {
Some ( 1 )
}
fn deserialize_compression < ' de , D > ( deserializer : D ) -> Result < Option < i32 > , D ::Error >
where
D : de ::Deserializer < ' de > ,
{
struct OptionVisitor ;
impl < ' de > serde ::de ::Visitor < ' de > for OptionVisitor {
type Value = Option < i32 > ;
fn expecting ( & self , formatter : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
formatter . write_str ( " int or 'none' " )
}
fn visit_str < E > ( self , value : & str ) -> Result < Self ::Value , E >
where
E : de ::Error ,
{
if value . eq_ignore_ascii_case ( " none " ) {
Ok ( None )
} else {
Err ( E ::custom ( format! (
" Invalid compression level: '{}', should be a number, or 'none' " ,
value
) ) )
}
}
fn visit_i64 < E > ( self , v : i64 ) -> Result < Self ::Value , E >
where
E : de ::Error ,
{
i32 ::try_from ( v )
. map ( Some )
. map_err ( | _ | E ::custom ( " Compression level out of bound " . to_owned ( ) ) )
}
fn visit_u64 < E > ( self , v : u64 ) -> Result < Self ::Value , E >
where
E : de ::Error ,
{
i32 ::try_from ( v )
. map ( Some )
. map_err ( | _ | E ::custom ( " Compression level out of bound " . to_owned ( ) ) )
}
}
deserializer . deserialize_any ( OptionVisitor )
}
2023-01-07 12:49:15 +00:00
2023-09-11 16:34:59 +00:00
fn deserialize_capacity < ' de , D > ( deserializer : D ) -> Result < usize , D ::Error >
where
D : de ::Deserializer < ' de > ,
{
struct CapacityVisitor ;
impl < ' de > serde ::de ::Visitor < ' de > for CapacityVisitor {
type Value = usize ;
fn expecting ( & self , formatter : & mut std ::fmt ::Formatter ) -> std ::fmt ::Result {
formatter . write_str ( " int or '<capacity>' " )
}
fn visit_str < E > ( self , value : & str ) -> Result < Self ::Value , E >
where
E : de ::Error ,
{
value
. parse ::< bytesize ::ByteSize > ( )
. map ( | x | x . as_u64 ( ) )
. map_err ( | e | E ::custom ( format! ( " invalid capacity value: {} " , e ) ) )
. and_then ( | v | {
usize ::try_from ( v )
. map_err ( | _ | E ::custom ( " capacity value out of bound " . to_owned ( ) ) )
} )
}
fn visit_i64 < E > ( self , v : i64 ) -> Result < Self ::Value , E >
where
E : de ::Error ,
{
usize ::try_from ( v ) . map_err ( | _ | E ::custom ( " capacity value out of bound " . to_owned ( ) ) )
}
fn visit_u64 < E > ( self , v : u64 ) -> Result < Self ::Value , E >
where
E : de ::Error ,
{
usize ::try_from ( v ) . map_err ( | _ | E ::custom ( " capacity value out of bound " . to_owned ( ) ) )
}
}
deserializer . deserialize_any ( CapacityVisitor )
}
2023-01-07 12:49:15 +00:00
#[ cfg(test) ]
mod tests {
use crate ::error ::Error ;
use std ::fs ::File ;
use std ::io ::Write ;
#[ test ]
2023-02-03 14:27:39 +00:00
fn test_rpc_secret ( ) -> Result < ( ) , Error > {
2023-01-07 12:49:15 +00:00
let path2 = mktemp ::Temp ::new_file ( ) ? ;
let mut file2 = File ::create ( path2 . as_path ( ) ) ? ;
writeln! (
file2 ,
r #"
metadata_dir = " /tmp/garage/meta "
data_dir = " /tmp/garage/data "
replication_mode = " 3 "
rpc_bind_addr = " [::]:3901 "
rpc_secret = " foo "
[ s3_api ]
s3_region = " garage "
api_bind_addr = " [::]:3900 "
" #
) ? ;
let config = super ::read_config ( path2 . to_path_buf ( ) ) ? ;
assert_eq! ( " foo " , config . rpc_secret . unwrap ( ) ) ;
drop ( path2 ) ;
drop ( file2 ) ;
Ok ( ( ) )
}
#[ test ]
fn test_rpc_secret_file_works ( ) -> Result < ( ) , Error > {
let path_secret = mktemp ::Temp ::new_file ( ) ? ;
let mut file_secret = File ::create ( path_secret . as_path ( ) ) ? ;
writeln! ( file_secret , " foo " ) ? ;
drop ( file_secret ) ;
let path_config = mktemp ::Temp ::new_file ( ) ? ;
let mut file_config = File ::create ( path_config . as_path ( ) ) ? ;
2023-02-03 14:27:39 +00:00
let path_secret_path = path_secret . as_path ( ) ;
2023-01-07 12:49:15 +00:00
writeln! (
file_config ,
r #"
metadata_dir = " /tmp/garage/meta "
data_dir = " /tmp/garage/data "
replication_mode = " 3 "
rpc_bind_addr = " [::]:3901 "
2023-02-03 14:27:39 +00:00
rpc_secret_file = " {} "
2023-05-05 22:18:24 +00:00
2023-01-07 12:49:15 +00:00
[ s3_api ]
s3_region = " garage "
api_bind_addr = " [::]:3900 "
2023-02-03 14:27:39 +00:00
" #,
path_secret_path . display ( )
2023-01-07 12:49:15 +00:00
) ? ;
let config = super ::read_config ( path_config . to_path_buf ( ) ) ? ;
assert_eq! ( " foo " , config . rpc_secret . unwrap ( ) ) ;
2023-02-03 14:27:39 +00:00
#[ cfg(unix) ]
{
use std ::os ::unix ::fs ::PermissionsExt ;
let metadata = std ::fs ::metadata ( & path_secret_path ) ? ;
let mut perm = metadata . permissions ( ) ;
perm . set_mode ( 0o660 ) ;
std ::fs ::set_permissions ( & path_secret_path , perm ) ? ;
std ::env ::set_var ( " GARAGE_ALLOW_WORLD_READABLE_SECRETS " , " false " ) ;
assert! ( super ::read_config ( path_config . to_path_buf ( ) ) . is_err ( ) ) ;
std ::env ::set_var ( " GARAGE_ALLOW_WORLD_READABLE_SECRETS " , " true " ) ;
assert! ( super ::read_config ( path_config . to_path_buf ( ) ) . is_ok ( ) ) ;
}
2023-01-07 12:49:15 +00:00
drop ( path_config ) ;
drop ( path_secret ) ;
drop ( file_config ) ;
Ok ( ( ) )
}
#[ test ]
fn test_rcp_secret_and_rpc_secret_file_cannot_be_set_both ( ) -> Result < ( ) , Error > {
let path_config = mktemp ::Temp ::new_file ( ) ? ;
let mut file_config = File ::create ( path_config . as_path ( ) ) ? ;
writeln! (
file_config ,
r #"
metadata_dir = " /tmp/garage/meta "
data_dir = " /tmp/garage/data "
replication_mode = " 3 "
rpc_bind_addr = " [::]:3901 "
rpc_secret = " dummy "
rpc_secret_file = " dummy "
2023-05-05 22:18:24 +00:00
2023-01-07 12:49:15 +00:00
[ s3_api ]
s3_region = " garage "
api_bind_addr = " [::]:3900 "
" #
) ? ;
assert_eq! (
" only one of `rpc_secret` and `rpc_secret_file` can be set " ,
super ::read_config ( path_config . to_path_buf ( ) )
. unwrap_err ( )
. to_string ( )
) ;
drop ( path_config ) ;
drop ( file_config ) ;
Ok ( ( ) )
}
}