diff --git a/changelog.md b/changelog.md index b8b829e2..e0531215 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,13 @@ ## Unreleased +## 2024.12.6.0 + +- deprecated: set personal certificate as default +- deprecated: `base/audit_configuration.py`. Use `base/audit/configuration.py` instead +- feature: base/audit/configuration.py added +- feature: base/audit/components.py added + ## 2024.10.11.0 - fix: corrections in test script @@ -85,7 +92,7 @@ ## 2023.4.25.0 - fix: add id parameter to ibmsecurity/isam/aac/fido2/relying_parties.py (#377) -- fix: add __init__.py in ibmsecurity/isvg sub folders (#380) +- fix: add `__init__.py` in ibmsecurity/isvg sub folders (#380) ### Build & Deploy diff --git a/ibmsecurity/isam/base/audit/__init__.py b/ibmsecurity/isam/base/audit/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/ibmsecurity/isam/base/audit/components.py b/ibmsecurity/isam/base/audit/components.py new file mode 100644 index 00000000..e9383530 --- /dev/null +++ b/ibmsecurity/isam/base/audit/components.py @@ -0,0 +1,137 @@ +import logging + +from ibmsecurity.utilities import tools + +try: + basestring +except NameError: + basestring = (str, bytes) + +logger = logging.getLogger(__name__) + +# URI for this module +uri = "/iam/access/v8/audit/components" +requires_modules = ["mga", "federation"] +requires_version = None + + +def get_all(isamAppliance, check_mode=False, force=False): + """ + Retrieve audit configuration + """ + return isamAppliance.invoke_get("Retrieve all audit component configuration", uri, requires_modules=requires_modules, + requires_version=requires_version) + + +def search(isamAppliance, component_name: str, check_mode=False, force=False): + """ + Get the id for the component by (group) name + """ + ret_obj = None + + ret_obj = get_all(isamAppliance) + if ret_obj.get("data", None): + for obj in ret_obj.get("data"): + if obj['group'] == component_name: + logger.info(f"Found name {component_name} id: {obj['id']}") + return obj['id'] + return None + else: + return None + + +def get(isamAppliance, group_name: str = None, component_id: str = None, type_id: str = None, check_mode=False, force=False): + """ + Retrieve specific audit configuration component group by component_id, by type or by group name + """ + requires_version = None + warnings = [] + if component_id is None and type_id is None and group_name is None: + warnings = ['No group_name, component_id nor type_id passed'] + return isamAppliance.create_return_object(warnings) + elif group_name: + # translate name to component_id + component_id = search(isamAppliance, component_name=group_name) + if component_id: + uri_part = component_id + else: + warnings = [f"Cannot find group by name of: {group_name}"] + return isamAppliance.create_return_object(warnings) + elif component_id: + # Ignore type_id in this case + uri_part = component_id + else: + # valid values are runtime or management + if type_id in ['runtime', 'management']: + requires_version = "10.0.7.0" + uri_part = type_id + else: + warnings = [f"Invalid type_id passed to function: {type_id}"] + return isamAppliance.create_return_object(warnings) + return isamAppliance.invoke_get("Retrieve audit configuration component ", f"{uri}/{uri_part}", requires_modules=requires_modules, + requires_version=requires_version, warnings=warnings) + + +def set(isamAppliance, component_id: str, enabled=True, check_mode=False, force=False): + """ + Update Audit Configuration Component by id + This simply enables or disables the group. + TODO: Add set by type and set_all + """ + if isinstance(enabled, str): + if enabled.upper() in ['TRUE', 'YES']: + enabled = True + else: + enabled = False + update_required = _check(isamAppliance, component_id, enabled) + if enabled: + json_data = { + 'enabled': True + } + else: + json_data = { + 'enabled': False + } + if force or update_required: + if check_mode: + return isamAppliance.create_return_object(changed=True) + else: + return isamAppliance.invoke_put( + "Update Audit Configuration Component", + f"{uri}/{component_id}", + json_data, + requires_modules=requires_modules, + requires_version=requires_version) + + +def _check(isamAppliance, component_id: str, enabled: bool): + """ + Check and return True if update needed + """ + update_required = False + + ret_obj = get(isamAppliance, component_id=component_id) + cmp_cfg = ret_obj.get("data", None) + logger.debug(f"\n\n{cmp_cfg}\n\n") + if cmp_cfg is not None and str(cmp_cfg.get('enabled', "frottekop")) != str(enabled): + update_required = True + logger.debug(f"\n\nAudit Configuration Component requires an update {cmp_cfg.get('enabled')} <> {enabled}") + else: + logger.warning("Audit Configuration Component does not need an update or does not exist.") + + return update_required + + +def compare(isamAppliance1, isamAppliance2): + """ + Compare Audit Configuration Components between two appliances + """ + ret_obj1 = get_all(isamAppliance1) + ret_obj2 = get_all(isamAppliance2) + + for obj in ret_obj1['data']: + del obj['id'] + for obj in ret_obj2['data']: + del obj['id'] + + return tools.json_compare(ret_obj1, ret_obj2, deleted_keys=['id']) diff --git a/ibmsecurity/isam/base/audit/configuration.py b/ibmsecurity/isam/base/audit/configuration.py new file mode 100644 index 00000000..d8d53081 --- /dev/null +++ b/ibmsecurity/isam/base/audit/configuration.py @@ -0,0 +1,242 @@ +import logging +from ibmsecurity.utilities import tools + +try: + basestring +except NameError: + basestring = (str, bytes) + +logger = logging.getLogger(__name__) + +# URI for this module +uri = "/iam/access/v8/audit" +requires_modules = ["mga", "federation"] +requires_version = None + + +def get(isamAppliance, check_mode=False, force=False): + """ + Retrieve audit configuration + """ + return isamAppliance.invoke_get("Retrieve audit configuration", uri, requires_modules=requires_modules, + requires_version=requires_version) + + +def set(isamAppliance, id, config, enabled=True, type='Syslog', verbose=True, check_mode=False, force=False, use_json=False, components=None): + """ + Update Audit Configuration + + Sample data for Audit Configuration: + In JSON Format: + { u'config': [ { u'datatype': u'String', + u'key': u'ISAM.Audit.syslogclient.SSL_TRUST_STORE', + u'sensitive': False, + u'validValues': [], + u'value': u''}, + { u'datatype': u'String', + u'key': u'ISAM.Audit.syslogclient.CLIENT_AUTH_KEY', + u'sensitive': False, + u'validValues': [], + u'value': u''}, + { u'datatype': u'Boolean', + u'key': u'ISAM.Audit.syslogclient.FAILOVER_TO_DISK', + u'sensitive': False, + u'validValues': [], + u'value': u'false'}, + { u'datatype': u'Integer', + u'key': u'ISAM.Audit.syslogclient.NUM_RETRY', + u'sensitive': False, + u'validValues': [], + u'value': u'2'}, + { u'datatype': u'Integer', + u'key': u'ISAM.Audit.syslogclient.NUM_SENDER_THREADS', + u'sensitive': False, + u'validValues': [], + u'value': u'1'}, + { u'datatype': u'Boolean', + u'key': u'ISAM.Audit.syslogclient.CLIENT_CERT_AUTH_REQUIRED', + u'sensitive': False, + u'validValues': [], + u'value': u'false'}, + { u'datatype': u'Integer', + u'key': u'ISAM.Audit.syslogclient.SERVER_PORT', + u'sensitive': False, + u'validValues': [], + u'value': u'514'}, + { u'datatype': u'Hostname', + u'key': u'ISAM.Audit.syslogclient.SERVER_HOST', + u'sensitive': False, + u'validValues': [], + u'value': u'127.0.0.1'}, + { u'datatype': u'String', + u'key': u'ISAM.Audit.syslogclient.TRANSPORT', + u'sensitive': False, + u'validValues': [], + u'value': u'TRANSPORT_UDP'}, + { u'datatype': u'Integer', + u'key': u'ISAM.Audit.syslogclient.QUEUE_FULL_TIMEOUT', + u'sensitive': False, + u'validValues': [], + u'value': u'-1'}, + { u'datatype': u'Integer', + u'key': u'ISAM.Audit.syslogclient.MAX_QUEUE_SIZE', + u'sensitive': False, + u'validValues': [], + u'value': u'1000'}], + u'enabled': False, + u'id': u'1', + u'type': u'Syslog', + u'verbose': False} + + In YAML Format: + config: + - datatype: String + key: ISAM.Audit.syslogclient.SSL_TRUST_STORE + sensitive: false + validValues: [] + value: '' + - datatype: String + key: ISAM.Audit.syslogclient.CLIENT_AUTH_KEY + sensitive: false + validValues: [] + value: '' + - datatype: Boolean + key: ISAM.Audit.syslogclient.FAILOVER_TO_DISK + sensitive: false + validValues: [] + value: 'false' + - datatype: Integer + key: ISAM.Audit.syslogclient.NUM_RETRY + sensitive: false + validValues: [] + value: '2' + - datatype: Integer + key: ISAM.Audit.syslogclient.NUM_SENDER_THREADS + sensitive: false + validValues: [] + value: '1' + - datatype: Boolean + key: ISAM.Audit.syslogclient.CLIENT_CERT_AUTH_REQUIRED + sensitive: false + validValues: [] + value: 'false' + - datatype: Integer + key: ISAM.Audit.syslogclient.SERVER_PORT + sensitive: false + validValues: [] + value: '514' + - datatype: Hostname + key: ISAM.Audit.syslogclient.SERVER_HOST + sensitive: false + validValues: [] + value: 127.0.0.1 + - datatype: String + key: ISAM.Audit.syslogclient.TRANSPORT + sensitive: false + validValues: [] + value: TRANSPORT_UDP + - datatype: Integer + key: ISAM.Audit.syslogclient.QUEUE_FULL_TIMEOUT + sensitive: false + validValues: [] + value: '-1' + - datatype: Integer + key: ISAM.Audit.syslogclient.MAX_QUEUE_SIZE + sensitive: false + validValues: [] + value: '1000' + enabled: false + id: '1' + type: Syslog + verbose: false + """ + pol_id, update_required, json_data = _check(isamAppliance, id, config, enabled, type, verbose, use_json, components) + if pol_id is None: + from ibmsecurity.appliance.ibmappliance import IBMError + raise IBMError("999", "Cannot update data for unknown Audit Configuration ID: {0}".format(id)) + + if force is True or update_required is True: + if check_mode is True: + return isamAppliance.create_return_object(changed=True) + else: + return isamAppliance.invoke_put( + "Update Audit Configuration", + "{0}/{1}".format(uri, id), json_data, requires_modules=requires_modules, + requires_version=requires_version) + + return isamAppliance.create_return_object() + + +def _check(isamAppliance, id, config, enabled, type, verbose, use_json=False, components=None): + """ + Check and return True if update needed + """ + update_required = False + pol_id = None + # convert all values into string - any other type causes issues + for cfg in config: + if isinstance(cfg['value'], bool): + cfg['value'] = str(cfg['value']).lower() + else: + cfg['value'] = str(cfg['value']) + # Ensure boolean variables are set correctly + if isinstance(verbose, str): + if verbose.lower() == "true": + verbose = True + else: + verbose = False + if isinstance(enabled, str): + if enabled.lower() == "true": + enabled = True + else: + enabled = False + if isinstance(use_json, str): + if use_json.lower() == "true": + use_json = True + else: + use_json = False + json_data = { + "id": id, + "config": config, + "enabled": enabled, + "type": type, + "verbose": verbose, + "useJSONFormat": use_json + } + ret_obj = get(isamAppliance) + for aud_cfg in ret_obj['data']: + if id == aud_cfg['id']: + pol_id = id + break + if pol_id is None: + logger.warning("Audit Configuration not found, returning no update required.") + return pol_id, update_required, json_data + elif components is not None: + json_data["components"] = components + update_required = True + else: + import ibmsecurity.utilities.tools + sorted_json_data = ibmsecurity.utilities.tools.json_sort(json_data) + logger.debug("Sorted input: {0}".format(sorted_json_data)) + sorted_ret_obj = ibmsecurity.utilities.tools.json_sort(aud_cfg) + logger.debug("Sorted existing data: {0}".format(sorted_ret_obj)) + if sorted_ret_obj != sorted_json_data: + logger.info("Changes detected, update needed.") + update_required = True + + return pol_id, update_required, json_data + + +def compare(isamAppliance1, isamAppliance2): + """ + Compare Audit Configuration between two appliances + """ + ret_obj1 = get(isamAppliance1) + ret_obj2 = get(isamAppliance2) + + for obj in ret_obj1['data']: + del obj['id'] + for obj in ret_obj2['data']: + del obj['id'] + + return tools.json_compare(ret_obj1, ret_obj2, deleted_keys=['id']) diff --git a/ibmsecurity/isam/base/audit_configuration.py b/ibmsecurity/isam/base/audit_configuration.py index 688b33bb..f8bb4a91 100644 --- a/ibmsecurity/isam/base/audit_configuration.py +++ b/ibmsecurity/isam/base/audit_configuration.py @@ -13,6 +13,7 @@ comp_uri = uri + "/components" requires_modules = ["mga", "federation"] requires_version = None +warnings = ['This module (audit_configuration) is deprecated, use audit.configuration instead'] def get(isamAppliance, check_mode=False, force=False): @@ -20,7 +21,8 @@ def get(isamAppliance, check_mode=False, force=False): Retrieve audit configuration """ return isamAppliance.invoke_get("Retrieve audit configuration", uri, requires_modules=requires_modules, - requires_version=requires_version) + requires_version=requires_version, warnings=warnings) + def getComponents(isamAppliance, check_mode=False, force=False): """ @@ -165,12 +167,12 @@ def set(isamAppliance, id, config, enabled=True, type='Syslog', verbose=True, ch if force is True or update_required is True: if check_mode is True: - return isamAppliance.create_return_object(changed=True) + return isamAppliance.create_return_object(changed=True, warnings=warnings) else: return isamAppliance.invoke_put( "Update Audit Configuration", "{0}/{1}".format(uri, id), json_data, requires_modules=requires_modules, - requires_version=requires_version) + requires_version=requires_version, warnings=warnings) return isamAppliance.create_return_object() diff --git a/ibmsecurity/isam/base/ssl_certificates/personal_certificate.py b/ibmsecurity/isam/base/ssl_certificates/personal_certificate.py index 6c69fb71..74469eb6 100644 --- a/ibmsecurity/isam/base/ssl_certificates/personal_certificate.py +++ b/ibmsecurity/isam/base/ssl_certificates/personal_certificate.py @@ -10,16 +10,20 @@ def get_all(isamAppliance, kdb_id, check_mode=False, force=False): """ Retrieving personal certificate names and details in a certificate database """ - return isamAppliance.invoke_get("Retrieving personal certificate names and details in a certificate database", - "/isam/ssl_certificates/{0}/personal_cert".format(kdb_id)) + return isamAppliance.invoke_get( + "Retrieving personal certificate names and details in a certificate database", + "/isam/ssl_certificates/{0}/personal_cert".format(kdb_id) + ) def get(isamAppliance, kdb_id, cert_id, check_mode=False, force=False): """ Retrieving a personal certificate from a certificate database """ - return isamAppliance.invoke_get("Retrieving a personal certificate from a certificate database", - "/isam/ssl_certificates/{0}/personal_cert/{1}".format(kdb_id, cert_id)) + return isamAppliance.invoke_get( + "Retrieving a personal certificate from a certificate database", + "/isam/ssl_certificates/{0}/personal_cert/{1}".format(kdb_id, cert_id) + ) def generate(isamAppliance, kdb_id, label, dn, expire='365', default='no', size='2048', signature_algorithm='', @@ -66,22 +70,55 @@ def generate(isamAppliance, kdb_id, label, dn, expire='365', default='no', size= return isamAppliance.create_return_object(changed=False, warnings=warnings) +def rename(isamAppliance, kdb_id, cert_id, new_id, + check_mode=False, force=False): + """ + Rename a personal certificate. New in 10.0.7 + """ + warnings = [] + + if ibmsecurity.utilities.tools.version_compare(isamAppliance.facts["version"], "10.0.7.0") < 0: + warnings.append( + f"Appliance is at version: {isamAppliance.facts['version']}. Renaming a certificate requires at least 10.0.7.0" + ) + else: + if force or (_check(isamAppliance, kdb_id, cert_id) and not _check(isamAppliance, kdb_id, new_id)): + if check_mode: + return isamAppliance.create_return_object(changed=True) + else: + return isamAppliance.invoke_put( + "Renaming a personal certificate as default in a certificate database", + f"/isam/ssl_certificates/{kdb_id}/personal_cert/{cert_id}", + { + 'new_id': new_id + }) + + def set(isamAppliance, kdb_id, cert_id, default='no', check_mode=False, force=False): """ Setting a personal certificate as default in a certificate database + + Obsolete since 10.0.3 """ - if force is True or _check_default(isamAppliance, kdb_id, cert_id, default) is True: - if check_mode is True: - return isamAppliance.create_return_object(changed=True) - else: - return isamAppliance.invoke_put( - "Setting a personal certificate as default in a certificate database", - "/isam/ssl_certificates/{0}/personal_cert/{1}".format(kdb_id, cert_id), - { - 'default': default - }) + warnings = [] - return isamAppliance.create_return_object() + if ibmsecurity.utilities.tools.version_compare(isamAppliance.facts["version"], "10.0.3.0") > 0: + warnings.append( + f"Appliance is at version: {isamAppliance.facts['version']}. Setting certificates as default is no longer supported." + ) + else: + if force is True or _check_default(isamAppliance, kdb_id, cert_id, default): + if check_mode is True: + return isamAppliance.create_return_object(changed=True) + else: + return isamAppliance.invoke_put( + "Setting a personal certificate as default in a certificate database", + "/isam/ssl_certificates/{0}/personal_cert/{1}".format(kdb_id, cert_id), + { + 'default': default + }) + + return isamAppliance.create_return_object(warnings=warnings) def receive(isamAppliance, kdb_id, label, cert, default='no', check_mode=False, force=False): @@ -149,19 +186,20 @@ def import_cert(isamAppliance, kdb_id, cert, label=None, password=None, check_mo return isamAppliance.create_return_object(changed=True) else: return isamAppliance.invoke_post_files( - "Importing a personal certificate into a certificate database", - "/isam/ssl_certificates/{0}/personal_cert".format(kdb_id), - [ - { - 'file_formfield': 'cert', - 'filename': cert, - 'mimetype': 'application/octet-stream' - } - ], + "Importing a personal certificate into a certificate database", + "/isam/ssl_certificates/{0}/personal_cert".format(kdb_id), + [ { - 'password': password, - 'operation': 'import' - }) + 'file_formfield': 'cert', + 'filename': cert, + 'mimetype': 'application/octet-stream' + } + ], + { + 'password': password, + 'operation': 'import' + } + ) return isamAppliance.create_return_object() diff --git a/pyproject.toml b/pyproject.toml index 8fadf66e..dcf50210 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" [project] name = "ibmsecurity" -version = "2024.10.11.0" +version = "2024.12.6.0" authors = [ { name="IBM", email="secorch@wwpdl.vnet.ibm.com" }, ] diff --git a/setup.py b/setup.py index c0e05e29..c0bcf74b 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ packages=find_packages(), # Date of release used for version - please be sure to use YYYY.MM.DD.seq#, MM and DD should be two digits e.g. 2017.02.05.0 # seq# will be zero unless there are multiple release on a given day - then increment by one for additional release for that date - version="2024.10.11.0", + version="2024.12.6.0", description="Idempotent functions for IBM Security Appliance REST APIs", author="IBM", author_email="secorch@wwpdl.vnet.ibm.com",