From 61df85f5c386c1123cfbc4d331fb1f578d0ff131 Mon Sep 17 00:00:00 2001 From: George Taylor Date: Wed, 22 Nov 2023 13:51:32 +0000 Subject: [PATCH] migration to python-ldap - correction on tree deletion --- cli/__init__.py | 6 +- cli/ldap/rbac.py | 527 ---------------------------- cli/{ldap => ldap_cmds}/__init__.py | 0 cli/ldap_cmds/rbac.py | 518 +++++++++++++++++++++++++++ cli/{ldap => ldap_cmds}/user.py | 48 +-- requirements-dev.txt | 2 +- requirements.txt | 12 +- 7 files changed, 546 insertions(+), 567 deletions(-) delete mode 100644 cli/ldap/rbac.py rename cli/{ldap => ldap_cmds}/__init__.py (100%) create mode 100644 cli/ldap_cmds/rbac.py rename cli/{ldap => ldap_cmds}/user.py (91%) diff --git a/cli/__init__.py b/cli/__init__.py index 4ea2b0b..f370ca6 100644 --- a/cli/__init__.py +++ b/cli/__init__.py @@ -1,6 +1,6 @@ import click -import cli.ldap.rbac -import cli.ldap.user +import cli.ldap_cmds.rbac +import cli.ldap_cmds.user from cli import ( logger, @@ -166,7 +166,7 @@ def update_user_roles( def rbac_uplift( rbac_repo_tag, ): - cli.ldap.rbac.main(rbac_repo_tag) + cli.ldap_cmds.rbac.main(rbac_repo_tag) @click.command() diff --git a/cli/ldap/rbac.py b/cli/ldap/rbac.py deleted file mode 100644 index c7b6204..0000000 --- a/cli/ldap/rbac.py +++ /dev/null @@ -1,527 +0,0 @@ -import ldap3.utils.hashed -from cli.ldap import ( - ldap_connect, -) -from cli import ( - env, -) -import cli.git as git -import glob -from cli.logger import ( - log, -) -from pathlib import ( - Path, -) -import cli.template -from ldif import ( - LDIFParser, -) - -# example for token auth -# def get_repo_with_token(repo_tag="master"): -# app_id = env.vars.get("GH_APP_ID") -# private_key = env.vars.get("GH_PRIVATE_KEY") -# installation_id = env.vars.get("GH_INSTALLATION_ID") -# token = git.get_access_token(app_id, private_key, installation_id) - - -ldap_config = { - "bind_user": "cn=root,dc=moj,dc=com", - "bind_user_cn": "root", - "base_root": "dc=moj,dc=com", - "base_root_dc": "moj", - "base_users": "ou=Users,dc=moj,dc=com", - "base_users_ou": "Users", - "base_service_users": "cn=EISUsers,ou=Users,dc=moj,dc=com", - "base_roles": "cn=ndRoleCatalogue,ou=Users,dc=moj,dc=com", - "base_role_groups": "cn=ndRoleGroups,ou=Users,dc=moj,dc=com", - "base_groups": "ou=groups,dc=moj,dc=com", - "base_groups_ou": "groups", -} - - -def get_repo( - repo_tag="master", -): - url = "https://github.com/ministryofjustice/hmpps-ndelius-rbac.git" - try: - repo = git.get_repo( - url, - dest_name="rbac", - branch_or_tag=repo_tag, - ) - return repo - except Exception as e: - log.exception(e) - return None - - -def prep_for_templating( - files, - strings=None, -): - rbac_substitutions = { - "bind_password_hash.stdout": "bind_password_hash", - r"ldap_config.base_users | regex_replace('^.+?=(.+?),.*$', '\\1')": "ldap_config.base_users_ou", - r"ldap_config.base_root | regex_replace('^.+?=(.+?),.*$', '\\1')": "ldap_config.base_root_dc", - r"ldap_config.base_groups | regex_replace('^.+?=(.+?),.*$', '\\1')": "ldap_config.base_groups_ou", - r"ldap_config.bind_user | regex_replace('^.+?=(.+?),.*$', '\\1')": "ldap_config.bind_user_cn", - "'/'+environment_name+'/'+project_name+'": "", - "/gdpr/api/": "'gdpr_api_", - "/pwm/pwm/config_password": "'pwm_config_password", - "/merge/api/client_secret": "'merge_api_client_secret", - "/weblogic/ndelius-domain/umt_client_secret": "'umt_client_secret", - "ssm_prefix + ": "", - "cn=Users,dc=pcms,dc=internal": "ou=Users,dc=moj,dc=com", - "ssm_prefix+": "", - } - - if strings is None: - strings = env.vars.get("RBAC_SUBSTITUTIONS") or rbac_substitutions - - for file_path in files: - file = Path(file_path) - log.info("Replacing strings in rbac files") - for ( - k, - v, - ) in strings.items(): - log.debug(f"replacing {k} with {v} in {file_path}") - file.write_text( - file.read_text().replace( - k, - v, - ), - ) - - -def template_rbac( - files, -): - hashed_pwd_admin_user = ldap3.utils.hashed.hashed( - ldap3.HASHED_SALTED_SHA, - env.secrets.get("LDAP_ADMIN_PASSWORD"), - ) - rendered_files = [] - - for file in files: - rendered_text = cli.template.render( - file, - ldap_config=env.vars.get("LDAP_CONFIG") or ldap_config, - bind_password_hash=hashed_pwd_admin_user, - secrets=env.secrets, - oasys_password=env.secrets.get("OASYS_PASSWORD"), - environment_name=env.vars.get("ENVIRONMENT_NAME"), - project_name=env.vars.get("PROJECT_NAME"), - ) - rendered_file = cli.template.save( - rendered_text, - file, - ) - rendered_files.append(rendered_file) - return rendered_files - - -def context_ldif( - rendered_files, -): - context_file = [file for file in rendered_files if "context" in Path(file).name] - for file in context_file: - parser = LDIFParser( - open( - file, - "rb", - ), - strict=False, - ) - for ( - dn, - record, - ) in parser.parse(): - log.info(f"got entry record: {dn}") - log.debug(record) - try: - ldap_connection = ldap_connect( - env.vars.get("LDAP_HOST"), - env.vars.get("LDAP_USER"), - env.secrets.get("LDAP_BIND_PASSWORD"), - ) - except Exception as e: - log.exception(f"Failed to connect to ldap") - raise e - - try: - ldap_connection.add( - dn, - attributes=record, - ) - log.debug(ldap_connection.result["result"]) - except Exception as e: - log.exception(f"Failed to add {dn}... {record}") - raise e - - if ldap_connection.result["result"] == 0: - log.info("Successfully added context") - elif ldap_connection.result["result"] == 68: - log.info(f"{dn} already exists") - else: - log.debug(ldap_connection.result) - log.debug(ldap_connection.response) - raise Exception(f"Failed to add {dn}... {record}") - - -def group_ldifs( - rendered_files, -): - # connect to ldap - try: - ldap_connection = ldap_connect( - env.vars.get("LDAP_HOST"), - env.vars.get("LDAP_USER"), - env.secrets.get("LDAP_BIND_PASSWORD"), - ) - except Exception as e: - log.exception(f"Failed to connect to ldap") - raise e - - group_files = [file for file in rendered_files if "groups" in Path(file).name] - # loop through the group files - for file in group_files: - # parse the ldif into dn and record - parser = LDIFParser( - open( - file, - "rb", - ), - strict=False, - ) - # loop through the records - for ( - dn, - record, - ) in parser.parse(): - log.debug(f"got entry record: {dn}") - log.debug(record) - # add the record to ldap - try: - ldap_connection.add( - dn, - attributes=record, - ) - except Exception as e: - log.exception(f"Failed to add {dn}... {record}") - raise e - - if record.get("description"): - log.info(f"Updating description for {record}") - try: - ldap_connection.modify( - dn, - { - "description": [ - ( - ldap3.MODIFY_REPLACE, - record["description"], - ) - ] - }, - ) - except Exception as e: - log.exception(f"Failed to add {dn}... {record}") - raise e - - if ldap_connection.result["result"] == 0: - log.info(f"Successfully added groups") - elif ldap_connection.result["result"] == 68: - log.info(f"{dn} already exists") - else: - log.debug(ldap_connection.result) - log.debug(ldap_connection.response) - raise Exception(f"Failed to add {dn}... {record}") - - -def policy_ldifs( - rendered_files, -): - # connect to ldap - try: - ldap_connection = ldap_connect( - env.vars.get("LDAP_HOST"), - env.vars.get("LDAP_USER"), - env.secrets.get("LDAP_BIND_PASSWORD"), - ) - except Exception as e: - log.exception(f"Failed to connect to ldap") - raise e - - policy_files = [file for file in rendered_files if "policy" in Path(file).name] - - # first, delete the policies - ldap_config_dict = env.vars.get("LDAP_CONFIG") or ldap_config - ldap_connection.delete("ou=Policies," + ldap_config_dict.get("base_root")) - - # loop through the policy files - for file in policy_files: - # parse the ldif into dn and record - parser = LDIFParser( - open( - file, - "rb", - ), - strict=False, - ) - # loop through the records - for ( - dn, - record, - ) in parser.parse(): - log.info(f"Got entry record: {dn}") - # add the record to ldap - try: - ldap_connection.add( - dn, - attributes=record, - ) - except Exception as e: - log.exception(f"Failed to add {dn}... {record}") - raise e - - if ldap_connection.result["result"] == 0: - log.info(f"Successfully added policies") - elif ldap_connection.result["result"] == 68: - log.info(f"{dn} already exists") - else: - log.debug(ldap_connection.result) - log.debug(ldap_connection.response) - raise Exception(f"Failed to add {dn}... {record}") - - -def role_ldifs( - rendered_files, -): - # connect to ldap - try: - ldap_connection = ldap_connect( - env.vars.get("LDAP_HOST"), - env.vars.get("LDAP_USER"), - env.secrets.get("LDAP_BIND_PASSWORD"), - ) - except Exception as e: - log.exception(f"Failed to connect to ldap") - raise e - role_files = [file for file in rendered_files if "nd_role" in Path(file).name] - - # first, delete the roles - ldap_config_dict = env.vars.get("LDAP_CONFIG") or ldap_config - ldap_connection.delete("cn=ndRoleCatalogue," + ldap_config_dict.get("base_users")) - ldap_connection.delete("cn=ndRoleGroups," + ldap_config_dict.get("base_users")) - - # ensure boolean values are Uppercase.. this comes from the ansible yml - # (not yet implemented, probably not needed) - - # loop through the role files - for file in role_files: - # parse the ldif into dn and record - parser = LDIFParser( - open( - file, - "rb", - ), - strict=False, - ) - # loop through the records - for ( - dn, - record, - ) in parser.parse(): - log.info(f"Got entry record: {dn}") - # add the record to ldap - try: - ldap_connection.add( - dn, - attributes=record, - ) - except Exception as e: - log.exception(f"Failed to add {dn}... {record}") - raise e - - if ldap_connection.result["result"] == 0: - log.info(f"Successfully added roles") - elif ldap_connection.result["result"] == 68: - log.info(f"{dn} already exists") - else: - log.debug(ldap_connection.result) - log.debug(ldap_connection.response) - raise Exception(f"Failed to add {dn}... {record}") - - -# not complete!! -# see https://github.com/ministryofjustice/hmpps-delius-pipelines/blob/master/components/delius-core/playbooks/rbac/import_schemas.yml -def schema_ldifs( - rendered_files, -): - # connect to ldap - try: - ldap_connection = ldap_connect( - env.vars.get("LDAP_HOST"), - env.vars.get("LDAP_USER"), - env.secrets.get("LDAP_BIND_PASSWORD"), - ) - except Exception as e: - log.exception(f"Failed to connect to ldap") - raise e - - schema_files = [file for file in rendered_files if "delius.ldif" or "pwm.ldif" in Path(file).name] - - # loop through the schema files - for file in schema_files: - # parse the ldif into dn and record - parser = LDIFParser( - open( - file, - "rb", - ), - strict=False, - ) - # loop through the records - for ( - dn, - record, - ) in parser.parse(): - log.info(f"Got entry record: {dn}") - # add the record to ldap - try: - ldap_connection.add( - dn, - attributes=record, - ) - except Exception as e: - log.exception(f"Failed to add {dn}... {record}") - raise e - - if ldap_connection.result["result"] == 0: - log.info(f"Successfully added schemas") - elif ldap_connection.result["result"] == 68: - log.info(f"{dn} already exists") - else: - log.debug(ldap_connection.result) - log.debug(ldap_connection.response) - raise Exception(f"Failed to add {dn}... {record}") - - -def user_ldifs( - rendered_files, -): - # connect to ldap - try: - ldap_connection = ldap_connect( - env.vars.get("LDAP_HOST"), - env.vars.get("LDAP_USER"), - env.secrets.get("LDAP_BIND_PASSWORD"), - ) - except Exception as e: - log.exception(f"Failed to connect to ldap") - raise e - - user_files = [file for file in rendered_files if "-users" in Path(file).name] - - # first, delete the users - for file in user_files: - # parse the ldif into dn and record - parser = LDIFParser( - open( - file, - "rb", - ), - strict=False, - ) - # loop through the records - for dn, record in parser.parse(): - log.info(f"Got entry record: {dn}") - - # for each user find child entries - try: - ldap_connection.search( - dn, - "(objectclass=*)", - search_scope=ldap3.SUBTREE, - ) - except Exception as e: - log.exception(f"Failed to search {dn}") - raise e - - # delete child entries - try: - for entry in ldap_connection.entries: - log.debug(entry.entry_dn) - ldap_connection.delete(entry.entry_dn) - except Exception as e: - log.exception(f"Failed to delete {entry.entry_dn}") - raise e - - try: - ldap_connection.delete(dn) - except Exception as e: - log.exception(f"Failed to delete {dn}") - raise e - - # loop through the user files - for file in user_files: - # parse the ldif into dn and record - parser = LDIFParser( - open( - file, - "rb", - ), - strict=False, - ) - # loop through the records - for ( - dn, - record, - ) in parser.parse(): - log.info(f"Got entry record: {dn}") - - # add the record to ldap - try: - ldap_connection.add( - dn, - attributes=record, - ) - except Exception as e: - log.exception(f"Failed to add {dn}... {record}") - raise e - - if ldap_connection.result["result"] == 0: - log.info(f"Successfully added users") - elif ldap_connection.result["result"] == 68: - log.info(f"{dn} already exists") - else: - log.debug(ldap_connection.result) - log.debug(ldap_connection.response) - raise Exception(f"Failed to add {dn}... {record}") - - -def main( - rbac_repo_tag, - clone_path="./rbac", -): - get_repo(rbac_repo_tag) - files = [ - file - for file in glob.glob( - f"{clone_path}/**/*", - recursive=True, - ) - if Path(file).is_file() and Path(file).name.endswith(".ldif") or Path(file).name.endswith(".j2") - ] - - prep_for_templating(files) - rendered_files = template_rbac(files) - context_ldif(rendered_files) - policy_ldifs(rendered_files) - # schema_ldifs(files) probably not needed, but need to check! - role_ldifs(rendered_files) - group_ldifs(rendered_files) - user_ldifs(rendered_files) diff --git a/cli/ldap/__init__.py b/cli/ldap_cmds/__init__.py similarity index 100% rename from cli/ldap/__init__.py rename to cli/ldap_cmds/__init__.py diff --git a/cli/ldap_cmds/rbac.py b/cli/ldap_cmds/rbac.py new file mode 100644 index 0000000..33a30b4 --- /dev/null +++ b/cli/ldap_cmds/rbac.py @@ -0,0 +1,518 @@ +from pprint import pprint + +import ldap +import ldap3.utils.hashed +import ldif +import ldap.modlist as modlist + +from cli.ldap_cmds import ( + ldap_connect, +) +from cli import ( + env, +) +import cli.git as git +import glob +from cli.logger import ( + log, +) +from pathlib import ( + Path, +) +import cli.template + +# example for token auth +# def get_repo_with_token(repo_tag="master"): +# app_id = env.vars.get("GH_APP_ID") +# private_key = env.vars.get("GH_PRIVATE_KEY") +# installation_id = env.vars.get("GH_INSTALLATION_ID") +# token = git.get_access_token(app_id, private_key, installation_id) + + +ldap_config = { + "bind_user": "cn=root,dc=moj,dc=com", + "bind_user_cn": "root", + "base_root": "dc=moj,dc=com", + "base_root_dc": "moj", + "base_users": "ou=Users,dc=moj,dc=com", + "base_users_ou": "Users", + "base_service_users": "cn=EISUsers,ou=Users,dc=moj,dc=com", + "base_roles": "cn=ndRoleCatalogue,ou=Users,dc=moj,dc=com", + "base_role_groups": "cn=ndRoleGroups,ou=Users,dc=moj,dc=com", + "base_groups": "ou=groups,dc=moj,dc=com", + "base_groups_ou": "groups", +} + + +def get_repo( + repo_tag="master", +): + url = "https://github.com/ministryofjustice/hmpps-ndelius-rbac.git" + try: + repo = git.get_repo( + url, + dest_name="rbac", + branch_or_tag=repo_tag, + ) + return repo + except Exception as e: + log.exception(e) + return None + + +def prep_for_templating( + files, + strings=None, +): + rbac_substitutions = { + "bind_password_hash.stdout": "bind_password_hash", + r"ldap_config.base_users | regex_replace('^.+?=(.+?),.*$', '\\1')": "ldap_config.base_users_ou", + r"ldap_config.base_root | regex_replace('^.+?=(.+?),.*$', '\\1')": "ldap_config.base_root_dc", + r"ldap_config.base_groups | regex_replace('^.+?=(.+?),.*$', '\\1')": "ldap_config.base_groups_ou", + r"ldap_config.bind_user | regex_replace('^.+?=(.+?),.*$', '\\1')": "ldap_config.bind_user_cn", + "'/'+environment_name+'/'+project_name+'": "", + "/gdpr/api/": "'gdpr_api_", + "/pwm/pwm/config_password": "'pwm_config_password", + "/merge/api/client_secret": "'merge_api_client_secret", + "/weblogic/ndelius-domain/umt_client_secret": "'umt_client_secret", + "ssm_prefix + ": "", + "cn=Users,dc=pcms,dc=internal": "ou=Users,dc=moj,dc=com", + "ssm_prefix+": "", + } + + if strings is None: + strings = env.vars.get("RBAC_SUBSTITUTIONS") or rbac_substitutions + + for file_path in files: + file = Path(file_path) + log.info("Replacing strings in rbac files") + for ( + k, + v, + ) in strings.items(): + log.debug(f"replacing {k} with {v} in {file_path}") + file.write_text( + file.read_text().replace( + k, + v, + ), + ) + + +def template_rbac( + files, +): + hashed_pwd_admin_user = ldap3.utils.hashed.hashed( + ldap3.HASHED_SALTED_SHA, + env.secrets.get("LDAP_ADMIN_PASSWORD"), + ) + rendered_files = [] + + for file in files: + rendered_text = cli.template.render( + file, + ldap_config=env.vars.get("LDAP_CONFIG") or ldap_config, + bind_password_hash=hashed_pwd_admin_user, + secrets=env.secrets, + oasys_password=env.secrets.get("OASYS_PASSWORD"), + environment_name=env.vars.get("ENVIRONMENT_NAME"), + project_name=env.vars.get("PROJECT_NAME"), + ) + rendered_file = cli.template.save( + rendered_text, + file, + ) + rendered_files.append(rendered_file) + return rendered_files + + +def context_ldif( + rendered_files, +): + context_file = [file for file in rendered_files if "context" in Path(file).name] + + # connect to ldap + try: + connection = ldap.initialize("ldap://" + env.vars.get("LDAP_HOST")) + connection.simple_bind_s(env.vars.get("LDAP_USER"), env.secrets.get("LDAP_BIND_PASSWORD")) + except Exception as e: + log.exception(f"Failed to connect to ldap") + raise e + + for file in context_file: + # parse the ldif into dn and record + + records = ldif.LDIFRecordList(open(file, "rb")) + records.parse() + + pprint(records.all_records) + # loop through the records + for entry in records.all_records: + dn = entry[0] + attributes = entry[1] + log.info(f"got entry record: {dn}") + log.debug(attributes) + + try: + connection.add_s( + dn, + modlist.addModlist(attributes), + ) + except ldap.ALREADY_EXISTS as already_exists_e: + log.info(f"{dn} already exists") + log.debug(already_exists_e) + except Exception as e: + log.exception(f"Failed to add {dn}... {attributes}") + raise e + + +def group_ldifs( + rendered_files, +): + # connect to ldap + try: + connection = ldap.initialize("ldap://" + env.vars.get("LDAP_HOST")) + connection.simple_bind_s(env.vars.get("LDAP_USER"), env.secrets.get("LDAP_BIND_PASSWORD")) + except Exception as e: + log.exception(f"Failed to connect to ldap") + raise e + + group_files = [file for file in rendered_files if "-groups" in Path(file).name] + # loop through the group files + for file in group_files: + # parse the ldif into dn and record + + records = ldif.LDIFRecordList(open(file, "rb")) + records.parse() + + pprint(records.all_records) + # loop through the records + for entry in records.all_records: + dn = entry[0] + attributes = entry[1] + log.debug(f"got entry record: {dn}") + log.debug(attributes) + # add the record to ldap + try: + connection.add_s( + dn, + modlist.addModlist(attributes), + ) + except ldap.ALREADY_EXISTS as already_exists_e: + log.info(f"{dn} already exists") + log.debug(already_exists_e) + except Exception as e: + log.exception(f"Failed to add {dn}... {attributes}") + raise e + + if attributes.get("description"): + log.info(f"Updating description for {dn}") + try: + connection.modify(dn, [(ldap.MOD_REPLACE, "description", attributes["description"])]) + except ldap.ALREADY_EXISTS as already_exists_e: + log.info(f"{dn} already exists") + log.debug(already_exists_e) + except Exception as e: + log.exception(f"Failed to add {dn}... {attributes}") + raise e + + +def policy_ldifs( + rendered_files, +): + # connect to ldap + try: + connection = ldap.initialize("ldap://" + env.vars.get("LDAP_HOST")) + connection.simple_bind_s(env.vars.get("LDAP_USER"), env.secrets.get("LDAP_BIND_PASSWORD")) + except Exception as e: + log.exception(f"Failed to connect to ldap") + raise e + + policy_files = [file for file in rendered_files if "policy" in Path(file).name] + + # first, delete the policies + ldap_config_dict = env.vars.get("LDAP_CONFIG") or ldap_config + policy_tree = "ou=Policies," + ldap_config_dict.get("base_root") + + tree = connection.search_s( + policy_tree, + ldap.SCOPE_SUBTREE, + "(objectClass=*)", + ) + tree.reverse() + + for entry in tree: + try: + log.debug(entry[0]) + connection.delete_ext_s(entry[0], serverctrls=[ldap.controls.simple.ManageDSAITControl()]) + print(f"Deleted {entry[0]}") + except ldap.NO_SUCH_OBJECT as no_such_object_e: + log.info("No such object found, 32") + log.debug(no_such_object_e) + + # loop through the policy files + for file in policy_files: + # parse the ldif into dn and record + + records = ldif.LDIFRecordList(open(file, "rb")) + records.parse() + + pprint(records.all_records) + # loop through the records + for entry in records.all_records: + dn = entry[0] + attributes = entry[1] + log.info(f"Got entry record: {dn}") + # add the record to ldap + try: + connection.add_s( + dn, + modlist.addModlist(attributes), + ) + except ldap.ALREADY_EXISTS as already_exists_e: + log.info(f"{dn} already exists") + log.debug(already_exists_e) + except Exception as e: + log.exception(f"Failed to add {dn}... {attributes}") + raise e + + +def role_ldifs( + rendered_files, +): + # connect to ldap + try: + connection = ldap.initialize("ldap://" + env.vars.get("LDAP_HOST")) + connection.simple_bind_s(env.vars.get("LDAP_USER"), env.secrets.get("LDAP_BIND_PASSWORD")) + except Exception as e: + log.exception(f"Failed to connect to ldap") + raise e + + role_files = [file for file in rendered_files if "nd_role" in Path(file).name] + + # first, delete the roles + ldap_config_dict = env.vars.get("LDAP_CONFIG") or ldap_config + + role_trees = [ + "cn=ndRoleCatalogue," + ldap_config_dict.get("base_users"), + "cn=ndRoleGroups," + ldap_config_dict.get("base_users"), + ] + for role_tree in role_trees: + tree = connection.search_s( + role_tree, + ldap.SCOPE_SUBTREE, + "(objectClass=*)", + ) + tree.reverse() + + for entry in tree: + try: + log.debug(entry[0]) + connection.delete_ext_s(entry[0], serverctrls=[ldap.controls.simple.ManageDSAITControl()]) + print(f"Deleted {entry[0]}") + except ldap.NO_SUCH_OBJECT as no_such_object_e: + log.info("No such object found, 32") + log.debug(no_such_object_e) + + # ensure boolean values are Uppercase.. this comes from the ansible yml + # (not yet implemented, probably not needed) + + # loop through the role files + for file in role_files: + # parse the ldif into dn and record + + records = ldif.LDIFRecordList(open(file, "rb")) + records.parse() + + pprint(records.all_records) + # loop through the records + for entry in records.all_records: + dn = entry[0] + attributes = entry[1] + log.info(f"Got entry record: {dn}") + # add the record to ldap + try: + connection.add_s( + dn, + modlist.addModlist(attributes), + ) + except ldap.ALREADY_EXISTS as already_exists_e: + log.info(f"{dn} already exists") + log.debug(already_exists_e) + except Exception as e: + log.exception(f"Failed to add {dn}... {attributes}") + raise e + + +# not complete!! +# see https://github.com/ministryofjustice/hmpps-delius-pipelines/blob/master/components/delius-core/playbooks/rbac/import_schemas.yml +def schema_ldifs( + rendered_files, +): + # connect to ldap + try: + connection = ldap.initialize("ldap://" + env.vars.get("LDAP_HOST")) + connection.simple_bind_s(env.vars.get("LDAP_USER"), env.secrets.get("LDAP_BIND_PASSWORD")) + except Exception as e: + log.exception(f"Failed to connect to ldap") + raise e + + schema_files = [file for file in rendered_files if "delius.ldif" or "pwm.ldif" in Path(file).name] + + # loop through the schema files + for file in schema_files: + # parse the ldif into dn and record + records = ldif.LDIFRecordList(open(file, "rb")) + records.parse() + # loop through the records + for entry in records.all_records: + log.info(f"Got entry record: {dn}") + # add the record to ldap + try: + dn = entry[0] + attributes = entry[1] + print(f" {entry[0]}") + connection.add_s(dn, modlist.addModlist(attributes)) + except ldap.ALREADY_EXISTS as already_exists_e: + log.info(f"{dn} already exists") + log.debug(already_exists_e) + except Exception as e: + log.exception(f"Failed to add {dn}... {attributes}") + raise e + + +def user_ldifs( + rendered_files, +): + # connect to ldap + try: + connection = ldap.initialize("ldap://" + env.vars.get("LDAP_HOST")) + connection.simple_bind_s(env.vars.get("LDAP_USER"), env.secrets.get("LDAP_BIND_PASSWORD")) + except Exception as e: + log.exception(f"Failed to connect to ldap") + raise e + + except Exception as e: + log.exception(f"Failed to connect to ldap") + raise e + + user_files = [file for file in rendered_files if "-users.ldif" in Path(file).name] + + # first, delete the users + for file in user_files: + records = ldif.LDIFRecordList(open(file, "rb")) + records.parse() + + for record in records.all_records: + dn = record[0] + log.info(f"Got entry record: {dn}") + try: + # search for dn children + tree = connection.search_s( + dn, + ldap.SCOPE_SUBTREE, + "(objectClass=*)", + ) + tree.reverse() + print(tree) + for entry in tree: + try: + log.debug(entry[0]) + connection.delete_ext_s(entry[0], serverctrls=[ldap.controls.simple.ManageDSAITControl()]) + print(f"Deleted {entry[0]}") + except ldap.NO_SUCH_OBJECT as no_such_object_e: + log.info("No such object found, 32") + log.debug(no_such_object_e) + # connection.delete_ext_s(dn, serverctrls=[ldap.controls.simple.ManageDSAITControl()]) + # print(f"Deleted {dn}") + except ldap.NO_SUCH_OBJECT as no_such_object_e: + log.info("No such object found, 32") + log.debug(no_such_object_e) + except Exception as e: + log.exception(e) + raise e + + for file in user_files: + records = ldif.LDIFRecordList(open(file, "rb")) + records.parse() + + pprint(records.all_records) + # loop through the records + for entry in records.all_records: + dn = entry[0] + attributes = entry[1] + print(f" {entry[0]}") + connection.add_s(dn, modlist.addModlist(attributes)) + + # connect to ldap + # try: + # ldap_connection_addition = ldap_connect( + # env.vars.get("LDAP_HOST"), + # env.vars.get("LDAP_USER"), + # env.secrets.get("LDAP_BIND_PASSWORD"), + # ) + # except Exception as e: + # log.exception(f"Failed to connect to ldap") + # raise e + + # loop through the user files + # for file in user_files: + # # parse the ldif into dn and record + # parser = LDIFParser( + # open( + # file, + # "rb", + # ), + # strict=False, + # ) + # # loop through the records + # for ( + # dn, + # record, + # ) in parser.parse(): + # log.info(f"Got entry record: {dn}") + # + # # add the record to ldap + # try: + # print(dn) + # print(record) + # ldap_connection_addition.add( + # dn, + # record, + # ) + # except Exception as e: + # log.exception(f"Failed to add {dn}... {record}") + # raise e + # + # if ldap_connection_addition.result["result"] == 0: + # log.info(f"Successfully added users") + # elif ldap_connection_addition.result["result"] == 68: + # log.info(f"{dn} already exists") + # else: + # log.debug(ldap_connection_addition.result) + # log.debug(ldap_connection_addition.response) + # raise Exception(f"Failed to add {dn}... {record}") + + +def main( + rbac_repo_tag, + clone_path="./rbac", +): + get_repo(rbac_repo_tag) + files = [ + file + for file in glob.glob( + f"{clone_path}/**/*", + recursive=True, + ) + if Path(file).is_file() and Path(file).name.endswith(".ldif") or Path(file).name.endswith(".j2") + ] + + prep_for_templating(files) + rendered_files = template_rbac(files) + context_ldif(rendered_files) + policy_ldifs(rendered_files) + # schema_ldifs(files) probably not needed, but need to check! + role_ldifs(rendered_files) + group_ldifs(rendered_files) + user_ldifs(rendered_files) diff --git a/cli/ldap/user.py b/cli/ldap_cmds/user.py similarity index 91% rename from cli/ldap/user.py rename to cli/ldap_cmds/user.py index 1aa4efa..410a801 100644 --- a/cli/ldap/user.py +++ b/cli/ldap_cmds/user.py @@ -1,6 +1,6 @@ import oracledb -import cli.ldap +import cli.ldap_cmds from cli.logger import ( log, @@ -9,7 +9,7 @@ env, ) -from cli.ldap import ( +from cli.ldap_cmds import ( ldap_connect, ) from ldap3 import ( @@ -45,7 +45,9 @@ def change_home_areas( env.secrets.get("LDAP_BIND_PASSWORD"), ) - search_filter = f"(&(objectclass={object_class})(userHomeArea={old_home_area})(!(cn={old_home_area}))(!(endDate=*)))" + search_filter = ( + f"(&(objectclass={object_class})(userHomeArea={old_home_area})(!(cn={old_home_area}))(!(endDate=*)))" + ) ldap_connection.search( ",".join( [ @@ -77,9 +79,7 @@ def change_home_areas( if ldap_connection.result["result"] == 0: log.info(f"Successfully updated {attribute} for {dn}") else: - log.error( - f"Failed to update {attribute} for {dn}: {ldap_connection.result}" - ) + log.error(f"Failed to update {attribute} for {dn}: {ldap_connection.result}") ######################################### @@ -95,10 +95,7 @@ def parse_user_role_list( # and the roles are separated by a semi-colon: # username1,role1;role2;role3|username2,role1;role2 - return { - user.split(",")[0]: user.split(",")[1].split(";") - for user in user_role_list.split("|") - } + return {user.split(",")[0]: user.split(",")[1].split(";") for user in user_role_list.split("|")} def add_roles_to_user( @@ -208,13 +205,7 @@ def update_roles( log.exception("Failed to search for users") raise e - users_found = sorted( - [ - entry.cn.value - for entry in ldap_connection_user_filter.entries - if entry.cn.value - ] - ) + users_found = sorted([entry.cn.value for entry in ldap_connection_user_filter.entries if entry.cn.value]) log.debug("users found from user filter") log.debug(users_found) ldap_connection_user_filter.unbind() @@ -224,7 +215,9 @@ def update_roles( # create role filter if len(roles_filter_list) > 0: - full_role_filter = f"(&(objectclass=NDRoleAssociation)(|{''.join(['(cn=' + role + ')' for role in roles_filter_list])}))" + full_role_filter = ( + f"(&(objectclass=NDRoleAssociation)(|{''.join(['(cn=' + role + ')' for role in roles_filter_list])}))" + ) else: full_role_filter = "(&(objectclass=NDRoleAssociation)(cn=*))" @@ -257,12 +250,7 @@ def update_roles( raise e roles_found = sorted( - set( - { - entry.entry_dn.split(",")[1].split("=")[1] - for entry in ldap_connection_role_filter.entries - } - ) + set({entry.entry_dn.split(",")[1].split("=")[1] for entry in ldap_connection_role_filter.entries}) ) log.debug("users found from roles filter: ") log.debug(roles_found) @@ -321,9 +309,7 @@ def update_roles( log.e(f"Failed to add role '{item[1]}' to user '{item[0]}'") log.debug(ldap_connection_action.result) elif remove: - ldap_connection_action.delete( - f"cn={item[1]},cn={item[0]},{user_ou},{root_dn}" - ) + ldap_connection_action.delete(f"cn={item[1]},cn={item[0]},{user_ou},{root_dn}") if ldap_connection_action.result["result"] == 0: log.info(f"Successfully removed role '{item[1]}' from user '{item[0]}'") elif ldap_connection_action.result["result"] == 32: @@ -402,9 +388,7 @@ def deactivate_crc_users( env.secrets.get("LDAP_BIND_PASSWORD"), ) - user_filter = ( - "(userSector=private)(!(userSector=public))(!(endDate=*))(objectclass=NDUser)" - ) + user_filter = "(userSector=private)(!(userSector=public))(!(endDate=*))(objectclass=NDUser)" home_areas = [ [ @@ -479,7 +463,9 @@ def deactivate_crc_users( connection = cli.database.connection() for user_dn in all_users: try: - update_sql = f"UPDATE USER_ SET END_DATE=TRUNC(CURRENT_DATE) WHERE UPPER(DISTINGUISHED_NAME)=UPPER(:user_dn)" + update_sql = ( + f"UPDATE USER_ SET END_DATE=TRUNC(CURRENT_DATE) WHERE UPPER(DISTINGUISHED_NAME)=UPPER(:user_dn)" + ) update_cursor = connection.cursor() update_cursor.execute( update_sql, diff --git a/requirements-dev.txt b/requirements-dev.txt index ae204bd..77bfeb0 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,8 +6,8 @@ PyGithub GitPython pyjwt python-dotenv +python-ldap Jinja2 -git+https://github.com/abilian/ldif.git@4.2.0 pre-commit black flake8 diff --git a/requirements.txt b/requirements.txt index 79a4e4b..f2550d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,10 +1,12 @@ click==8.1.6 -ldap3 +ldap3~=2.9.1 oracledb==1.4 ansible-runner PyGithub GitPython==3.1.37 -pyjwt -python-dotenv -Jinja2 -git+https://github.com/abilian/ldif.git@4.2.0 \ No newline at end of file +pyjwt~=2.8.0 +python-dotenv~=1.0.0 +Jinja2~=3.1.2 +python-ldap +requests~=2.31.0 +setuptools~=68.2.2 \ No newline at end of file