Skip to content

Commit a5aa81a

Browse files
committed
feat: enhance configuration validation for user credentials and SSL settings
1 parent e045f50 commit a5aa81a

File tree

1 file changed

+56
-9
lines changed

1 file changed

+56
-9
lines changed

src/config.rs

Lines changed: 56 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -745,7 +745,31 @@ impl Config {
745745
}
746746
}
747747

748-
/// Validate additional rules that aren't covered by the JSON schema
748+
/// Validates the configuration against additional rules that aren't covered by the JSON schema.
749+
///
750+
/// This method performs deeper validation checks that can't be easily expressed in a JSON schema,
751+
/// such as verifying that certificate and key pairs are both present, validating base64 encoding
752+
/// of cryptographic material, and checking user password hashes.
753+
///
754+
/// # Arguments
755+
///
756+
/// * `config` - The configuration object to validate
757+
///
758+
/// # Returns
759+
///
760+
/// * `Ok(())` if all validations pass
761+
/// * `Err(anyhow::Error)` with descriptive message if any validation fails
762+
///
763+
/// # Validation Rules
764+
///
765+
/// This function validates:
766+
///
767+
/// - **SSL Configuration**: Ensures that if a certificate is provided, a key is also provided (and vice versa)
768+
/// - **Base64 Encoding**: Validates that certificates, keys, and RS256 keys are valid base64-encoded strings
769+
/// - **Port Range**: Ensures the visualization port is within a valid range (1-65534)
770+
/// - **IP Address Format**: Checks if the provided address is a valid IP address or special value
771+
/// - **User Credentials**: Validates that user password hashes are properly base64-encoded and follow
772+
/// the expected format from `openssl passwd`
749773
fn validate_specific_rules(config: &Config) -> Result<()> {
750774
debug!("Performing additional validation checks");
751775

@@ -794,6 +818,27 @@ impl Config {
794818
.decode(&config.visualization.rs256_public_key)
795819
.context("RS256 public key is not valid base64")?;
796820

821+
// if AccessConfig contains users, validate their credentials
822+
// User password should be a valid base64 string
823+
// the decoded string should be a valid password hash conforming to the openssl passwd -1 format
824+
for user in &config.access.0 {
825+
if !user.pass.is_empty() {
826+
let decoded_pass = base64::engine::general_purpose::STANDARD
827+
.decode(&user.pass)
828+
.context("User password is not valid base64")?;
829+
// Check if the decoded password is a valid hash
830+
// Password hash should start with $1$, $5$, $6$, $apr1$
831+
// Next contains the salt
832+
// The rest is the hash
833+
if !decoded_pass.starts_with(b"$1$")
834+
&& !decoded_pass.starts_with(b"$5$")
835+
&& !decoded_pass.starts_with(b"$6$")
836+
&& !decoded_pass.starts_with(b"$apr1$")
837+
{
838+
anyhow::bail!("User password is not a valid hash, you should use openssl passwd -5 <password> | base64 -w0");
839+
}
840+
}
841+
}
797842
Ok(())
798843
}
799844
}
@@ -863,14 +908,14 @@ pub fn output_config_schema() -> Result<()> {
863908
pub struct User {
864909
/// The username used for authentication
865910
pub user: String,
866-
911+
867912
/// Base64-encoded password hash
868-
///
913+
///
869914
/// This should be created using: `openssl passwd -1 <password> | base64 -w0`
870915
pub pass: String,
871-
916+
872917
/// List of permission strings that define what actions the user can perform
873-
///
918+
///
874919
/// Common permissions include:
875920
/// * "read:api" - Allows read-only access to API endpoints
876921
/// * "write:api" - Allows modification operations on API endpoints
@@ -883,6 +928,10 @@ pub struct User {
883928
/// This structure defines the users who can access the application
884929
/// and their associated permissions. Each user has a username, password hash,
885930
/// and a list of permissions that control what actions they can perform.
931+
/// A valid password hash is generated using the `openssl` command:
932+
/// ```bash
933+
/// openssl passwd -5 admin123 | base64 -w0
934+
/// ```
886935
///
887936
/// # Example
888937
///
@@ -910,7 +959,7 @@ impl Default for User {
910959
Self {
911960
user: "admin".to_string(),
912961
// Default password hash for "admin123" (should be changed in production)
913-
pass: "JDEkcEdsUXY1d1gkcEoyaloyY1BRQmI4Qy5NUUZnTzZqLwo=".to_string(),
962+
pass: "JDUkM2E2OUZwQW0xejZBbWV2QSRvMlhhN0lxcVdVU1VPTUh6UVJiM3JjRlRhZy9WYjdpSWJtZUJFaXA3Y1ZECg==".to_string(),
914963
permissions: vec![
915964
"read:api".to_string(),
916965
"write:api".to_string(),
@@ -922,8 +971,6 @@ impl Default for User {
922971

923972
impl Default for AccessConfig {
924973
fn default() -> Self {
925-
Self(vec![
926-
User::default(),
927-
])
974+
Self(vec![User::default()])
928975
}
929976
}

0 commit comments

Comments
 (0)