Skip to content

Commit

Permalink
Merge pull request #31 from canonical/usersync-password
Browse files Browse the repository at this point in the history
CSS-9793 Make ranger usersync password configurable
  • Loading branch information
AmberCharitos authored Jul 23, 2024
2 parents 9f79ff8 + 7bd6ada commit 1ba4200
Show file tree
Hide file tree
Showing 10 changed files with 144 additions and 11 deletions.
13 changes: 12 additions & 1 deletion config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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!
25 changes: 24 additions & 1 deletion lib/charms/data_platform_libs/v0/data_interfaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -331,7 +331,7 @@ def _on_topic_requested(self, event: TopicRequestedEvent):

# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
LIBPATCH = 37
LIBPATCH = 38

PYDEPS = ["ops>=2.0.0"]

Expand Down Expand Up @@ -2606,6 +2606,14 @@ def set_version(self, relation_id: int, version: str) -> None:
"""
self.update_relation_data(relation_id, {"version": version})

def set_subordinated(self, relation_id: int) -> None:
"""Raises the subordinated flag in the application relation databag.
Args:
relation_id: the identifier for a particular relation.
"""
self.update_relation_data(relation_id, {"subordinated": "true"})


class DatabaseProviderEventHandlers(EventHandlers):
"""Provider-side of the database relation handlers."""
Expand Down Expand Up @@ -2842,6 +2850,21 @@ def _on_relation_created_event(self, event: RelationCreatedEvent) -> None:

def _on_relation_changed_event(self, event: RelationChangedEvent) -> None:
"""Event emitted when the database relation has changed."""
is_subordinate = False
remote_unit_data = None
for key in event.relation.data.keys():
if isinstance(key, Unit) and not key.name.startswith(self.charm.app.name):
remote_unit_data = event.relation.data[key]
elif isinstance(key, Application) and key.name != self.charm.app.name:
is_subordinate = event.relation.data[key].get("subordinated") == "true"

if is_subordinate:
if not remote_unit_data:
return

if remote_unit_data.get("state") != "ready":
return

# Check which data has changed to emit customs events.
diff = self._diff(event)

Expand Down
40 changes: 40 additions & 0 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand All @@ -268,6 +270,29 @@ def _configure_ranger_usersync(self, container):
)
return USERSYNC_ENTRYPOINT, context

def _validate_password(self, password, config_key, state_key):
"""Validate that the admin and usersync passwords are not changed after deployment.
Args:
password: the deployment password.
config_key: the config key for the password.
state_key: the key the password is stored in state.
Raises:
ValueError: in case the password has been changed.
"""
if password is None:
if self.unit.is_leader():
setattr(self._state, state_key, self.config[config_key])
logger.info(self._state.ranger_admin_password)
elif password != self.config[config_key]:
message = (
f"value of '{config_key}' config cannot be changed after deployment. "
f"Value should be {password}"
)
logger.error(message)
raise ValueError(message)

def validate(self):
"""Validate that configuration and relations are valid and ready.
Expand All @@ -283,6 +308,21 @@ def validate(self):
if self.config["charm-function"].value == "usersync":
self.ldap.validate()

ranger_admin_password = self._state.ranger_admin_password
ranger_usersync_password = self._state.ranger_usersync_password

logger.info(ranger_admin_password)
self._validate_password(
ranger_admin_password,
"ranger-admin-password",
"ranger_admin_password",
)
self._validate_password(
ranger_usersync_password,
"ranger-usersync-password",
"ranger_usersync_password",
)

def update(self, event):
"""Update the Ranger server configuration and re-plan its execution.
Expand Down
27 changes: 26 additions & 1 deletion src/structured_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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) -> 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.")
8 changes: 4 additions & 4 deletions templates/admin-config.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion templates/ranger-usersync-config.jinja
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
1 change: 1 addition & 0 deletions tests/integration/test_usersync.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(".")
Expand Down
11 changes: 8 additions & 3 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
},
}
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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 = "rangerR0cks!" # nosec
got_admin_password = harness.get_container_pebble_plan(
"ranger"
).to_dict()["services"]["ranger"]["environment"]["RANGER_ADMIN_PWD"]
Expand All @@ -185,7 +187,10 @@ def test_config_changed(self):
# The ActiveStatus is set with replan message.
self.assertEqual(
harness.model.unit.status,
MaintenanceStatus("replanning application"),
BlockedStatus(
"value of 'ranger-admin-password' config cannot be changed after deployment. "
"Value should be rangerR0cks!"
),
)

def test_ingress(self):
Expand Down
27 changes: 27 additions & 0 deletions tests/unit/test_structured_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down

0 comments on commit 1ba4200

Please sign in to comment.