diff --git a/docs/reference/index.md b/docs/reference/index.md index e73b59b7..e7ac11f8 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -258,6 +258,87 @@ As a convenience for production deployments, with the below environment variable CS_DATABASE__INSTALL_AWS_RDS_CERT_BUNDLE="true" ``` +## Command line options + + + +## Command line interface + +The CipherStash Proxy accepts command line arguments. +For example, the upstream database can be specified via command line arguments. +Useful for local dev and testing. + +### Usage + +```bash +cipherstash-proxy [OPTIONS] [DBNAME] [COMMAND] +``` + +### Commands + +- **encrypt** + Encrypt one or more columns in a table. This command requires a running and properly configured CipherStash Proxy instance. + +- **help** + Print the help message or detailed information for the specified subcommand(s). + +### Arguments + +- **DBNAME** + + Optional name of the database to connect to. If not specified, the tool will use the environment variables or configuration file settings. + + Default value: none + +- **-H, --db-host ** + + Optional database host. This value will default to the one defined in your environment or configuration file if not provided. + + Default value: `127.0.0.1` + +- **-u, --db-user ** + + Optional database user. This value will default to the one defined in your environment or configuration file if not provided. + + Default value: `postgres` + +- **-p, --config-file-path ** + + Specifies an optional path to a CipherStash Proxy configuration file. + If provided, the application attempts to load configuration settings from this file. + However, environment variables can be used instead of the file or to override any values defined within it. + + Default Value: `cipherstash-proxy.toml` + + Note: + The application will look for "cipherstash-proxy.toml" by default if no other file path is specified. + +- **-l, --log-level ** + + Sets an optional log level for the application, which controls the verbosity of the logging output. + This can be particularly useful for adjusting the level of detail in application logs + to suit different environments or debugging needs. + + Default Value: `info` + + Environment Variable: `CS_LOG__LEVEL` + + Possible Values: `error`, `warn`, `info`, `debug`, `trace` + +- **-f, --log-format ** + + Specifies an optional log format for the output logs. + The default log format is "pretty" when the application detects that it is running in a terminal session, + otherwise it defaults to "structured" for non-interactive environments. + The setting can be overridden by the corresponding environment variable. + + Default Value: `pretty` (if running in a terminal session), otherwise `structured` + + Environment Variable: `CS_LOG__FORMAT` + + Possible Values: `pretty`, `structured`, `text` + + ## Multitenant operation CipherStash Proxy supports multitenant applications using ZeroKMS keysets to provide strong cryptographic separation between tenants. diff --git a/packages/cipherstash-proxy-integration/src/migrate/mod.rs b/packages/cipherstash-proxy-integration/src/migrate/mod.rs index d120300f..701f3731 100644 --- a/packages/cipherstash-proxy-integration/src/migrate/mod.rs +++ b/packages/cipherstash-proxy-integration/src/migrate/mod.rs @@ -42,6 +42,9 @@ mod tests { log_level: LogLevel::Debug, log_format: LogFormat::Pretty, command: None, + db_host: None, + db_name: None, + db_user: None, }; let config = match TandemConfig::load(&args) { diff --git a/packages/cipherstash-proxy/src/cli/mod.rs b/packages/cipherstash-proxy/src/cli/mod.rs index 92a91c0b..f2341834 100644 --- a/packages/cipherstash-proxy/src/cli/mod.rs +++ b/packages/cipherstash-proxy/src/cli/mod.rs @@ -21,6 +21,21 @@ const DEFAULT_CONFIG_FILE: &str = "cipherstash-proxy.toml"; /// CipherStash Proxy keeps your sensitive data in PostgreSQL encrypted and searchable, with no changes to SQL. /// pub struct Args { + /// Optional database host to connect to. + /// Uses env or config file if not specified. + #[arg(short = 'H', long)] + pub db_host: Option, + + /// Optional database name to connect to. + /// Uses env or config file if not specified. + #[arg(value_name = "DBNAME")] + pub db_name: Option, + + /// Optional database user to connect as. + /// Uses env or config file if not specified. + #[arg(short = 'u', long)] + pub db_user: Option, + /// Optional path to a CipherStash Proxy configuration file. /// /// Default is "cipherstash-proxy.toml". diff --git a/packages/cipherstash-proxy/src/config/database.rs b/packages/cipherstash-proxy/src/config/database.rs index 464698cb..a41613ca 100644 --- a/packages/cipherstash-proxy/src/config/database.rs +++ b/packages/cipherstash-proxy/src/config/database.rs @@ -14,6 +14,8 @@ pub struct DatabaseConfig { pub port: u16, pub name: String, + + #[serde(default = "DatabaseConfig::default_username")] pub username: String, #[serde(deserialize_with = "protected_string_deserializer")] @@ -40,6 +42,10 @@ impl DatabaseConfig { 5432 } + pub fn default_username() -> String { + "postgres".to_string() + } + pub const fn default_config_reload_interval() -> u64 { 60 } diff --git a/packages/cipherstash-proxy/src/config/log.rs b/packages/cipherstash-proxy/src/config/log.rs index c2851b1a..b5f61011 100644 --- a/packages/cipherstash-proxy/src/config/log.rs +++ b/packages/cipherstash-proxy/src/config/log.rs @@ -130,30 +130,30 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars([("CS_LOG__LEVEL", Some("error"))], || { let config = - TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap(); + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml").unwrap(); assert_eq!(config.log.level, LogLevel::Error); }); temp_env::with_vars([("CS_LOG__LEVEL", Some("WARN"))], || { let config = - TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap(); + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml").unwrap(); assert_eq!(config.log.level, LogLevel::Warn); }); temp_env::with_vars([("CS_LOG__OUTPUT", Some("stderr"))], || { let config = - TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap(); + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml").unwrap(); assert_eq!(config.log.output, LogOutput::Stderr); }); temp_env::with_vars([("CS_LOG__FORMAT", Some("Pretty"))], || { let config = - TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap(); + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml").unwrap(); assert_eq!(config.log.format, LogFormat::Pretty); }); temp_env::with_vars([("CS_LOG__FORMAT", Some("dEbUG"))], || { - let config = TandemConfig::build("tests/config/cipherstash-proxy-test.toml"); + let config = TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml"); assert!(config.is_err()); assert!(matches!(config.unwrap_err(), Error::Config(_))); diff --git a/packages/cipherstash-proxy/src/config/tandem.rs b/packages/cipherstash-proxy/src/config/tandem.rs index bf018e99..e326c472 100644 --- a/packages/cipherstash-proxy/src/config/tandem.rs +++ b/packages/cipherstash-proxy/src/config/tandem.rs @@ -101,7 +101,7 @@ impl TandemConfig { ); println!("Loading config values from environment variables."); } - let mut config = TandemConfig::build(&args.config_file_path)?; + let mut config = TandemConfig::build(args)?; // If log level is default, it has not been set by the user in config if config.log.level == LogConfig::default_log_level() { @@ -116,7 +116,20 @@ impl TandemConfig { Ok(config) } - pub fn build(path: &str) -> Result { + pub fn build_path(path: &str) -> Result { + let args = Args { + config_file_path: path.to_string(), + db_host: None, + db_name: None, + db_user: None, + log_level: LogConfig::default_log_level(), + log_format: LogConfig::default_log_format(), + command: None, + }; + Self::build(&args) + } + + pub fn build(args: &Args) -> Result { // For parsing top-level values such as CS_HOST, CS_PORT // and for parsing nested env values such as CS_DATABASE__HOST, CS_DATABASE__PORT let cs_env_source = Environment::with_prefix(CS_PREFIX) @@ -161,9 +174,25 @@ impl TandemConfig { env })); + // Command line arguments override env vars + if let Some(db_host) = &args.db_host { + println!("Overriding database host from command line argument"); + env::set_var("CS_DATABASE__HOST", db_host); + } + + if let Some(dbname) = &args.db_name { + println!("Overriding database name from command line argument"); + env::set_var("CS_DATABASE__NAME", dbname); + } + + if let Some(db_user) = &args.db_user { + println!("Overriding database user from command line argument"); + env::set_var("CS_DATABASE__USER", db_user); + } + // Source order is important! let config = Config::builder() - .add_source(config::File::with_name(path).required(false)) + .add_source(config::File::with_name(&args.config_file_path).required(false)) .add_source(cs_env_source) .add_source(stash_setup_source) .build()? @@ -372,7 +401,8 @@ mod tests { ], || { let config = - TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap(); + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml") + .unwrap(); assert_eq!(config.encrypt.client_id, "CS_CLIENT_ID".to_string()); @@ -404,7 +434,8 @@ mod tests { ], || { let config = - TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap(); + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml") + .unwrap(); assert_eq!( &config.encrypt.client_id, @@ -418,7 +449,7 @@ mod tests { #[test] fn database_as_url() { let config = with_no_cs_vars(|| { - TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap() + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml").unwrap() }); assert_eq!( config.database.to_socket_address(), @@ -430,14 +461,15 @@ mod tests { fn dataset_as_uuid() { temp_env::with_vars_unset(["CS_ENCRYPT__DATASET_ID", "CS_DEFAULT_KEYSET_ID"], || { let config = with_no_cs_vars(|| { - TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap() + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml").unwrap() }); assert_eq!( config.encrypt.default_keyset_id, Some(Uuid::parse_str("484cd205-99e8-41ca-acfe-55a7e25a8ec2").unwrap()) ); - let config = TandemConfig::build("tests/config/cipherstash-proxy-bad-dataset.toml"); + let config = + TandemConfig::build_path("tests/config/cipherstash-proxy-bad-dataset.toml"); assert!(config.is_err()); assert!(matches!(config.unwrap_err(), Error::Config(_))); @@ -447,12 +479,13 @@ mod tests { #[test] fn prometheus_config() { with_no_cs_vars(|| { - let config = TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap(); + let config = + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml").unwrap(); assert!(!config.prometheus_enabled()); temp_env::with_vars([("CS_PROMETHEUS__ENABLED", Some("true"))], || { let config = - TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap(); + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml").unwrap(); assert!(config.prometheus_enabled()); assert!(config.prometheus.enabled); assert_eq!(config.prometheus.port, 9930); @@ -460,7 +493,7 @@ mod tests { temp_env::with_vars([("CS_PROMETHEUS__PORT", Some("7777"))], || { let config = - TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap(); + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml").unwrap(); assert!(!config.prometheus_enabled()); assert!(!config.prometheus.enabled); assert_eq!(config.prometheus.port, 7777); @@ -473,7 +506,8 @@ mod tests { ], || { let config = - TandemConfig::build("tests/config/cipherstash-proxy-test.toml").unwrap(); + TandemConfig::build_path("tests/config/cipherstash-proxy-test.toml") + .unwrap(); assert!(config.prometheus_enabled()); assert!(config.prometheus.enabled); assert_eq!(config.prometheus.port, 7777); @@ -549,7 +583,7 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars(env, || { - let config = TandemConfig::build("tests/config/unknown.toml").unwrap(); + let config = TandemConfig::build_path("tests/config/unknown.toml").unwrap(); assert_eq!( "E4UMRN47WJNSMAKR", config.auth.workspace_crn.workspace_id.to_string() @@ -567,7 +601,7 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars(env, || { - let config = TandemConfig::build("tests/config/unknown.toml"); + let config = TandemConfig::build_path("tests/config/unknown.toml"); assert_eq!( "E4UMRN47WJNSMAKR", @@ -583,7 +617,7 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars(env, || { - let config = TandemConfig::build("tests/config/unknown.toml"); + let config = TandemConfig::build_path("tests/config/unknown.toml"); assert!(config.is_err()); if let Err(e) = config { @@ -604,7 +638,7 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars(env, || { - let config = TandemConfig::build("tests/config/unknown.toml"); + let config = TandemConfig::build_path("tests/config/unknown.toml"); assert!(config.is_err()); if let Err(e) = config { @@ -620,7 +654,7 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars(env, || { - let result = TandemConfig::build("tests/config/unknown.toml"); + let result = TandemConfig::build_path("tests/config/unknown.toml"); assert!(result.is_err()); if let Err(err) = result { @@ -642,7 +676,7 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars(env, || { - let result = TandemConfig::build("tests/config/unknown.toml"); + let result = TandemConfig::build_path("tests/config/unknown.toml"); assert!(result.is_err()); if let Err(err) = result { @@ -671,7 +705,7 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars(env, || { - let result = TandemConfig::build("tests/config/unknown.toml"); + let result = TandemConfig::build_path("tests/config/unknown.toml"); assert!(result.is_err()); if let Err(err) = result { @@ -693,7 +727,7 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars(env, || { - let result = TandemConfig::build("tests/config/unknown.toml"); + let result = TandemConfig::build_path("tests/config/unknown.toml"); assert!(result.is_err()); if let Err(err) = result { @@ -725,7 +759,7 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars(env, || { - let result = TandemConfig::build("tests/config/unknown.toml"); + let result = TandemConfig::build_path("tests/config/unknown.toml"); assert!(result.is_err()); if let Err(err) = result { @@ -747,7 +781,7 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars(env, || { - let result = TandemConfig::build("tests/config/unknown.toml"); + let result = TandemConfig::build_path("tests/config/unknown.toml"); assert!(result.is_err()); if let Err(err) = result { @@ -765,7 +799,7 @@ mod tests { fn crn_can_load_from_toml() { with_no_cs_vars(|| { let config = - TandemConfig::build("tests/config/cipherstash-proxy-with-crn.toml").unwrap(); + TandemConfig::build_path("tests/config/cipherstash-proxy-with-crn.toml").unwrap(); assert_eq!( "E4UMRN47WJNSMAKR", config.auth.workspace_crn.workspace_id.to_string() @@ -786,7 +820,7 @@ mod tests { with_no_cs_vars(|| { temp_env::with_vars(env, || { - let config = TandemConfig::build("tests/config/unknown.toml").unwrap(); + let config = TandemConfig::build_path("tests/config/unknown.toml").unwrap(); assert_eq!( "E4UMRN47WJNSMAKR", config.auth.workspace_crn.workspace_id.to_string() diff --git a/packages/cipherstash-proxy/src/proxy/mod.rs b/packages/cipherstash-proxy/src/proxy/mod.rs index eff55840..05e6478c 100644 --- a/packages/cipherstash-proxy/src/proxy/mod.rs +++ b/packages/cipherstash-proxy/src/proxy/mod.rs @@ -145,7 +145,7 @@ mod tests { fn build_tandem_config(env: Vec<(&str, Option<&str>)>) -> TandemConfig { with_no_cs_vars(|| { temp_env::with_vars(env, || { - TandemConfig::build("tests/config/unknown.toml").unwrap() + TandemConfig::build_path("tests/config/unknown.toml").unwrap() }) }) }