Skip to content

Commit f153b49

Browse files
committed
Add env var override to all configuration settings
This allows users to configure VSS-Server entirely through environment variables, without a configuration file. We hence remove the requirement that a path to a configuration file always be passed as an argument. If a file is passed as an argument, and it does not exist, we now abort startup. Also consolidate host and port settings into single socket address setting.
1 parent 32cfbcd commit f153b49

File tree

3 files changed

+190
-112
lines changed

3 files changed

+190
-112
lines changed

rust/server/src/main.rs

Lines changed: 41 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
#![deny(rustdoc::private_intra_doc_links)]
1010
#![deny(missing_docs)]
1111

12-
use std::net::SocketAddr;
1312
use std::sync::Arc;
1413

1514
use tokio::net::TcpListener;
@@ -25,34 +24,19 @@ use auth_impls::jwt::JWTAuthorizer;
2524
#[cfg(feature = "sigs")]
2625
use auth_impls::signature::SignatureValidatingAuthorizer;
2726
use impls::postgres_store::{PostgresPlaintextBackend, PostgresTlsBackend};
28-
use util::config::{Config, ServerConfig};
2927
use vss_service::VssService;
3028

3129
mod util;
3230
mod vss_service;
3331

3432
fn main() {
3533
let args: Vec<String> = std::env::args().collect();
36-
if args.len() != 2 {
37-
eprintln!("Usage: {} <config-file-path>", args[0]);
38-
std::process::exit(1);
39-
}
4034

41-
let Config { server_config: ServerConfig { host, port }, jwt_auth_config, postgresql_config } =
42-
match util::config::load_config(&args[1]) {
43-
Ok(cfg) => cfg,
44-
Err(e) => {
45-
eprintln!("Failed to load configuration: {}", e);
46-
std::process::exit(1);
47-
},
48-
};
49-
let addr: SocketAddr = match format!("{}:{}", host, port).parse() {
50-
Ok(addr) => addr,
51-
Err(e) => {
52-
eprintln!("Invalid host/port configuration: {}", e);
53-
std::process::exit(1);
54-
},
55-
};
35+
let config =
36+
util::config::load_configuration(args.get(1).map(|s| s.as_str())).unwrap_or_else(|e| {
37+
eprintln!("Failed to load configuration: {}", e);
38+
std::process::exit(-1);
39+
});
5640

5741
let runtime = match tokio::runtime::Builder::new_multi_thread().enable_all().build() {
5842
Ok(runtime) => Arc::new(runtime),
@@ -74,17 +58,8 @@ fn main() {
7458
let mut authorizer: Option<Arc<dyn Authorizer>> = None;
7559
#[cfg(feature = "jwt")]
7660
{
77-
let rsa_pem_env = match std::env::var("VSS_JWT_RSA_PEM") {
78-
Ok(env) => Some(env),
79-
Err(std::env::VarError::NotPresent) => None,
80-
Err(e) => {
81-
println!("Failed to load the VSS_JWT_RSA_PEM env var: {}", e);
82-
std::process::exit(-1);
83-
},
84-
};
85-
let rsa_pem = rsa_pem_env.or(jwt_auth_config.and_then(|config| config.rsa_pem));
86-
if let Some(pem) = rsa_pem {
87-
authorizer = match JWTAuthorizer::new(pem.as_str()).await {
61+
if let Some(rsa_pem) = config.rsa_pem {
62+
authorizer = match JWTAuthorizer::new(&rsa_pem).await {
8863
Ok(auth) => {
8964
println!("Configured JWT authorizer with RSA public key");
9065
Some(Arc::new(auth))
@@ -110,43 +85,47 @@ fn main() {
11085
Arc::new(NoopAuthorizer {})
11186
};
11287

113-
let postgresql_config =
114-
postgresql_config.expect("PostgreSQLConfig must be defined in config file.");
115-
let endpoint = postgresql_config.to_postgresql_endpoint();
116-
let default_db = postgresql_config.default_database;
117-
let vss_db = postgresql_config.vss_database;
118-
let store: Arc<dyn KvStore> = if let Some(tls_config) = postgresql_config.tls {
119-
let postgres_tls_backend = match PostgresTlsBackend::new(
120-
&endpoint,
121-
&default_db,
122-
&vss_db,
123-
tls_config.crt_pem.as_deref(),
88+
let store: Arc<dyn KvStore> = if let Some(crt_pem) = config.tls_config {
89+
let postgres_tls_backend = PostgresTlsBackend::new(
90+
&config.postgresql_prefix,
91+
&config.default_db,
92+
&config.vss_db,
93+
crt_pem.as_deref(),
12494
)
12595
.await
126-
{
127-
Ok(backend) => backend,
128-
Err(e) => {
129-
println!("Failed to start postgres tls backend: {}", e);
130-
std::process::exit(-1);
131-
},
132-
};
96+
.unwrap_or_else(|e| {
97+
println!("Failed to start postgres TLS backend: {}", e);
98+
std::process::exit(-1);
99+
});
100+
println!(
101+
"Connected to PostgreSQL TLS backend with DSN: {}/{}",
102+
config.postgresql_prefix, config.vss_db
103+
);
133104
Arc::new(postgres_tls_backend)
134105
} else {
135-
let postgres_plaintext_backend =
136-
match PostgresPlaintextBackend::new(&endpoint, &default_db, &vss_db).await {
137-
Ok(backend) => backend,
138-
Err(e) => {
139-
println!("Failed to start postgres plaintext backend: {}", e);
140-
std::process::exit(-1);
141-
},
142-
};
106+
let postgres_plaintext_backend = PostgresPlaintextBackend::new(
107+
&config.postgresql_prefix,
108+
&config.default_db,
109+
&config.vss_db,
110+
)
111+
.await
112+
.unwrap_or_else(|e| {
113+
println!("Failed to start postgres plaintext backend: {}", e);
114+
std::process::exit(-1);
115+
});
116+
println!(
117+
"Connected to PostgreSQL plaintext backend with DSN: {}/{}",
118+
config.postgresql_prefix, config.vss_db
119+
);
143120
Arc::new(postgres_plaintext_backend)
144121
};
145-
println!("Connected to PostgreSQL backend with DSN: {}/{}", endpoint, vss_db);
146122

147-
let rest_svc_listener =
148-
TcpListener::bind(&addr).await.expect("Failed to bind listening port");
149-
println!("Listening for incoming connections on {}", addr);
123+
let rest_svc_listener = TcpListener::bind(&config.bind_address).await.unwrap_or_else(|e| {
124+
println!("Failed to bind listening port: {}", e);
125+
std::process::exit(-1);
126+
});
127+
println!("Listening for incoming connections on {}", config.bind_address);
128+
150129
loop {
151130
tokio::select! {
152131
res = rest_svc_listener.accept() => {

rust/server/src/util/config.rs

Lines changed: 141 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,159 @@
11
use serde::Deserialize;
2+
use std::net::SocketAddr;
23

3-
#[derive(Deserialize)]
4-
pub(crate) struct Config {
5-
pub(crate) server_config: ServerConfig,
6-
pub(crate) jwt_auth_config: Option<JwtAuthConfig>,
7-
pub(crate) postgresql_config: Option<PostgreSQLConfig>,
4+
const BIND_ADDR_VAR: &str = "VSS_BIND_ADDRESS";
5+
const JWT_RSA_PEM_VAR: &str = "VSS_JWT_RSA_PEM";
6+
const PSQL_USER_VAR: &str = "VSS_PSQL_USERNAME";
7+
const PSQL_PASS_VAR: &str = "VSS_PSQL_PASSWORD";
8+
const PSQL_ADDR_VAR: &str = "VSS_PSQL_ADDRESS";
9+
const PSQL_DB_VAR: &str = "VSS_PSQL_DEFAULT_DB";
10+
const PSQL_VSS_DB_VAR: &str = "VSS_PSQL_VSS_DB";
11+
const PSQL_TLS_VAR: &str = "VSS_PSQL_TLS";
12+
const PSQL_CERT_PEM_VAR: &str = "VSS_PSQL_CRT_PEM";
13+
14+
// The structure of the toml config file. Any settings specified therein can be overriden by the corresponding
15+
// environment variable.
16+
#[derive(Deserialize, Default)]
17+
struct TomlConfig {
18+
server_config: Option<ServerConfig>,
19+
jwt_auth_config: Option<JwtAuthConfig>,
20+
postgresql_config: Option<PostgreSQLConfig>,
821
}
922

1023
#[derive(Deserialize)]
11-
pub(crate) struct ServerConfig {
12-
pub(crate) host: String,
13-
pub(crate) port: u16,
24+
struct ServerConfig {
25+
bind_address: Option<SocketAddr>,
1426
}
1527

1628
#[derive(Deserialize)]
17-
pub(crate) struct JwtAuthConfig {
18-
pub(crate) rsa_pem: Option<String>,
29+
struct JwtAuthConfig {
30+
rsa_pem: Option<String>,
1931
}
2032

2133
#[derive(Deserialize)]
22-
pub(crate) struct PostgreSQLConfig {
23-
pub(crate) username: Option<String>, // Optional in TOML, can be overridden by env
24-
pub(crate) password: Option<String>, // Optional in TOML, can be overridden by env
25-
pub(crate) host: String,
26-
pub(crate) port: u16,
27-
pub(crate) default_database: String,
28-
pub(crate) vss_database: String,
29-
pub(crate) tls: Option<TlsConfig>,
34+
struct PostgreSQLConfig {
35+
username: Option<String>,
36+
password: Option<String>,
37+
address: Option<SocketAddr>,
38+
default_database: Option<String>,
39+
vss_database: Option<String>,
40+
tls: Option<TlsConfig>,
3041
}
3142

3243
#[derive(Deserialize)]
33-
pub(crate) struct TlsConfig {
34-
pub(crate) crt_pem: Option<String>,
35-
}
36-
37-
impl PostgreSQLConfig {
38-
pub(crate) fn to_postgresql_endpoint(&self) -> String {
39-
let username_env = std::env::var("VSS_POSTGRESQL_USERNAME");
40-
let username = username_env.as_ref()
41-
.ok()
42-
.or_else(|| self.username.as_ref())
43-
.expect("PostgreSQL database username must be provided in config or env var VSS_POSTGRESQL_USERNAME must be set.");
44-
let password_env = std::env::var("VSS_POSTGRESQL_PASSWORD");
45-
let password = password_env.as_ref()
46-
.ok()
47-
.or_else(|| self.password.as_ref())
48-
.expect("PostgreSQL database password must be provided in config or env var VSS_POSTGRESQL_PASSWORD must be set.");
49-
50-
format!("postgresql://{}:{}@{}:{}", username, password, self.host, self.port)
44+
struct TlsConfig {
45+
crt_pem: Option<String>,
46+
}
47+
48+
// Encapsulates the result of reading both the environment variables and the config file.
49+
pub(crate) struct Configuration {
50+
pub(crate) bind_address: SocketAddr,
51+
pub(crate) rsa_pem: Option<String>,
52+
pub(crate) postgresql_prefix: String,
53+
pub(crate) default_db: String,
54+
pub(crate) vss_db: String,
55+
pub(crate) tls_config: Option<Option<String>>,
56+
}
57+
58+
#[inline]
59+
fn read_env(env_var: &str) -> Result<Option<String>, String> {
60+
match std::env::var(env_var) {
61+
Ok(env) => Ok(Some(env)),
62+
Err(std::env::VarError::NotPresent) => Ok(None),
63+
Err(e) => Err(format!("Failed to load the {} environment variable: {}", env_var, e)),
5164
}
5265
}
5366

54-
pub(crate) fn load_config(config_path: &str) -> Result<Config, Box<dyn std::error::Error>> {
55-
let config_str = std::fs::read_to_string(config_path)?;
56-
let config: Config = toml::from_str(&config_str)?;
57-
Ok(config)
67+
#[inline]
68+
fn read_config<'a, T: std::fmt::Display>(
69+
env: Option<T>, config: Option<T>, item: &str, var_name: &str,
70+
) -> Result<T, String> {
71+
env.or(config).ok_or(format!(
72+
"{} must be provided in the configuration file or the environment variable {} must be set.",
73+
item, var_name
74+
))
75+
}
76+
77+
pub(crate) fn load_configuration(config_file_path: Option<&str>) -> Result<Configuration, String> {
78+
let TomlConfig { server_config, jwt_auth_config, postgresql_config } = match config_file_path {
79+
Some(path) => {
80+
let config_file = std::fs::read_to_string(path)
81+
.map_err(|e| format!("Failed to read configuration file: {}", e))?;
82+
toml::from_str(&config_file)
83+
.map_err(|e| format!("Failed to parse configuration file: {}", e))?
84+
},
85+
None => TomlConfig::default(), // All fields are set to `None`
86+
};
87+
88+
let bind_address_env = read_env(BIND_ADDR_VAR)?
89+
.map(|addr| {
90+
addr.parse().map_err(|e| {
91+
format!("Unable to parse the bind address environment variable: {}", e)
92+
})
93+
})
94+
.transpose()?;
95+
let bind_address = read_config(
96+
bind_address_env,
97+
server_config.and_then(|c| c.bind_address),
98+
"VSS server bind address",
99+
BIND_ADDR_VAR,
100+
)?;
101+
102+
let rsa_pem_env = read_env(JWT_RSA_PEM_VAR)?;
103+
let rsa_pem = rsa_pem_env.or(jwt_auth_config.and_then(|config| config.rsa_pem));
104+
105+
let username_env = read_env(PSQL_USER_VAR)?;
106+
let password_env = read_env(PSQL_PASS_VAR)?;
107+
let address_env: Option<SocketAddr> = read_env(PSQL_ADDR_VAR)?
108+
.map(|address| {
109+
address.parse().map_err(|e| {
110+
format!("Unable to parse the postgresql address environment variable: {}", e)
111+
})
112+
})
113+
.transpose()?;
114+
let default_db_env = read_env(PSQL_DB_VAR)?;
115+
let vss_db_env = read_env(PSQL_VSS_DB_VAR)?;
116+
let tls_config_env = read_env(PSQL_TLS_VAR)?;
117+
let crt_pem_env = read_env(PSQL_CERT_PEM_VAR)?;
118+
119+
let (
120+
username_config,
121+
password_config,
122+
address_config,
123+
default_db_config,
124+
vss_db_config,
125+
tls_config,
126+
) = match postgresql_config {
127+
Some(c) => (
128+
c.username,
129+
c.password,
130+
c.address,
131+
c.default_database,
132+
c.vss_database,
133+
c.tls.map(|tls| tls.crt_pem),
134+
),
135+
None => (None, None, None, None, None, None),
136+
};
137+
138+
let username =
139+
read_config(username_env, username_config, "PostgreSQL database username", PSQL_USER_VAR)?;
140+
let password =
141+
read_config(password_env, password_config, "PostgreSQL database password", PSQL_PASS_VAR)?;
142+
let address =
143+
read_config(address_env, address_config, "PostgreSQL service address", PSQL_ADDR_VAR)?;
144+
let default_db = read_config(
145+
default_db_env,
146+
default_db_config,
147+
"PostgreSQL default database name",
148+
PSQL_DB_VAR,
149+
)?;
150+
let vss_db =
151+
read_config(vss_db_env, vss_db_config, "PostgreSQL vss database name", PSQL_VSS_DB_VAR)?;
152+
153+
let tls_config =
154+
crt_pem_env.map(|pem| Some(pem)).or(tls_config_env.map(|_| None)).or(tls_config);
155+
156+
let postgresql_prefix = format!("postgresql://{}:{}@{}", username, password, address);
157+
158+
Ok(Configuration { bind_address, rsa_pem, postgresql_prefix, default_db, vss_db, tls_config })
58159
}

rust/server/vss-server-config.toml

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
[server_config]
2-
host = "127.0.0.1"
3-
port = 8080
2+
bind_address = "127.0.0.1:8080" # Optional in TOML, can be overridden by env var `VSS_BIND_ADDRESS`
43

54
# Uncomment the table below to verify JWT tokens in the HTTP Authorization header against the given RSA public key,
65
# can be overridden by env var `VSS_JWT_RSA_PEM`
@@ -11,17 +10,16 @@ port = 8080
1110
# """
1211

1312
[postgresql_config]
14-
username = "postgres" # Optional in TOML, can be overridden by env var `VSS_POSTGRESQL_USERNAME`
15-
password = "postgres" # Optional in TOML, can be overridden by env var `VSS_POSTGRESQL_PASSWORD`
16-
host = "localhost"
17-
port = 5432
13+
username = "postgres" # Optional in TOML, can be overridden by env var `VSS_PSQL_USERNAME`
14+
password = "postgres" # Optional in TOML, can be overridden by env var `VSS_PSQL_PASSWORD`
15+
address = "127.0.0.1:5432" # Optional in TOML, can be overridden by env var `VSS_PSQL_ADDRESS`
1816
# We first connect to `default_database` to create `vss_database` if it does not exist
19-
default_database = "postgres"
20-
vss_database = "vss"
17+
default_database = "postgres" # Optional in TOML, can be overridden by env var `VSS_PSQL_DEFAULT_DB`
18+
vss_database = "vss" # Optional in TOML, can be overridden by env var `VSS_PSQL_VSS_DB`
2119

22-
# [postgresql_config.tls] # Uncomment to make TLS connections to the postgres database
20+
# [postgresql_config.tls] # Uncomment, or set env var `VSS_PSQL_TLS` to make TLS connections to the postgres database
2321
#
24-
# Uncomment the lines below to add a root certificate to your trusted root certificates
22+
# Uncomment the lines below, or set `VSS_PSQL_CRT_PEM`, to add a root certificate to your trusted root certificates
2523
#
2624
# crt_pem = """
2725
# -----BEGIN CERTIFICATE-----

0 commit comments

Comments
 (0)