diff --git a/config.yaml b/config.yaml index 8ec7653..4ed53a0 100644 --- a/config.yaml +++ b/config.yaml @@ -4,7 +4,10 @@ options: ranger-admin-password: description: | - Password used for the policy manager (Web interface) + The password for Ranger Admin user. + Password can not be changed using this property after initial deployment. + It can be changed in the UI. + Password should be minimum 8 characters with min one alphabet and one numeric. default: "rangerR0cks!" type: string tls-secret-name: @@ -122,3 +125,11 @@ options: functionality for Ranger service in ms. type: int default: 3000 + ranger-usersync-password: + description: | + The password for the user that synchronizes users and groups from LDAP to Ranger admin. + Password can not be changed using this property after initial deployment. + It can be changed in the UI. + Password should be minimum 8 characters with min one alphabet and one numeric. + type: string + default: rangerR0cks! diff --git a/src/charm.py b/src/charm.py index 678a0a6..49cdb82 100755 --- a/src/charm.py +++ b/src/charm.py @@ -225,6 +225,7 @@ def _configure_ranger_admin(self, container): "DB_USER": db_conn["user"], "DB_PWD": db_conn["password"], "RANGER_ADMIN_PWD": self.config["ranger-admin-password"], + "RANGER_USERSYNC_PWD": self.config["ranger-usersync-password"], "JAVA_OPTS": "-Duser.timezone=UTC0", } config = render("admin-config.jinja", context) @@ -258,6 +259,7 @@ def _configure_ranger_usersync(self, container): context.update( { "POLICY_MGR_URL": self.config["policy-mgr-url"], + "RANGER_USERSYNC_PWD": self.config["ranger-usersync-password"], } ) config = render("ranger-usersync-config.jinja", context) diff --git a/src/structured_config.py b/src/structured_config.py index 3d33d7f..b1df2a4 100644 --- a/src/structured_config.py +++ b/src/structured_config.py @@ -57,7 +57,7 @@ class CharmConfig(BaseConfigModel): sync_ldap_user_group_name_attribute: Optional[str] sync_ldap_deltasync: bool sync_interval: Optional[int] - ranger_usersync_password: Optional[str] + ranger_usersync_password: str policy_mgr_url: str charm_function: FunctionType lookup_timeout: int @@ -133,3 +133,28 @@ def lookup_timeout_validator(cls, value: str) -> Optional[int]: if 1000 <= int_value <= 10000: return int_value raise ValueError("Value out of range.") + + @validator("ranger_admin_password", "ranger_usersync_password") + @classmethod + def password_validator(cls, value: str) -> Optional[str]: + """Validate if the password meets the following requirements. + + - Minimum 8 characters in length + - Contains at least one alphabetic character + - Contains at least one numeric character + + Args: + value: The password to validate. + + Returns: + value: The validated password if it meets the requirements. + + Raises: + ValueError: If the password does not meet the requirements. + """ + pattern = re.compile( + r"^(?=.*[A-Za-z])(?=.*\d)(?=.*[\W_])[A-Za-z\d\W_]{8,}$" + ) + if pattern.match(value): + return value + raise ValueError("Password does not match requirements.") diff --git a/templates/admin-config.jinja b/templates/admin-config.jinja index 37e9b5b..14518c0 100644 --- a/templates/admin-config.jinja +++ b/templates/admin-config.jinja @@ -72,10 +72,10 @@ db_password={{ DB_PWD }} # change password. Password for below mentioned users can be changed only once using this property. #PLEASE NOTE :: Password should be minimum 8 characters with min one alphabet and one numeric. -rangerAdmin_password={{ RANGER_ADMIN_PWD | default("rangerR0cks!") }} -rangerTagsync_password={{ RANGER_ADMIN_PWD | default("rangerR0cks!") }} -rangerUsersync_password={{ RANGER_ADMIN_PWD | default("rangerR0cks!") }} -keyadmin_password={{ RANGER_ADMIN_PWD | default("rangerR0cks!") }} +rangerAdmin_password={{ RANGER_ADMIN_PWD }} +rangerTagsync_password={{ RANGER_ADMIN_PWD }} +rangerUsersync_password={{ RANGER_USERSYNC_PWD }} +keyadmin_password={{ RANGER_ADMIN_PWD }} #Source for Audit Store. Currently solr and elasticsearch are supported. diff --git a/templates/ranger-usersync-config.jinja b/templates/ranger-usersync-config.jinja index 92de7f3..4424256 100644 --- a/templates/ranger-usersync-config.jinja +++ b/templates/ranger-usersync-config.jinja @@ -27,7 +27,7 @@ unix_group=ranger # This password should be changed from the default. # Please note that this password should be as per rangerusersync user in ranger admin. -rangerUsersync_password= rangerR0cks! +rangerUsersync_password= {{ RANGER_USERSYNC_PWD }} hadoop_conf=/etc/hadoop/conf diff --git a/tests/integration/conftest.py b/tests/integration/conftest.py index 91f53b3..c626591 100644 --- a/tests/integration/conftest.py +++ b/tests/integration/conftest.py @@ -36,6 +36,7 @@ async def deploy(ops_test: OpsTest): resources=resources, application_name=APP_NAME, num_units=1, + config={"ranger-usersync-password": "P@ssw0rd1234"}, ) await ops_test.model.wait_for_idle( diff --git a/tests/integration/test_usersync.py b/tests/integration/test_usersync.py index 13e7877..147bbca 100644 --- a/tests/integration/test_usersync.py +++ b/tests/integration/test_usersync.py @@ -31,6 +31,7 @@ async def test_user_sync(self, ops_test: OpsTest): ranger_config = { "charm-function": "usersync", + "ranger-usersync-password": "P@ssw0rd1234", } charm = await ops_test.build_charm(".") diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index 9181940..b8daed1 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -99,6 +99,7 @@ def test_admin_ready(self): "DB_USER": "postgres_user", "DB_PWD": "admin", "RANGER_ADMIN_PWD": "rangerR0cks!", + "RANGER_USERSYNC_PWD": "rangerR0cks!", "JAVA_OPTS": "-Duser.timezone=UTC0", }, } @@ -135,6 +136,7 @@ def test_usersync_ready(self): "startup": "enabled", "environment": { "POLICY_MGR_URL": "http://ranger-k8s:6080", + "RANGER_USERSYNC_PWD": "rangerR0cks!", "SYNC_GROUP_USER_MAP_SYNC_ENABLED": True, "SYNC_GROUP_SEARCH_ENABLED": True, "SYNC_GROUP_SEARCH_BASE": "dc=canonical,dc=dev,dc=com", @@ -172,10 +174,10 @@ def test_config_changed(self): simulate_admin_lifecycle(harness) # Update the config. - self.harness.update_config({"ranger-admin-password": "secure-pass"}) + self.harness.update_config({"ranger-admin-password": "s3cure-pass"}) # The new plan reflects the change. - want_admin_password = "secure-pass" # nosec + want_admin_password = "s3cure-pass" # nosec got_admin_password = harness.get_container_pebble_plan( "ranger" ).to_dict()["services"]["ranger"]["environment"]["RANGER_ADMIN_PWD"] diff --git a/tests/unit/test_structured_config.py b/tests/unit/test_structured_config.py index 808691c..a07d58a 100644 --- a/tests/unit/test_structured_config.py +++ b/tests/unit/test_structured_config.py @@ -49,6 +49,33 @@ def test_string_values(_harness) -> None: check_valid_values(_harness, "sync-ldap-url", accepted_values) +def test_password_fields(_harness) -> None: + """Test password fields validation.""" + erroneous_passwords = [ + "onlyletters", # No numbers + "12345678", # No letters + "NoSpecialChar123", # No special characters + "Short1!", # Too short + ] + + valid_passwords = [ + "Valid1Pass!", + "AnotherValid2#Password", + "Password1$", + "P@ssw0rd1234", + ] + + check_invalid_values( + _harness, "ranger-admin-password", erroneous_passwords + ) + check_valid_values(_harness, "ranger-admin-password", valid_passwords) + + check_invalid_values( + _harness, "ranger-usersync-password", erroneous_passwords + ) + check_valid_values(_harness, "ranger-usersync-password", valid_passwords) + + def check_valid_values(_harness, field: str, accepted_values: list) -> None: """Check the correctness of the passed values for a field.