@@ -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<()> {
863908pub 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
923972impl Default for AccessConfig {
924973 fn default ( ) -> Self {
925- Self ( vec ! [
926- User :: default ( ) ,
927- ] )
974+ Self ( vec ! [ User :: default ( ) ] )
928975 }
929976}
0 commit comments