Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(rust): provide additional ways to pass the database password #8775

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Database
- OCKAM_DATABASE_INSTANCE: Database host, port and name in the form `{host}:{port}/{database_name}`.
- OCKAM_DATABASE_USER: The database user
- OCKAM_DATABASE_PASSWORD: The database user password
- OCKAM_DATABASE_USER_AND_PASSWORD: The database user password as `{"username":"pgadmin", "password":"12345"}` for environments that provide both at the same time.

Tracing
- OCKAM_TELEMETRY_EXPORT: set this variable to a false value to disable tracing: `0`, `false`, `no`. Default value: `true`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use ockam_core::env::get_env;
use ockam_core::errcode::{Kind, Origin};
use ockam_core::{Error, Result};
use percent_encoding::NON_ALPHANUMERIC;
use serde_json::Value;
use std::fs::create_dir_all;
use std::path::{Path, PathBuf};

Expand All @@ -16,6 +17,8 @@ pub const OCKAM_DATABASE_INSTANCE: &str = "OCKAM_DATABASE_INSTANCE";
pub const OCKAM_DATABASE_USER: &str = "OCKAM_DATABASE_USER";
/// Database password
pub const OCKAM_DATABASE_PASSWORD: &str = "OCKAM_DATABASE_PASSWORD";
/// Database user + password in the format {"username":"pgadmin", "password":"s3cr3t"}
pub const OCKAM_DATABASE_USERNAME_AND_PASSWORD: &str = "OCKAM_DATABASE_USERNAME_AND_PASSWORD";

/// Configuration for the database.
/// We either use Sqlite or Postgres
Expand Down Expand Up @@ -203,28 +206,57 @@ impl DatabaseConfiguration {
}

/// We can either get the connection string directly from the OCKAM_DATABASE_CONNECTION_URL environment variable,
/// or we can build it from 3 other variables.
/// or we can build it from other variables. Either from:
///
/// - The database instance name + user + password,
/// - Or the database instance name + user & password as a single JSON string.
///
/// This useful when:
/// - The password is rotated externally.
///
/// - The password is rotated externally, by the AWS Secrets Manager service.
/// - The password needs to be url encoded.
///
fn get_database_connection_url() -> Result<Option<String>> {
let connection_string = match get_env::<String>(OCKAM_DATABASE_CONNECTION_URL)? {
Some(connection_string) => connection_string,
None => {
match (
let (instance, user, password) = match (
get_env::<String>(OCKAM_DATABASE_INSTANCE)?,
get_env::<String>(OCKAM_DATABASE_USER)?,
get_env::<String>(OCKAM_DATABASE_PASSWORD)?,
get_env::<String>(OCKAM_DATABASE_USERNAME_AND_PASSWORD)?,
) {
(Some(instance), Some(user), Some(password)) => {
// A password can contain special characters, so we need to encode it.
let url_encoded_password =
percent_encoding::utf8_percent_encode(&password, NON_ALPHANUMERIC);
format!("postgres://{user}:{url_encoded_password}@{instance}")
(Some(instance), Some(user), Some(password), None) => (instance, user, password),
(Some(instance), None, None, Some(user_and_password)) => {
let parsed: Value = serde_json::from_str(&user_and_password).map_err(|_| {
Error::new(
Origin::Api,
Kind::Invalid,
format!("Expected a JSON object. Got: {user_and_password}"),
)
})?;
if let (Some(user), Some(password)) =
(parsed["username"].as_str(), parsed["password"].as_str())
{
(instance, user.to_string(), password.to_string())
} else {
return Err(Error::new(
Origin::Api,
Kind::Invalid,
format!(
"Expected the username and password as `{}`.
Got: {user_and_password}",
r#"{"username":"pgadmin", "password":"12345"}"#
),
));
}
}
_ => return Ok(None),
}
};
// A password can contain special characters, so we need to encode it.
let url_encoded_password =
percent_encoding::utf8_percent_encode(&password, NON_ALPHANUMERIC);
format!("postgres://{user}:{url_encoded_password}@{instance}")
}
};
check_connection_string_format(&connection_string)?;
Expand Down Expand Up @@ -304,6 +336,24 @@ mod tests {
Ok(())
}

#[test]
fn test_make_connection_url_from_separate_env_variables_user_and_password() -> Result<()> {
env::set_var(OCKAM_DATABASE_INSTANCE, "localhost:5432/ockam");
env::set_var(
OCKAM_DATABASE_USERNAME_AND_PASSWORD,
r#"{"username":"pgadmin", "password":"xR::7Zp(h|<g<Q*t:5T"}"#,
);
assert_eq!(
get_database_connection_url().unwrap(),
Some(
"postgres://pgadmin:xR%3A%3A7Zp%28h%7C%3Cg%3CQ%2At%3A5T@localhost:5432/ockam"
.into()
),
"the password is url encoded"
);
Ok(())
}

#[test]
fn test_valid_connection_strings() -> Result<()> {
assert!(
Expand Down
Loading