Skip to content

Commit d4e4473

Browse files
committed
feat(spider-storage)!: Read database credentials from the environment.
Storage read the database username and password from its config file, which the Helm chart renders into a Kubernetes ConfigMap -- leaving the password in plaintext. It now reads them from the `SPIDER_STORAGE_DB_USERNAME` and `SPIDER_STORAGE_DB_PASSWORD` environment variables (both required) via a dedicated `DatabaseCredentials` type, so they can instead be injected from a Secret and kept out of the ConfigMap. BREAKING CHANGE: the storage service now requires the database credentials to be provided via environment variables; they are no longer read from the config file.
1 parent 1fe99d6 commit d4e4473

5 files changed

Lines changed: 104 additions & 7 deletions

File tree

components/spider-storage/src/bin/grpc_server.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ use spider_proto_rust::storage::ResourceGroupManagementServiceServer;
1212
use spider_proto_rust::storage::SchedulerRegistrationServiceServer;
1313
use spider_proto_rust::storage::SessionManagementServiceServer;
1414
use spider_proto_rust::storage::TaskInstanceManagementServiceServer;
15+
use spider_storage::DatabaseCredentials;
1516
use spider_storage::ServerConfig;
1617
use spider_storage::grpc::GrpcServiceState;
1718
use spider_storage::state::runtime::create_runtime;
@@ -35,7 +36,8 @@ struct Cli {
3536
async fn main() -> Result<(), Box<dyn Error>> {
3637
let _log_guard = set_up_logging();
3738
let cli = Cli::parse();
38-
let server_config = ServerConfig::from_yaml_file(&cli.config)?;
39+
let mut server_config = ServerConfig::from_yaml_file(&cli.config)?;
40+
server_config.runtime.db_config.credentials = DatabaseCredentials::from_env()?;
3941
let listen_addr = SocketAddr::new(server_config.host, server_config.port);
4042
let (runtime, cancellation_token) = create_runtime(&server_config.runtime).await?;
4143
let grpc_service =

components/spider-storage/src/config.rs

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::net::IpAddr;
33
use secrecy::SecretString;
44
use serde::Deserialize;
55
use serde::Serialize;
6+
use thiserror::Error;
67

78
use crate::state::runtime::RuntimeConfig;
89

@@ -28,8 +29,97 @@ pub struct DatabaseConfig {
2829
pub host: String,
2930
pub port: u16,
3031
pub name: String,
32+
pub max_connections: u32,
33+
34+
#[serde(skip)]
35+
pub credentials: DatabaseCredentials,
36+
}
37+
38+
/// Credentials for authenticating with the database.
39+
#[derive(Clone, Debug)]
40+
pub struct DatabaseCredentials {
3141
pub username: String,
32-
#[serde(skip_serializing)]
3342
pub password: SecretString,
34-
pub max_connections: u32,
43+
}
44+
45+
impl DatabaseCredentials {
46+
/// Reads the database credentials from the [`DB_USERNAME_ENV`] and [`DB_PASSWORD_ENV`]
47+
/// environment variables.
48+
///
49+
/// # Returns
50+
///
51+
/// The credentials on success.
52+
///
53+
/// # Errors
54+
///
55+
/// Returns an error if:
56+
///
57+
/// * [`CredentialsError::MissingEnvVar`] if either environment variable is unset.
58+
pub fn from_env() -> Result<Self, CredentialsError> {
59+
let username = std::env::var(DB_USERNAME_ENV)
60+
.map_err(|_| CredentialsError::MissingEnvVar(DB_USERNAME_ENV))?;
61+
let password = std::env::var(DB_PASSWORD_ENV)
62+
.map_err(|_| CredentialsError::MissingEnvVar(DB_PASSWORD_ENV))?;
63+
Ok(Self {
64+
username,
65+
password: SecretString::from(password),
66+
})
67+
}
68+
}
69+
70+
impl Default for DatabaseCredentials {
71+
fn default() -> Self {
72+
Self {
73+
username: String::new(),
74+
password: SecretString::from(String::new()),
75+
}
76+
}
77+
}
78+
79+
/// An error returned while reading database credentials from the environment.
80+
#[derive(Debug, Error)]
81+
pub enum CredentialsError {
82+
#[error("required environment variable `{0}` is not set")]
83+
MissingEnvVar(&'static str),
84+
}
85+
86+
/// Environment variable that supplies the database username.
87+
const DB_USERNAME_ENV: &str = "SPIDER_STORAGE_DB_USERNAME";
88+
89+
/// Environment variable that supplies the database password.
90+
const DB_PASSWORD_ENV: &str = "SPIDER_STORAGE_DB_PASSWORD";
91+
92+
#[cfg(test)]
93+
mod tests {
94+
use secrecy::ExposeSecret;
95+
96+
use super::*;
97+
98+
#[test]
99+
fn read_credential_from_env() -> anyhow::Result<()> {
100+
// SAFETY: these variables are read only by this test, so mutating this process-global state
101+
// does not race with other tests.
102+
103+
// Both variables set: the credentials are read from the environment.
104+
unsafe {
105+
std::env::set_var(DB_USERNAME_ENV, "env-user");
106+
std::env::set_var(DB_PASSWORD_ENV, "env-pass");
107+
}
108+
let both_set = DatabaseCredentials::from_env();
109+
110+
// A variable unset: an error naming the missing variable is returned.
111+
unsafe { std::env::remove_var(DB_PASSWORD_ENV) };
112+
let password_unset = DatabaseCredentials::from_env();
113+
114+
unsafe { std::env::remove_var(DB_USERNAME_ENV) };
115+
116+
let credentials = both_set?;
117+
assert_eq!(credentials.username, "env-user");
118+
assert_eq!(credentials.password.expose_secret(), "env-pass");
119+
assert!(matches!(
120+
password_unset,
121+
Err(CredentialsError::MissingEnvVar(var)) if var == DB_PASSWORD_ENV
122+
));
123+
Ok(())
124+
}
35125
}

components/spider-storage/src/db/mariadb.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ impl MariaDbStorageConnector {
6565
.host(&config.host)
6666
.port(config.port)
6767
.database(&config.name)
68-
.username(&config.username)
69-
.password(config.password.expose_secret());
68+
.username(&config.credentials.username)
69+
.password(config.credentials.password.expose_secret());
7070

7171
let pool = sqlx::mysql::MySqlPoolOptions::new()
7272
.max_connections(config.max_connections)

components/spider-storage/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,7 @@ pub mod ready_queue;
77
pub mod state;
88
pub mod task_instance_pool;
99

10+
pub use config::CredentialsError;
1011
pub use config::DatabaseConfig;
12+
pub use config::DatabaseCredentials;
1113
pub use config::ServerConfig;

components/spider-storage/tests/mariadb_infra.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use secrecy::SecretString;
22
use spider_core::types::id::ResourceGroupId;
33
use spider_storage::DatabaseConfig;
4+
use spider_storage::DatabaseCredentials;
45
use spider_storage::db::MariaDbStorageConnector;
56
use spider_storage::db::ResourceGroupManagement;
67

@@ -47,9 +48,11 @@ pub fn create_mariadb_config() -> DatabaseConfig {
4748
host: "localhost".to_string(),
4849
port,
4950
name: database,
50-
username,
51-
password: SecretString::from(password),
5251
max_connections: 5,
52+
credentials: DatabaseCredentials {
53+
username,
54+
password: SecretString::from(password),
55+
},
5356
}
5457
}
5558

0 commit comments

Comments
 (0)