From afef62e52a6bbb248399b37d7cc9f48b934b391b Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Mon, 22 Jan 2024 17:01:04 +0100 Subject: [PATCH 01/13] feature: add junction_server_exists function --- .../isam/web/reverse_proxy/junctions.py | 245 +++++++++++------- 1 file changed, 157 insertions(+), 88 deletions(-) diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions.py b/ibmsecurity/isam/web/reverse_proxy/junctions.py index 5d709ccb..b7a5ce2d 100644 --- a/ibmsecurity/isam/web/reverse_proxy/junctions.py +++ b/ibmsecurity/isam/web/reverse_proxy/junctions.py @@ -1,5 +1,6 @@ import logging from ibmsecurity.utilities import tools +import json try: basestring @@ -79,8 +80,14 @@ def get(isamAppliance, reverseproxy_id, junctionname, check_mode=False, force=Fa return ret_obj -def _check(isamAppliance, reverseproxy_id, junctionname): - ret_obj = get_all(isamAppliance, reverseproxy_id) +def _check(isamAppliance, reverseproxy_id, junctionname, currentJunctions=None): + """ CurrentJunctions is the output of get_all. + This avoids constantly having to call the get_all function. + """ + if currentJunctions is not None: + ret_obj = get_all(isamAppliance, reverseproxy_id) + else: + ret_obj = currentJunctions for jct in ret_obj['data']: if jct['id'] == junctionname: @@ -365,95 +372,25 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ if _check(isamAppliance, reverseproxy_id, junction_point) is True: logger.debug("Junction exists. Compare junction details.") ret_obj = get(isamAppliance, reverseproxy_id=reverseproxy_id, junctionname=junction_point) - server_found = False + logger.debug("See if the backend junction server matches any on the junction. Look for just one match.") srvs = ret_obj['data']['servers'] srvs_len = len(srvs) - for srv in srvs: - if srv['server_hostname'] == server_hostname and str(srv['server_port']) == str(server_port): - logger.debug("Matched a server - {0}.".format(srv)) - server_found = True - server_json = { - 'server_hostname': server_hostname, - 'server_port': str(server_port) - } - if case_sensitive_url is None: - server_json['case_sensitive_url'] = 'no' - else: - server_json['case_sensitive_url'] = case_sensitive_url - if http_port is None: - server_json['http_port'] = str(server_port) - else: - server_json['http_port'] = str(http_port) - if local_ip is None: - server_json['local_ip'] = '' - else: - server_json['local_ip'] = local_ip - if query_contents is None or query_contents == '': - server_json['query_content_url'] = '/cgi-bin/query_contents' - else: - server_json['query_content_url'] = query_contents - if server_dn is None: - server_json['server_dn'] = '' - else: - server_json['server_dn'] = server_dn - if server_uuid is not None: - server_json['server_uuid'] = server_uuid - else: - # Server UUID gets generated if not specified - if 'server_uuid' in srv: - del srv['server_uuid'] - if not isVirtualJunction: - if virtual_hostname: - logger.debug("Only for standard junctions - {0}.".format(virtual_hostname)) - server_json['virtual_junction_hostname'] = virtual_hostname - else: - if server_json['server_port'] in ['80','443',80,443]: - server_json['virtual_junction_hostname'] = server_json['server_hostname'] - else: - server_json['virtual_junction_hostname'] = server_json['server_hostname'] + ":" + server_json['server_port'] - if windows_style_url is None: - server_json['windows_style_url'] = 'no' - else: - server_json['windows_style_url'] = windows_style_url - # v10.0.2 - if tools.version_compare(isamAppliance.facts["version"], "10.0.2.0") >= 0: - if priority is None: - server_json['priority'] = '9' - else: - server_json['priority'] = str(priority) - if server_cn is None: - server_json['server_cn'] = '' - else: - server_json['server_cn'] = server_cn - # Delete dynamic data shown when we get junctions details - if 'current_requests' in srv: - del srv['current_requests'] - if 'total_requests' in srv: - del srv['total_requests'] - if 'operation_state' in srv: - del srv['operation_state'] - if 'server_state' in srv: - del srv['server_state'] - # Not sure what this attribute is supposed to contain? - if 'query_contents' in srv: - del srv['query_contents'] - if not isVirtualJunction: - if 'virtual_junction_hostname' not in srv: - # this is not in the returned servers object for virtual host junctions, it's in the junction's object - if virtual_hostname: - srv['virtual_junction_hostname'] = virtual_hostname - else: - srv['virtual_junction_hostname'] = srv['server_hostname'] + ":" + srv['server_port'] - if tools.json_sort(server_json) != tools.json_sort(srv): - logger.debug("Servers are found to be different. See following JSON for difference.") - logger.debug("New Server JSON: {0}".format(tools.json_sort(server_json))) - logger.debug("Old Server JSON: {0}".format(tools.json_sort(srv))) - add_required = True - break - if server_found is False: + + if not junction_server_exists(srvs, server_hostname, server_port, + case_sensitive_url, + isVirtualJunction, + http_port, + local_ip, + query_contents, + server_dn, + server_uuid, + virtual_hostname, + windows_style_url, + priority, + server_cn): add_required = True - elif add_required is False: + elif not add_required: exist_jct = ret_obj['data'] jct_json = { 'junction_point': junction_point, @@ -625,7 +562,7 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ if tools.json_sort(jct_json) != tools.json_sort(exist_jct): logger.debug("Junctions are found to be different. See following JSON for difference.") add_required = True - if add_required is True and srvs_len > 1: + if add_required and srvs_len > 1: warnings.append( "Junction will replaced. Existing multiple servers #{0} will be overwritten. Please re-add as needed.".format( srvs_len)) @@ -696,3 +633,135 @@ def compare(isamAppliance1, isamAppliance2, reverseproxy_id, reverseproxy_id2=No return tools.json_compare(ret_obj1, ret_obj2, deleted_keys=['active_worker_threads', 'servers/current_requests', 'servers/operation_state', 'servers/server_state', 'servers/server_uuid', 'servers/total_requests']) + +def set_all(isamAppliance, reverseproxy_id, junctions=[], check_mode=False, force=False): + """ + Set junctions + The input is a list of junction objects, that can be passed to the `set` function + The list of junctions is first compared to the output of `get_all`, so we only need to update junctions that are changed. + + :param isamAppliance: + :param reverseproxy_id: + :param junction: List of junctions to set + :param check_mode: + :param force: + :return: + """ + currentJunctions = get_all(isamAppliance, reverseproxy_id=reverseproxy_id, detailed=True) + + if currentJunctions['rc'] == 0: + logger.debug(f"\nCurrent junctions:\n{currentJunctions}") + + else: + # no junctions exists + # TODO + logger.debug("No junctions exist yet. Create them all.") + # Compare the junctions and the currentJunctions. + for j in junctions: + #result = next((item for item in currentJunctions if item["junction_name"] == j["junction_name"]), None) + logger.debug(f"New junction: {j['junction_point']}") + _checkUpdate = False + for c in currentJunctions['data']: + if c['junction_point'] == j['junction_point']: + logger.debug(f"The junction at {j['junction_point']} already exists.") + _checkUpdate = True + if _checkUpdate: + # perform a comparison. + cj = json.dumps(c, skipkeys=True, sort_keys=True) + logger.debug(f"\nSorted Current Junction:\n {c}\n") + + nj = json.dumps(j, skipkeys=True, sort_keys=True) + logger.debug(f"\nSorted Desired Junction:\n {j}\n") + else: + set(isamAppliance, reverseproxy_id, j['junction_point'], j['server_hostname'], j['server_port'], force=True) + + +def junction_server_exists(srvs, server_hostname, server_port, case_sensitive_url='no', + isVirtualJunction=False, http_port=None, local_ip=None, + query_contents=None, server_dn=None, + server_uuid=None, virtual_hostname=None, windows_style_url=None, priority=None, server_cn=None): + server_found = False + for srv in srvs: + if srv['server_hostname'] == server_hostname and str(srv['server_port']) == str(server_port): + logger.debug("Matched a server - {0}.".format(srv)) + server_found = True + server_json = { + 'server_hostname': server_hostname, + 'server_port': str(server_port) + } + if case_sensitive_url is None: + server_json['case_sensitive_url'] = 'no' + else: + server_json['case_sensitive_url'] = case_sensitive_url + if http_port is None: + server_json['http_port'] = str(server_port) + else: + server_json['http_port'] = str(http_port) + if local_ip is None: + server_json['local_ip'] = '' + else: + server_json['local_ip'] = local_ip + if query_contents is None or query_contents == '': + server_json['query_content_url'] = '/cgi-bin/query_contents' + else: + server_json['query_content_url'] = query_contents + if server_dn is None: + server_json['server_dn'] = '' + else: + server_json['server_dn'] = server_dn + if server_uuid is not None: + server_json['server_uuid'] = server_uuid + else: + # Server UUID gets generated if not specified + if 'server_uuid' in srv: + del srv['server_uuid'] + if not isVirtualJunction: + if virtual_hostname: + logger.debug("Only for standard junctions - {0}.".format(virtual_hostname)) + server_json['virtual_junction_hostname'] = virtual_hostname + else: + if server_json['server_port'] in ['80', '443', 80, 443]: + server_json['virtual_junction_hostname'] = server_json['server_hostname'] + else: + server_json['virtual_junction_hostname'] = server_json['server_hostname'] + ":" + server_json[ + 'server_port'] + if windows_style_url is None: + server_json['windows_style_url'] = 'no' + else: + server_json['windows_style_url'] = windows_style_url + # v10.0.2 + if tools.version_compare(isamAppliance.facts["version"], "10.0.2.0") >= 0: + if priority is None: + server_json['priority'] = '9' + else: + server_json['priority'] = str(priority) + if server_cn is None: + server_json['server_cn'] = '' + else: + server_json['server_cn'] = server_cn + # Delete dynamic data shown when we get junctions details + if 'current_requests' in srv: + del srv['current_requests'] + if 'total_requests' in srv: + del srv['total_requests'] + if 'operation_state' in srv: + del srv['operation_state'] + if 'server_state' in srv: + del srv['server_state'] + # Not sure what this attribute is supposed to contain? + if 'query_contents' in srv: + del srv['query_contents'] + if not isVirtualJunction: + if 'virtual_junction_hostname' not in srv: + # this is not in the returned servers object for virtual host junctions, it's in the junction's object + if virtual_hostname: + srv['virtual_junction_hostname'] = virtual_hostname + else: + srv['virtual_junction_hostname'] = srv['server_hostname'] + ":" + srv['server_port'] + if tools.json_sort(server_json) != tools.json_sort(srv): + logger.debug("Servers are found to be different. See following JSON for difference.") + logger.debug("New Server JSON: {0}".format(tools.json_sort(server_json))) + logger.debug("Old Server JSON: {0}".format(tools.json_sort(srv))) + add_required = True + break + return server_found From 507a8b17d88261ac19f96818d6014a2392d01670 Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Fri, 2 Feb 2024 14:07:50 +0100 Subject: [PATCH 02/13] feature: add junction_server set code. --- .../web/reverse_proxy/junctions_server.py | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions_server.py b/ibmsecurity/isam/web/reverse_proxy/junctions_server.py index 967f09e3..ee5663ec 100644 --- a/ibmsecurity/isam/web/reverse_proxy/junctions_server.py +++ b/ibmsecurity/isam/web/reverse_proxy/junctions_server.py @@ -115,6 +115,105 @@ def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, junctio return isamAppliance.create_return_object() +def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, junction_type="tcp", check_mode=False, force=False, warnings=[], + **optionargs): + """ + Adding a back-end server to an existing standard or virtual junctions + + :param isamAppliance: + :param reverseproxy_id: + :param junction_point: + :param server_hostname: + :param junction_type: + :param server_port: + :param virtual_hostname: + :param virtual_https_hostname: + :param server_dn: + :param query_contents: + :param stateful_junction: + :param case_sensitive_url: + :param windows_style_url: + :param https_port: + :param http_port: + :param proxy_hostname: + :param proxy_port: + :param sms_environment: + :param vhost_label: + :param server_uuid: + :param server_cn: + :param priority: + :param check_mode: + :param force: + :return: + """ + # load option args + # server_dn=None, + # stateful_junction='no', case_sensitive_url='no', windows_style_url='no', virtual_hostname=None, + # virtual_https_hostname=None, query_contents=None, https_port=None, http_port=None, proxy_hostname=None, + # proxy_port=None, sms_environment=None, vhost_label=None, server_uuid=None, + # server_cn=None, priority='9' + + # Search an existing server + ret_obj = get(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port) + exist_jct = {} + jct_srv_json = {} + if ret_obj: + exist_jct = ret_obj.get('data', {}) + exist_jct.pop('current_requests', None) + exist_jct.pop('total_requests', None) + exist_jct.pop('operation_state', None) + exist_jct.pop('server_state', None) + exist_jct.pop('query_contents', None) + + for _k, _v in optionargs.items(): + if _v is not None: + jct_srv_json[_k] = _v + + # add defaults + #defaults_no = ["stateful_junction", "case_sensitive_url", "windows_style_url"] + #for d in defaults_no: + # if jct_srv_json.get(d, None) is None: + # jct_srv_json[d] = 'no' + # 10.0.0.2 + if jct_srv_json.get('priority', None) is not None: + if tools.version_compare(isamAppliance.facts["version"], "10.0.2.0") < 0: + warnings.append( + "Appliance at version: {0}, priority: {1} is not supported. Needs 10.0.2.0 or higher. Ignoring description for this call.".format( + isamAppliance.facts["version"], priority)) + jct_srv_json.pop('priority', None) + else: + if tools.version_compare(isamAppliance.facts["version"], "10.0.2.0") >= 0: + warnings.append( + "Appliance at version: {0}, priority is required".format( + isamAppliance.facts["version"])) + jct_srv_json['priority'] = "9" + + # only compare values that are in the new request + exist_jct = {k: v for k, v in exist_jct.items() if k in jct_srv_json.keys()} + + newJSON = json.dumps(jct_srv_json, skipkeys=True, sort_keys=True) + logger.debug(f"\nSorted Desired Junction {junction_point} - {server_hostname}:\n\n {newJSON}\n") + + oldJSON = json.dumps(exist_jct, skipkeys=True, sort_keys=True) + logger.debug(f"\nSorted Current Junction {junction_point} - {server_hostname}:\n\n {oldJSON}\n") + + jct_srv_json["junction_point"] = junction_point + jct_srv_json["junction_type"] = junction_type + jct_srv_json["server_hostname"] = server_hostname + jct_srv_json["server_port"] = server_port + + if force or (newJSON != newJSON): + logger.debug(f"The JSONs are different. We're going to add the servers.") + if check_mode is True: + return isamAppliance.create_return_object(changed=True, warnings=warnings) + else: + return isamAppliance.invoke_put( + "Adding a back-end server to an existing standard or virtual junctions", + "{0}/{1}/junctions".format(uri, reverseproxy_id), data=jct_srv_json, warnings=warnings) + else: + logger.debug("Servers are the same") + return isamAppliance.create_return_object(warnings=warnings) + def delete(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, check_mode=False, force=False): """ From fb72eb904f4f28a1077c831e1cafc0a4f2131506 Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Mon, 5 Feb 2024 08:32:40 +0100 Subject: [PATCH 03/13] feature: set_all() --- .../isam/web/reverse_proxy/junctions.py | 225 ++++++++++++++++-- .../web/reverse_proxy/junctions_server.py | 113 +++++---- 2 files changed, 274 insertions(+), 64 deletions(-) diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions.py b/ibmsecurity/isam/web/reverse_proxy/junctions.py index b7a5ce2d..0e5d42d3 100644 --- a/ibmsecurity/isam/web/reverse_proxy/junctions.py +++ b/ibmsecurity/isam/web/reverse_proxy/junctions.py @@ -1,5 +1,6 @@ import logging from ibmsecurity.utilities import tools +import ibmsecurity.isam.web.reverse_proxy.junctions_server as junctions_server import json try: @@ -354,13 +355,14 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ username=None, password=None, server_uuid=None, local_ip=None, ltpa_keyfile_password=None, delegation_support=None, scripting_support=None, insert_ltpa_cookies=None, check_mode=False, force=False, http2_junction=None, http2_proxy=None, sni_name=None, description=None, - priority=None, server_cn=None, silent=None): + priority=None, server_cn=None, silent=None, servers=None): """ Setting a standard or virtual junction - compares with existing junction and replaces if changes are detected TODO: Compare all the parameters in the function - LTPA, BA are some that are not being compared """ warnings = [] add_required = False + servers = None # servers is merely there to allow it as a parameter. # See if it's a virtual or standard junction isVirtualJunction = True if junction_point[:1] == '/': @@ -377,7 +379,7 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ srvs = ret_obj['data']['servers'] srvs_len = len(srvs) - if not junction_server_exists(srvs, server_hostname, server_port, + if not junction_server_exists(isamAppliance, srvs, server_hostname, server_port, case_sensitive_url, isVirtualJunction, http_port, @@ -564,12 +566,12 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ add_required = True if add_required and srvs_len > 1: warnings.append( - "Junction will replaced. Existing multiple servers #{0} will be overwritten. Please re-add as needed.".format( + "Junction will be replaced. Existing multiple servers #{0} will be overwritten. Please re-add as needed.".format( srvs_len)) else: add_required = True - if force is True or add_required is True: + if force is True or add_required: # Junction force add will replace the junction, no need for delete (force set to True as a result) return add(isamAppliance=isamAppliance, reverseproxy_id=reverseproxy_id, junction_point=junction_point, server_hostname=server_hostname, server_port=server_port, junction_type=junction_type, @@ -634,52 +636,231 @@ def compare(isamAppliance1, isamAppliance2, reverseproxy_id, reverseproxy_id2=No 'servers/operation_state', 'servers/server_state', 'servers/server_uuid', 'servers/total_requests']) -def set_all(isamAppliance, reverseproxy_id, junctions=[], check_mode=False, force=False): +def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode=False, force=False, warnings=[]): """ - Set junctions + Set junctions with all the servers The input is a list of junction objects, that can be passed to the `set` function The list of junctions is first compared to the output of `get_all`, so we only need to update junctions that are changed. :param isamAppliance: :param reverseproxy_id: - :param junction: List of junctions to set + :param junctions: List of junctions to set :param check_mode: :param force: :return: """ currentJunctions = get_all(isamAppliance, reverseproxy_id=reverseproxy_id, detailed=True) + server_fields = {'server_hostname': {'type': 'string'}, + 'server_port': {'type': 'number'}, + 'case_sensitive_url': {'type': 'yesno', 'max_version': "10.0.6.0"}, + 'case_insensitive_url': {'type': 'yesno', 'min_version': "10.0.6.0"}, + 'http_port': {'type': 'number'}, + 'local_ip': {'type': 'string'}, + 'query_contents': {'type': 'string', 'alt_name': 'query_content_url'}, + 'server_dn': {'type': 'string'}, + 'server_uuid': {'type': 'ignore'}, + 'virtual_hostname': {'type': 'string', 'alt_name': 'virtual_junction_hostname'}, + 'windows_style_url': {'type': 'yesno'}, + 'current_requests': {'type': 'ignore'}, + 'total_requests': {'type': 'ignore'}, + 'operation_state': {'type': 'ignore'}, + 'server_state': {'type': 'ignore'}, + 'priority': {'type': 'number', 'min_version': "10.0.2.0"}, + 'server_cn': {'type': 'string', 'min_version': "10.0.2.0"}} + if currentJunctions['rc'] == 0: logger.debug(f"\nCurrent junctions:\n{currentJunctions}") - else: # no junctions exists - # TODO logger.debug("No junctions exist yet. Create them all.") + # Force create - There are no junctions yet + # use expansion + for j in junctions: + j.pop('isVirtualJunction', None) + __firstserver = j.get('servers', [''])[0] + for _field in list(server_fields.keys()): + if __firstserver.get(_field, None) is not None and j.get(_field, None) is None: + # only use server_fields if they are not defined on junction level + logger.debug(f"{_field} from {__firstserver.get('server_hostname')} copied to junction level") + j[_field] = __firstserver.get(_field, None) + if len(j.get('servers', [''])) > 1: + __servers = j.get('servers', [''])[1:] + else: + __servers = None + j.pop('servers', None) + set(isamAppliance, reverseproxy_id, **j, force=True) + # Also add servers (if servers[] has more than 1 item) + if __servers is not None: + for s in __servers: + for _field, kval in server_fields.items(): + if s.get(_field, None) is not None: + j[_field] = s.get(_field, None) + junctions_server.set(isamAppliance, reverseproxy_id, **j) # Compare the junctions and the currentJunctions. for j in junctions: #result = next((item for item in currentJunctions if item["junction_name"] == j["junction_name"]), None) - logger.debug(f"New junction: {j['junction_point']}") + logger.debug(f"Processing junction: {j['junction_point']}") _checkUpdate = False + j['isVirtualJunction'] = True + if j['junction_point'][:1] == '/': + j['isVirtualJunction'] = False + if j.get('junction_soft_limit', None) is None: + j['junction_soft_limit'] = '0 - using global value' + else: + j['junction_soft_limit'] = str( j.get('junction_soft_limit', None)) + if j.get('junction_hard_limit', None) is None: + j['junction_hard_limit'] = '0 - using global value' + else: + j['junction_hard_limit'] = str( j.get('junction_hard_limit', None)) + + # convenience. + # check that we have the required fields, if not, get them from the first server (if that exists) + __firstserver = j.get('servers', [''])[0] + for _field in list(server_fields.keys()): + if __firstserver.get(_field, None) is not None and j.get(_field, None) is None: + # only use server_fields if they are not defined on junction level + logger.debug(f"{_field} from {__firstserver.get('server_hostname')} copied to junction level") + j[_field] = __firstserver.get(_field, None) + if len(j.get('servers', [''])) > 1: + __servers = j.get('servers', [''])[1:] + for c in currentJunctions['data']: + servers = [] + if tools.version_compare(isamAppliance.facts["version"], "9.0.1.0") > 0: + srv_separator = '#' + else: + srv_separator = '&' + + if c.get('servers', None) is not None: + if type(c.get('servers',[])) is str: + srvs = c['servers'].split(srv_separator) + logger.debug("Servers in raw string: {0}".format(c['servers'])) + logger.debug("Number of servers in junction: {0}".format(len(srvs))) + for srv in srvs: + logger.debug("Parsing Server: {0}".format(srv)) + server = {} + for s in srv.split(';'): + if s != '': + kv = s.split('!') + server[kv[0]] = kv[1] + servers.append(server) + c['servers'] = servers + else: + c['servers'] = [] + if c['junction_point'] == j['junction_point']: logger.debug(f"The junction at {j['junction_point']} already exists.") - _checkUpdate = True + _checkUpdate = c + break + if _checkUpdate: - # perform a comparison. - cj = json.dumps(c, skipkeys=True, sort_keys=True) - logger.debug(f"\nSorted Current Junction:\n {c}\n") + # perform a comparison. we receive the actual current junction as input here + srvs = c['servers'] + logger.debug(f"\n\nServers in junction {j['junction_point']}:\n{srvs}") + add_required = False + if not junction_server_exists(isamAppliance, srvs, **j): + add_required = True + if not add_required: + # ok we still need to compare + exist_jct = c + j.pop('isVirtualJunction', None) + new_jct = dict(j) + + exist_jct.pop('active_worker_threads', None) + # remove the server fields - this has already been compared + for _field, kval in server_fields.items(): + exist_jct.pop(_field, None) + new_jct.pop(_field, None) + # remove the servers for the comparison + new_jct.pop('servers', None) + exist_jct.pop('servers', None) + #insert_ltpa_cookies + if exist_jct.get('insert_ltpa_cookies', None) is None: + exist_jct['insert_ltpa_cookies'] = 'no' + #sms_environment + if exist_jct.get('sms_environment', None) is None: + exist_jct['sms_environment'] = "" + #vhost_label + if exist_jct.get('vhost_label', None) is None: + exist_jct['vhost_label'] = "" + # request_encoding utf8_bin, utf8_uri, lcp_bin, and lcp_uri. + __re = exist_jct.get('request_encoding', None) + if __re is not None: + if __re == 'UTF-8, URI Encoded': + exist_jct['request_encoding'] = 'utf8_uri' + elif __re == 'UTF-8, Binary': + exist_jct['request_encoding'] = 'utf8_bin' + elif __re == 'Local Code Page, Binary': + exist_jct['request_encoding'] = 'lcp_bin' + elif __re == 'Local Code Page, URI Encoded': + exist_jct['request_encoding'] = 'lcp_uri' - nj = json.dumps(j, skipkeys=True, sort_keys=True) - logger.debug(f"\nSorted Desired Junction:\n {j}\n") - else: - set(isamAppliance, reverseproxy_id, j['junction_point'], j['server_hostname'], j['server_port'], force=True) + # To allow for multiple header values to be sorted during compare convert retrieved data into array + if exist_jct['remote_http_header'].startswith('insert - '): + exist_jct['remote_http_header'] = [_word.replace('_','-') for _word in (exist_jct['remote_http_header'][9:]).split(' ')] + elif exist_jct['remote_http_header'] == 'do not insert': + exist_jct['remote_http_header'] = [] + exist_jct['junction_type'] = exist_jct['junction_type'].lower() + # only compare values that are in the new request + # This may give unexpected results. + exist_jct = {k: v for k, v in exist_jct.items() if k in j.keys()} + newJSON = json.dumps(new_jct, skipkeys=True, sort_keys=True) + logger.debug(f"\nSorted Desired Junction {j['junction_point']}:\n\n {newJSON}\n") + + oldJSON = json.dumps(exist_jct, skipkeys=True, sort_keys=True) + logger.debug(f"\nSorted Current Junction {c['junction_point']}:\n\n {oldJSON}\n") + + if newJSON != oldJSON: + logger.debug("Junctions are found to be different. See JSON for difference.") + add_required = True + if add_required: + # Run set() + logger.debug("\n\nUpdate junction\n\n") + + set(isamAppliance, reverseproxy_id, **j, force=True) + logger.debug(f"Adding servers") + # Also add servers (if servers[] has more than 1 item) + if len(j.get('servers', [''])) > 1: + + j.pop('servers', None) + for s in __servers: + for _field, kval in server_fields.items(): + if s.get(_field, None) is not None: + j[_field] = s.get(_field, None) + junctions_server.set(isamAppliance, reverseproxy_id, **j) + else: + logger.debug(f"\n\nJunction {j.get('junction_point','')}does not need updating\n\n") + warnings.append(f"Junction {j.get('junction_point','')}does not need updating") -def junction_server_exists(srvs, server_hostname, server_port, case_sensitive_url='no', + else: + # Force create - this junction does not exist yet + j.pop('isVirtualJunction', None) + __firstserver = j.get('servers', [''])[0] + for _field in list(server_fields.keys()): + if __firstserver.get(_field, None) is not None and j.get(_field, None) is None: + # only use server_fields if they are not defined on junction level + logger.debug(f"{_field} from {__firstserver.get('server_hostname')} copied to junction level") + j[_field] = __firstserver.get(_field, None) + logger.debug(f"Creating new junction with {j}") + set(isamAppliance, reverseproxy_id, **j, force=True) + logger.debug(f"Adding servers") + # Also add servers (if servers[] has more than 1 item) + if len(j.get('servers', [''])) > 1: + __servers = j.get('servers', [''])[1:] + j.pop('servers', None) + for s in __servers: + for _field, kval in server_fields.items(): + if s.get(_field, None) is not None: + j[_field] = s.get(_field, None) + junctions_server.set(isamAppliance, reverseproxy_id, **j) +#def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, junction_type="tcp", check_mode=False, force=False, warnings=[], **optionargs): + +def junction_server_exists(isamAppliance, srvs, server_hostname: str, server_port, case_sensitive_url='no', isVirtualJunction=False, http_port=None, local_ip=None, query_contents=None, server_dn=None, - server_uuid=None, virtual_hostname=None, windows_style_url=None, priority=None, server_cn=None): + server_uuid=None, virtual_hostname=None, windows_style_url=None, priority=None, server_cn=None, **kwargs) -> bool: server_found = False for srv in srvs: if srv['server_hostname'] == server_hostname and str(srv['server_port']) == str(server_port): @@ -762,6 +943,8 @@ def junction_server_exists(srvs, server_hostname, server_port, case_sensitive_ur logger.debug("Servers are found to be different. See following JSON for difference.") logger.debug("New Server JSON: {0}".format(tools.json_sort(server_json))) logger.debug("Old Server JSON: {0}".format(tools.json_sort(srv))) - add_required = True + break return server_found + + diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions_server.py b/ibmsecurity/isam/web/reverse_proxy/junctions_server.py index ee5663ec..2ae54bee 100644 --- a/ibmsecurity/isam/web/reverse_proxy/junctions_server.py +++ b/ibmsecurity/isam/web/reverse_proxy/junctions_server.py @@ -2,6 +2,7 @@ import ibmsecurity.utilities.tools from ibmsecurity.utilities import tools import ibmsecurity.isam.web.reverse_proxy.junctions +import json logger = logging.getLogger(__name__) @@ -19,11 +20,18 @@ def search(isamAppliance, reverseproxy_id, junction_point, server_hostname, serv return ret_obj_new +def get(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port): + ret_obj_new = isamAppliance.create_return_object() + ret_obj = ibmsecurity.isam.web.reverse_proxy.junctions.get(isamAppliance, reverseproxy_id, junction_point) + for s in ret_obj['data']['servers']: + logger.debug("Servers in Junction server: {0} port: {1}".format(s['server_hostname'], s['server_port'])) + if str(server_hostname) == str(s['server_hostname']) and str(server_port) == str(s['server_port']): + ret_obj_new['data'] = s + break + return ret_obj_new -def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, junction_type, server_port, server_dn=None, - stateful_junction='no', case_sensitive_url='no', windows_style_url='no', virtual_hostname=None, - virtual_https_hostname=None, query_contents=None, https_port=None, http_port=None, proxy_hostname=None, - proxy_port=None, sms_environment=None, vhost_label=None, server_uuid=None, priority=None, server_cn=None, check_mode=False, force=False): +def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, junction_type="tcp", check_mode=False, force=False, + **optionargs): """ Adding a back-end server to an existing standard or virtual junctions @@ -66,49 +74,47 @@ def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, junctio "junction_type": junction_type, "server_hostname": server_hostname, "server_port": server_port, - "stateful_junction": stateful_junction, - "case_sensitive_url": case_sensitive_url, - "windows_style_url": windows_style_url, } - if https_port is not None: - jct_srv_json["https_port"] = https_port - if http_port is not None: - jct_srv_json["http_port"] = http_port - if proxy_hostname is not None: - jct_srv_json["proxy_hostname"] = proxy_hostname - if proxy_port is not None: - jct_srv_json["proxy_port"] = proxy_port - if sms_environment is not None: - jct_srv_json["sms_environment"] = sms_environment - if vhost_label is not None: - jct_srv_json["vhost_label"] = vhost_label - if server_dn is not None: - jct_srv_json["server_dn"] = server_dn - if virtual_hostname: - jct_srv_json["virtual_hostname"] = virtual_hostname - if virtual_https_hostname is not None: - jct_srv_json["virtual_https_hostname"] = virtual_https_hostname - if query_contents is not None: - jct_srv_json["query_contents"] = query_contents - if server_uuid is not None and server_uuid != '': - jct_srv_json["server_uuid"] = server_uuid - if server_cn is not None: - if tools.version_compare(isamAppliance.facts["version"], "10.0.2.0") < 0: + for _k, _v in optionargs.items(): + if _v is not None: + jct_srv_json[_k] = _v + if jct_srv_json.get('stateful_junction', None) is None: + jct_srv_json['stateful_junction'] = 'no' + if jct_srv_json.get('windows_style_url', None) is None: + jct_srv_json['windows_style_url'] = 'no' + + # case_insensitive/case_sensitive + if tools.version_compare(isamAppliance.facts["version"], "10.0.6.0") >= 0: + # If no case_insensitive_url is passed, we take the old one and invert it. + # Who thinks it's a good idea to make changes in an API like this ? + if jct_srv_json.get('case_insensitive_url', None) is None: + case_sensitive_url = jct_srv_json.get('case_sensitive_url', None) + if case_sensitive_url is not None and case_sensitive_url.lower() == 'no': + jct_srv_json["case_insensitive_url"] = 'yes' + else: + jct_srv_json["case_insensitive_url"] = 'no' # default + else: + jct_srv_json.pop("case_insensitive_url", None) + + if tools.version_compare(isamAppliance.facts["version"], "10.0.2.0") < 0: + if jct_srv_json.get('server_cn', None) is not None: warnings.append( "Appliance at version: {0}, server_cn: {1} is not supported. Needs 10.0.2.0 or higher. Ignoring server_cn for this call.".format( isamAppliance.facts["version"], server_cn)) - server_cn = None - else: - jct_srv_json["server_cn"] = server_cn - if priority is not None: - if tools.version_compare(isamAppliance.facts["version"], "10.0.2.0") < 0: + jct_srv_json.pop("server_cn", None) + + if tools.version_compare(isamAppliance.facts["version"], "10.0.2.0") < 0: + if jct_srv_json.get('priority', None) is not None: warnings.append( "Appliance at version: {0}, priority: {1} is not supported. Needs 10.0.2.0 or higher. Ignoring priority for this call.".format( isamAppliance.facts["version"], priority)) - priority = None - else: - jct_srv_json["priority"] = priority - + jct_srv_json.pop("priority", None) + else: + if jct_srv_json.get('priority', None) is None: + warnings.append( + "Appliance at version: {0}, priority is required on 10.0.2 or higher".format( + isamAppliance.facts["version"])) + jct_srv_json['priority'] = "9" return isamAppliance.invoke_put( "Adding a back-end server to an existing standard or virtual junctions", "{0}/{1}/junctions".format(uri, reverseproxy_id), jct_srv_json) @@ -165,10 +171,31 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ exist_jct.pop('server_state', None) exist_jct.pop('query_contents', None) + server_fields = {'server_hostname': {'type': 'string'}, + 'server_port': {'type': 'number'}, + 'case_sensitive_url': {'type': 'yesno', 'max_version': "10.0.6.0"}, + 'case_insensitive_url': {'type': 'yesno', 'min_version': "10.0.6.0"}, + 'http_port': {'type': 'number'}, + 'local_ip': {'type': 'string'}, + 'query_contents': {'type': 'string', 'alt_name': 'query_content_url'}, + 'server_dn': {'type': 'string'}, + 'server_uuid': {'type': 'ignore'}, + 'virtual_hostname': {'type': 'string', 'alt_name': 'virtual_junction_hostname'}, + 'windows_style_url': {'type': 'yesno'}, + 'current_requests': {'type': 'ignore'}, + 'total_requests': {'type': 'ignore'}, + 'operation_state': {'type': 'ignore'}, + 'server_state': {'type': 'ignore'}, + 'priority': {'type': 'number', 'min_version': "10.0.2.0"}, + 'server_cn': {'type': 'string', 'min_version': "10.0.2.0"}} + for _k, _v in optionargs.items(): - if _v is not None: + # only keep valid arguments + if _k in list(server_fields.keys()): jct_srv_json[_k] = _v - + else: + logger.debug(f"Invalid input parameter used {_k}") + warnings.append(f"Invalid input parameter used in function junctions_server.set() : {_k}") # add defaults #defaults_no = ["stateful_junction", "case_sensitive_url", "windows_style_url"] #for d in defaults_no: @@ -202,7 +229,7 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ jct_srv_json["server_hostname"] = server_hostname jct_srv_json["server_port"] = server_port - if force or (newJSON != newJSON): + if force or (newJSON != oldJSON): logger.debug(f"The JSONs are different. We're going to add the servers.") if check_mode is True: return isamAppliance.create_return_object(changed=True, warnings=warnings) From b18bcc7591c9b8ee849f2c0c2e3bf4089b59642d Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Tue, 6 Feb 2024 11:57:59 +0100 Subject: [PATCH 04/13] fix: correct logic in _check --- .../isam/web/reverse_proxy/junctions.py | 5 ++- .../web/reverse_proxy/junctions_server.py | 38 +++++++++---------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions.py b/ibmsecurity/isam/web/reverse_proxy/junctions.py index 0e5d42d3..fd4972f0 100644 --- a/ibmsecurity/isam/web/reverse_proxy/junctions.py +++ b/ibmsecurity/isam/web/reverse_proxy/junctions.py @@ -85,7 +85,7 @@ def _check(isamAppliance, reverseproxy_id, junctionname, currentJunctions=None): """ CurrentJunctions is the output of get_all. This avoids constantly having to call the get_all function. """ - if currentJunctions is not None: + if currentJunctions is None: ret_obj = get_all(isamAppliance, reverseproxy_id) else: ret_obj = currentJunctions @@ -355,7 +355,8 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ username=None, password=None, server_uuid=None, local_ip=None, ltpa_keyfile_password=None, delegation_support=None, scripting_support=None, insert_ltpa_cookies=None, check_mode=False, force=False, http2_junction=None, http2_proxy=None, sni_name=None, description=None, - priority=None, server_cn=None, silent=None, servers=None): + priority=None, server_cn=None, silent=None, servers=None + ): """ Setting a standard or virtual junction - compares with existing junction and replaces if changes are detected TODO: Compare all the parameters in the function - LTPA, BA are some that are not being compared diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions_server.py b/ibmsecurity/isam/web/reverse_proxy/junctions_server.py index 2ae54bee..445aab0e 100644 --- a/ibmsecurity/isam/web/reverse_proxy/junctions_server.py +++ b/ibmsecurity/isam/web/reverse_proxy/junctions_server.py @@ -8,6 +8,23 @@ uri = "/wga/reverseproxy" +server_fields = {'server_hostname': {'type': 'string'}, + 'server_port': {'type': 'number'}, + 'case_sensitive_url': {'type': 'yesno', 'max_version': "10.0.6.0"}, + 'case_insensitive_url': {'type': 'yesno', 'min_version': "10.0.6.0"}, + 'http_port': {'type': 'number'}, + 'local_ip': {'type': 'string'}, + 'query_contents': {'type': 'string', 'alt_name': 'query_content_url'}, + 'server_dn': {'type': 'string'}, + 'server_uuid': {'type': 'ignore'}, + 'virtual_hostname': {'type': 'string', 'alt_name': 'virtual_junction_hostname'}, + 'windows_style_url': {'type': 'yesno'}, + 'current_requests': {'type': 'ignore'}, + 'total_requests': {'type': 'ignore'}, + 'operation_state': {'type': 'ignore'}, + 'server_state': {'type': 'ignore'}, + 'priority': {'type': 'number', 'min_version': "10.0.2.0"}, + 'server_cn': {'type': 'string', 'min_version': "10.0.2.0"}} def search(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port): ret_obj_new = isamAppliance.create_return_object() @@ -30,7 +47,7 @@ def get(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ break return ret_obj_new -def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, junction_type="tcp", check_mode=False, force=False, +def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, junction_type="tcp", check_mode=False, force=False, warnings=[], **optionargs): """ Adding a back-end server to an existing standard or virtual junctions @@ -93,6 +110,7 @@ def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ jct_srv_json["case_insensitive_url"] = 'yes' else: jct_srv_json["case_insensitive_url"] = 'no' # default + jct_srv_json.pop("case_sensitive_url", None) else: jct_srv_json.pop("case_insensitive_url", None) @@ -171,24 +189,6 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ exist_jct.pop('server_state', None) exist_jct.pop('query_contents', None) - server_fields = {'server_hostname': {'type': 'string'}, - 'server_port': {'type': 'number'}, - 'case_sensitive_url': {'type': 'yesno', 'max_version': "10.0.6.0"}, - 'case_insensitive_url': {'type': 'yesno', 'min_version': "10.0.6.0"}, - 'http_port': {'type': 'number'}, - 'local_ip': {'type': 'string'}, - 'query_contents': {'type': 'string', 'alt_name': 'query_content_url'}, - 'server_dn': {'type': 'string'}, - 'server_uuid': {'type': 'ignore'}, - 'virtual_hostname': {'type': 'string', 'alt_name': 'virtual_junction_hostname'}, - 'windows_style_url': {'type': 'yesno'}, - 'current_requests': {'type': 'ignore'}, - 'total_requests': {'type': 'ignore'}, - 'operation_state': {'type': 'ignore'}, - 'server_state': {'type': 'ignore'}, - 'priority': {'type': 'number', 'min_version': "10.0.2.0"}, - 'server_cn': {'type': 'string', 'min_version': "10.0.2.0"}} - for _k, _v in optionargs.items(): # only keep valid arguments if _k in list(server_fields.keys()): From 0a979a2c45717b1401a71348c5ee9b836d827618 Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Tue, 6 Feb 2024 16:08:42 +0100 Subject: [PATCH 05/13] fix: avoid auth problems in lmi (lachlan) --- ibmsecurity/appliance/isamappliance.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ibmsecurity/appliance/isamappliance.py b/ibmsecurity/appliance/isamappliance.py index 97149a1d..09a64a9b 100644 --- a/ibmsecurity/appliance/isamappliance.py +++ b/ibmsecurity/appliance/isamappliance.py @@ -309,6 +309,8 @@ def _invoke_request(self, func, description, uri, ignore_error, data={}, require used directly. The invoke_get/invoke_put/etc functions should be used instead. """ self._log_desc(description=description) + self.session.cookies.pop('LtpaToken2', None) + self.session.cookies.pop('JSESSIONID', None) warnings, return_call = self._process_warnings(uri=uri, requires_modules=requires_modules, requires_version=requires_version, requires_model=requires_model, From e2d3e72759fb94a86d061ed3ffeb7024417dc659 Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Fri, 9 Feb 2024 17:24:18 +0100 Subject: [PATCH 06/13] fix: update checks --- .../isam/web/reverse_proxy/junctions.py | 228 ++++++++++++------ .../web/reverse_proxy/junctions_server.py | 9 +- 2 files changed, 162 insertions(+), 75 deletions(-) diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions.py b/ibmsecurity/isam/web/reverse_proxy/junctions.py index fd4972f0..9b784da5 100644 --- a/ibmsecurity/isam/web/reverse_proxy/junctions.py +++ b/ibmsecurity/isam/web/reverse_proxy/junctions.py @@ -8,14 +8,34 @@ except NameError: basestring = (str, bytes) +# does not work anymore in Python 3.11+ logger = logging.getLogger(__name__) + # URI for this module uri = "/wga/reverseproxy" requires_modules = ["wga"] requires_version = None -def get_all(isamAppliance, reverseproxy_id, detailed=None, check_mode=False, force=False): +server_fields = {'server_hostname': {'type': 'string'}, + 'server_port': {'type': 'number'}, + 'case_sensitive_url': {'type': 'yesno', 'max_version': "10.0.6.0"}, + 'case_insensitive_url': {'type': 'yesno', 'min_version': "10.0.6.0"}, + 'http_port': {'type': 'number'}, + 'local_ip': {'type': 'string'}, + 'query_contents': {'type': 'string', 'alt_name': 'query_content_url'}, + 'server_dn': {'type': 'string'}, + 'server_uuid': {'type': 'ignore'}, + 'virtual_hostname': {'type': 'string', 'alt_name': 'virtual_junction_hostname'}, + 'windows_style_url': {'type': 'yesno'}, + 'current_requests': {'type': 'ignore'}, + 'total_requests': {'type': 'ignore'}, + 'operation_state': {'type': 'ignore'}, + 'server_state': {'type': 'ignore'}, + 'priority': {'type': 'number', 'min_version': "10.0.2.0"}, + 'server_cn': {'type': 'string', 'min_version': "10.0.2.0"}} + +def get_all(isamAppliance, reverseproxy_id, detailed=None, check_mode=False, force=False, warnings=[]): """ Retrieving a list of standard and virtual junctions @@ -26,22 +46,32 @@ def get_all(isamAppliance, reverseproxy_id, detailed=None, check_mode=False, for :param force: :return: """ + logger = isamAppliance.logger if detailed and tools.version_compare(isamAppliance.facts["version"], "10.0.4") >= 0: - logger.debug("DETAILED") - return isamAppliance.invoke_get("Retrieving a list of standard and virtual junctions", + try: + returnValue = isamAppliance.invoke_get("Retrieving a list of standard and virtual junctions", "{0}/{1}/junctions?detailed=true".format(uri, reverseproxy_id), requires_modules=requires_modules, requires_version=requires_version) + except: + warnings.append("Detailed retrieval of junctions failed unexpectedly. Falling back to simple version.") + returnValue = isamAppliance.invoke_get("Retrieving a list of standard and virtual junctions (fallback)", + "{0}/{1}/junctions".format(uri, reverseproxy_id), + requires_modules=requires_modules, + requires_version=requires_version, + warnings=warnings + ) + returnValue['warnings'] = warnings + return returnValue else: #ignore detailed for older versions - logger.debug("IGNORE DETAILED") return isamAppliance.invoke_get("Retrieving a list of standard and virtual junctions", "{0}/{1}/junctions".format(uri, reverseproxy_id), requires_modules=requires_modules, requires_version=requires_version) -def get(isamAppliance, reverseproxy_id, junctionname, check_mode=False, force=False): +def get(isamAppliance, reverseproxy_id, junctionname, check_mode=False, force=False, warnings=[]): """ Retrieving the parameters for a single standard or virtual junction @@ -52,18 +82,19 @@ def get(isamAppliance, reverseproxy_id, junctionname, check_mode=False, force=Fa :param force: :return: """ + logger = isamAppliance.logger ret_obj = isamAppliance.invoke_get("Retrieving the parameters for a single standard or virtual junction", "{0}/{1}/junctions?junctions_id={2}".format(uri, reverseproxy_id, junctionname), requires_modules=requires_modules, - requires_version=requires_version) + requires_version=requires_version, + warnings=warnings) # servers are provided as a single string, here we parse it out into a list + dict servers = [] if tools.version_compare(isamAppliance.facts["version"], "9.0.1.0") > 0: srv_separator = '#' else: srv_separator = '&' - logger.debug("Server Separator being used: {0}".format(srv_separator)) srvs = ret_obj['data']['servers'].split(srv_separator) logger.debug("Servers in raw string: {0}".format(ret_obj['data']['servers'])) logger.debug("Number of servers in junction: {0}".format(len(srvs))) @@ -77,7 +108,6 @@ def get(isamAppliance, reverseproxy_id, junctionname, check_mode=False, force=Fa servers.append(server) ret_obj['data']['servers'] = servers - return ret_obj @@ -109,6 +139,8 @@ def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ delegation_support=None, scripting_support=None, insert_ltpa_cookies=None, check_mode=False, force=False, http2_junction=None, http2_proxy=None, sni_name=None, description=None, priority=None, server_cn=None, silent=None, + case_insensitive_url=None, + servers=None, warnings=[]): """ Creating a standard or virtual junction @@ -124,6 +156,7 @@ def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ :param query_contents: :param stateful_junction: :param case_sensitive_url: + :param case_insensitive_url: #v10.0.6 :param windows_style_url: :param https_port: :param http_port: @@ -172,6 +205,7 @@ def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ """ # See if it's a virtual or standard junction isVirtualJunction = True + logger = isamAppliance.logger if junction_point[:1] == '/': isVirtualJunction = False if force is True or _check(isamAppliance, reverseproxy_id, junction_point) is False: @@ -181,7 +215,7 @@ def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ # Create a simple json with just the main junction attributes jct_json = { "junction_point": junction_point, - "junction_type": junction_type, + "junction_type": junction_type.lower(), "server_hostname": server_hostname, "server_port": server_port, "force": force @@ -245,8 +279,18 @@ def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ jct_json["local_ip"] = local_ip if query_contents is not None: jct_json["query_contents"] = query_contents - if case_sensitive_url is not None: - jct_json["case_sensitive_url"] = case_sensitive_url + if tools.version_compare(isamAppliance.facts["version"], "10.0.6.0") >= 0: + # If no case_insensitive_url is passed, we take the old one and invert it. + # Who thinks it's a good idea to make changes in an API like this ? + if case_insensitive_url is not None: + jct_json["case_insensitive_url"] = case_insensitive_url + elif case_sensitive_url is not None: + if case_sensitive_url.lower() == 'no': + jct_json["case_insensitive_url"] = 'yes' + else: + jct_json["case_insensitive_url"] = 'no' # default + elif case_sensitive_url is not None: + jct_json['case_sensitive_url'] = case_sensitive_url if windows_style_url is not None: jct_json["windows_style_url"] = windows_style_url if ltpa_keyfile_password is not None: @@ -355,14 +399,13 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ username=None, password=None, server_uuid=None, local_ip=None, ltpa_keyfile_password=None, delegation_support=None, scripting_support=None, insert_ltpa_cookies=None, check_mode=False, force=False, http2_junction=None, http2_proxy=None, sni_name=None, description=None, - priority=None, server_cn=None, silent=None, servers=None - ): + priority=None, server_cn=None, silent=None, case_insensitive_url=None, servers=None, warnings=[]): """ Setting a standard or virtual junction - compares with existing junction and replaces if changes are detected TODO: Compare all the parameters in the function - LTPA, BA are some that are not being compared """ - warnings = [] add_required = False + logger = isamAppliance.logger servers = None # servers is merely there to allow it as a parameter. # See if it's a virtual or standard junction isVirtualJunction = True @@ -374,7 +417,7 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ logger.debug("Check if the junction exists.") if _check(isamAppliance, reverseproxy_id, junction_point) is True: logger.debug("Junction exists. Compare junction details.") - ret_obj = get(isamAppliance, reverseproxy_id=reverseproxy_id, junctionname=junction_point) + ret_obj = get(isamAppliance, reverseproxy_id=reverseproxy_id, junctionname=junction_point, warnings=warnings) logger.debug("See if the backend junction server matches any on the junction. Look for just one match.") srvs = ret_obj['data']['servers'] @@ -397,7 +440,7 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ exist_jct = ret_obj['data'] jct_json = { 'junction_point': junction_point, - 'junction_type': junction_type.upper() + 'junction_type': junction_type.lower() } if authz_rules is None: jct_json['authz_rules'] = 'no' @@ -461,7 +504,7 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ if remote_http_header is None or remote_http_header == []: jct_json['remote_http_header'] = 'do not insert' elif isinstance(remote_http_header, basestring) and remote_http_header.lower() == 'all': - jct_json['remote_http_header'] = ['iv_creds', 'iv_groups', 'iv_user'] + jct_json['remote_http_header'] = ['iv-creds', 'iv-groups', 'iv-user'] else: jct_json['remote_http_header'] = remote_http_header # To allow for multiple header values to be sorted during compare convert retrieved data into array @@ -559,6 +602,7 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ exist_jct['fsso_config_file'] = jct_json['fsso_config_file'] if 'transparent_path_junction' not in exist_jct: exist_jct['transparent_path_junction'] = jct_json['transparent_path_junction'] + logger.debug("New Junction JSON: {0}".format(tools.json_sort(jct_json))) logger.debug("Old Junction JSON: {0}".format(tools.json_sort(exist_jct))) @@ -595,9 +639,10 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ insert_ltpa_cookies=insert_ltpa_cookies, check_mode=check_mode, force=True, http2_junction=http2_junction, http2_proxy=http2_proxy, sni_name=sni_name, description=description, priority=priority, server_cn=server_cn, silent=silent, + case_insensitive_url=case_insensitive_url, warnings=warnings) - return isamAppliance.create_return_object() + return isamAppliance.create_return_object(warnings=warnings) def compare(isamAppliance1, isamAppliance2, reverseproxy_id, reverseproxy_id2=None): @@ -650,25 +695,12 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= :param force: :return: """ + logger = isamAppliance.logger + currentJunctions = get_all(isamAppliance, reverseproxy_id=reverseproxy_id, detailed=True) - server_fields = {'server_hostname': {'type': 'string'}, - 'server_port': {'type': 'number'}, - 'case_sensitive_url': {'type': 'yesno', 'max_version': "10.0.6.0"}, - 'case_insensitive_url': {'type': 'yesno', 'min_version': "10.0.6.0"}, - 'http_port': {'type': 'number'}, - 'local_ip': {'type': 'string'}, - 'query_contents': {'type': 'string', 'alt_name': 'query_content_url'}, - 'server_dn': {'type': 'string'}, - 'server_uuid': {'type': 'ignore'}, - 'virtual_hostname': {'type': 'string', 'alt_name': 'virtual_junction_hostname'}, - 'windows_style_url': {'type': 'yesno'}, - 'current_requests': {'type': 'ignore'}, - 'total_requests': {'type': 'ignore'}, - 'operation_state': {'type': 'ignore'}, - 'server_state': {'type': 'ignore'}, - 'priority': {'type': 'number', 'min_version': "10.0.2.0"}, - 'server_cn': {'type': 'string', 'min_version': "10.0.2.0"}} + __markChanged = False # use this bool to indicate if there's been a change or not. + if currentJunctions['rc'] == 0: logger.debug(f"\nCurrent junctions:\n{currentJunctions}") @@ -677,6 +709,7 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= logger.debug("No junctions exist yet. Create them all.") # Force create - There are no junctions yet # use expansion + __markChanged = True for j in junctions: j.pop('isVirtualJunction', None) __firstserver = j.get('servers', [''])[0] @@ -690,7 +723,8 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= else: __servers = None j.pop('servers', None) - set(isamAppliance, reverseproxy_id, **j, force=True) + j['force'] = True # Force create + set(isamAppliance, reverseproxy_id, **j) # Also add servers (if servers[] has more than 1 item) if __servers is not None: for s in __servers: @@ -702,6 +736,7 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= for j in junctions: #result = next((item for item in currentJunctions if item["junction_name"] == j["junction_name"]), None) logger.debug(f"Processing junction: {j['junction_point']}") + _checkUpdate = False j['isVirtualJunction'] = True if j['junction_point'][:1] == '/': @@ -714,6 +749,12 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= j['junction_hard_limit'] = '0 - using global value' else: j['junction_hard_limit'] = str( j.get('junction_hard_limit', None)) + if j.get('client_ip_http', None) is None or j.get('client_ip_http', '').lower() == 'no': + j['client_ip_http'] = 'do not insert' + elif j.get('client_ip_http', '').lower() == 'yes': + j['client_ip_http'] = 'insert' + if j.get('junction_type', None) is not None: + j['junction_type'] = j.get('junction_type', '').lower() # if junction_type is empty, rest api will fail anyway # convenience. # check that we have the required fields, if not, get them from the first server (if that exists) @@ -733,38 +774,61 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= else: srv_separator = '&' - if c.get('servers', None) is not None: - if type(c.get('servers',[])) is str: - srvs = c['servers'].split(srv_separator) - logger.debug("Servers in raw string: {0}".format(c['servers'])) - logger.debug("Number of servers in junction: {0}".format(len(srvs))) - for srv in srvs: - logger.debug("Parsing Server: {0}".format(srv)) - server = {} - for s in srv.split(';'): - if s != '': - kv = s.split('!') - server[kv[0]] = kv[1] - servers.append(server) - c['servers'] = servers - else: - c['servers'] = [] + if c.get('junction_point', None) == j['junction_point']: + if c.get('servers', None) is not None: + if type(c.get('servers', [])) is str: + srvs = c['servers'].split(srv_separator) + logger.debug("Servers in raw string: {0}".format(c['servers'])) + logger.debug("Number of servers in junction: {0}".format(len(srvs))) + for srv in srvs: + logger.debug("Parsing Server: {0}".format(srv)) + server = {} + for s in srv.split(';'): + if s != '': + kv = s.split('!') + server[kv[0]] = kv[1] + servers.append(server) + c['servers'] = servers + else: + c['servers'] = [] - if c['junction_point'] == j['junction_point']: logger.debug(f"The junction at {j['junction_point']} already exists.") - _checkUpdate = c + _checkUpdate = dict(c) + break + elif c.get('id', None) == j['junction_point']: + logger.debug(f"The junction at {j['junction_point']} already exists (simple syntax)") + logger.debug(f"Retrieve the details for this junction.") + warnings.append(f"Had to use simple get syntax unexpectedly for {j['junction_point']}") + _checkUpdate = get(isamAppliance, reverseproxy_id, j['junction_point'], check_mode=False, force=False, warnings=warnings) + _checkUpdate = _checkUpdate.get('data', _checkUpdate) + if _checkUpdate.get('servers', None) is not None: + if type(_checkUpdate.get('servers', [])) is str: + srvs = _checkUpdate['servers'].split(srv_separator) + logger.debug("Servers in raw string: {0}".format(_checkUpdate['servers'])) + logger.debug("Number of servers in junction: {0}".format(len(srvs))) + for srv in srvs: + logger.debug("Parsing Server: {0}".format(srv)) + server = {} + for s in srv.split(';'): + if s != '': + kv = s.split('!') + server[kv[0]] = kv[1] + servers.append(server) + _checkUpdate['servers'] = servers + else: + _checkUpdate['servers'] = [] break if _checkUpdate: # perform a comparison. we receive the actual current junction as input here - srvs = c['servers'] + srvs = _checkUpdate.get('servers', None) logger.debug(f"\n\nServers in junction {j['junction_point']}:\n{srvs}") add_required = False - if not junction_server_exists(isamAppliance, srvs, **j): + if srvs is not None and not junction_server_exists(isamAppliance, srvs, **j): add_required = True if not add_required: # ok we still need to compare - exist_jct = c + exist_jct = dict(_checkUpdate) j.pop('isVirtualJunction', None) new_jct = dict(j) @@ -775,7 +839,11 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= new_jct.pop(_field, None) # remove the servers for the comparison new_jct.pop('servers', None) + new_jct.pop('force', None) exist_jct.pop('servers', None) + # junction_type + if exist_jct.get('junction_type', None) is not None: + exist_jct['junction_type'] = exist_jct.get('junction_type', '').lower() #insert_ltpa_cookies if exist_jct.get('insert_ltpa_cookies', None) is None: exist_jct['insert_ltpa_cookies'] = 'no' @@ -798,33 +866,44 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= exist_jct['request_encoding'] = 'lcp_uri' # To allow for multiple header values to be sorted during compare convert retrieved data into array - if exist_jct['remote_http_header'].startswith('insert - '): - exist_jct['remote_http_header'] = [_word.replace('_','-') for _word in (exist_jct['remote_http_header'][9:]).split(' ')] - elif exist_jct['remote_http_header'] == 'do not insert': - exist_jct['remote_http_header'] = [] - exist_jct['junction_type'] = exist_jct['junction_type'].lower() + __rehh = exist_jct.get('remote_http_header', None) + if __rehh is not None: + if __rehh.startswith('insert - '): + exist_jct['remote_http_header'] = [_word.replace('_','-') for _word in (__rehh[9:]).split(' ')] + elif exist_jct['remote_http_header'] == 'do not insert': + exist_jct['remote_http_header'] = [] + + # basic_auth_mode - filter (default), ignore, supply, gso. + __bamode = exist_jct.get('basic_auth_mode', None) + if __bamode is not None: + # GSO + if __bamode == "use GSO": + exist_jct['basic_auth_mode'] = 'gso' + # only compare values that are in the new request - # This may give unexpected results. + # This may lead to unexpected results...although I can't think of any exist_jct = {k: v for k, v in exist_jct.items() if k in j.keys()} newJSON = json.dumps(new_jct, skipkeys=True, sort_keys=True) logger.debug(f"\nSorted Desired Junction {j['junction_point']}:\n\n {newJSON}\n") oldJSON = json.dumps(exist_jct, skipkeys=True, sort_keys=True) - logger.debug(f"\nSorted Current Junction {c['junction_point']}:\n\n {oldJSON}\n") + logger.debug(f"\nSorted Current Junction {exist_jct.get('junction_point', '')}:\n\n {oldJSON}\n") if newJSON != oldJSON: logger.debug("Junctions are found to be different. See JSON for difference.") add_required = True if add_required: + __markChanged = True # Run set() logger.debug("\n\nUpdate junction\n\n") - - set(isamAppliance, reverseproxy_id, **j, force=True) + warnings.append(f"Updating junction {j['junction_point']}") + j['force'] = True # force create + j['warnings'] = warnings + __result = set(isamAppliance, reverseproxy_id, **j) logger.debug(f"Adding servers") # Also add servers (if servers[] has more than 1 item) if len(j.get('servers', [''])) > 1: - j.pop('servers', None) for s in __servers: for _field, kval in server_fields.items(): @@ -832,12 +911,15 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= j[_field] = s.get(_field, None) junctions_server.set(isamAppliance, reverseproxy_id, **j) else: - logger.debug(f"\n\nJunction {j.get('junction_point','')}does not need updating\n\n") - warnings.append(f"Junction {j.get('junction_point','')}does not need updating") + logger.debug(f"\n\nJunction {j.get('junction_point','')} does not need updating\n\n") + warnings.append(f"Junction {j.get('junction_point','')} does not need updating") else: # Force create - this junction does not exist yet j.pop('isVirtualJunction', None) + j['force'] = True # force create + __markChanged = True + j['warnings'] = warnings __firstserver = j.get('servers', [''])[0] for _field in list(server_fields.keys()): if __firstserver.get(_field, None) is not None and j.get(_field, None) is None: @@ -845,7 +927,7 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= logger.debug(f"{_field} from {__firstserver.get('server_hostname')} copied to junction level") j[_field] = __firstserver.get(_field, None) logger.debug(f"Creating new junction with {j}") - set(isamAppliance, reverseproxy_id, **j, force=True) + set(isamAppliance, reverseproxy_id, **j) logger.debug(f"Adding servers") # Also add servers (if servers[] has more than 1 item) if len(j.get('servers', [''])) > 1: @@ -855,7 +937,11 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= for _field, kval in server_fields.items(): if s.get(_field, None) is not None: j[_field] = s.get(_field, None) - junctions_server.set(isamAppliance, reverseproxy_id, **j) + return junctions_server.set(isamAppliance, reverseproxy_id, **j) + if __markChanged: + return isamAppliance.create_return_object(changed=True, warnings=warnings) + else: + return isamAppliance.create_return_object(warnings=warnings) #def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, junction_type="tcp", check_mode=False, force=False, warnings=[], **optionargs): def junction_server_exists(isamAppliance, srvs, server_hostname: str, server_port, case_sensitive_url='no', diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions_server.py b/ibmsecurity/isam/web/reverse_proxy/junctions_server.py index 445aab0e..9a2885d4 100644 --- a/ibmsecurity/isam/web/reverse_proxy/junctions_server.py +++ b/ibmsecurity/isam/web/reverse_proxy/junctions_server.py @@ -47,7 +47,7 @@ def get(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ break return ret_obj_new -def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, junction_type="tcp", check_mode=False, force=False, warnings=[], +def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, junction_type="tcp", check_mode=False, force=False, **optionargs): """ Adding a back-end server to an existing standard or virtual junctions @@ -64,6 +64,7 @@ def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ :param query_contents: :param stateful_junction: :param case_sensitive_url: + :param case_insensitive_url: #v1.0.6+ :param windows_style_url: :param https_port: :param http_port: @@ -79,10 +80,10 @@ def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ :return: """ # Search for the UUID of the junctioned server - if force is False: + if not force: ret_obj = search(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port) - if force is True or ret_obj['data'] == {}: + if force or ret_obj['data'] == {}: if check_mode is True: return isamAppliance.create_return_object(changed=True) else: @@ -90,7 +91,7 @@ def add(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ "junction_point": junction_point, "junction_type": junction_type, "server_hostname": server_hostname, - "server_port": server_port, + "server_port": server_port } for _k, _v in optionargs.items(): if _v is not None: From d07cf3fa83ebb46231acca7e170f6a7aaac7aa1c Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Mon, 12 Feb 2024 09:15:37 +0100 Subject: [PATCH 07/13] fix: remove duplicate pop lines --- ibmsecurity/isam/web/reverse_proxy/junctions.py | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions.py b/ibmsecurity/isam/web/reverse_proxy/junctions.py index 9b784da5..be9c6f93 100644 --- a/ibmsecurity/isam/web/reverse_proxy/junctions.py +++ b/ibmsecurity/isam/web/reverse_proxy/junctions.py @@ -734,7 +734,6 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= junctions_server.set(isamAppliance, reverseproxy_id, **j) # Compare the junctions and the currentJunctions. for j in junctions: - #result = next((item for item in currentJunctions if item["junction_name"] == j["junction_name"]), None) logger.debug(f"Processing junction: {j['junction_point']}") _checkUpdate = False @@ -831,16 +830,12 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= exist_jct = dict(_checkUpdate) j.pop('isVirtualJunction', None) new_jct = dict(j) - - exist_jct.pop('active_worker_threads', None) # remove the server fields - this has already been compared for _field, kval in server_fields.items(): - exist_jct.pop(_field, None) new_jct.pop(_field, None) # remove the servers for the comparison new_jct.pop('servers', None) new_jct.pop('force', None) - exist_jct.pop('servers', None) # junction_type if exist_jct.get('junction_type', None) is not None: exist_jct['junction_type'] = exist_jct.get('junction_type', '').lower() @@ -881,8 +876,8 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= exist_jct['basic_auth_mode'] = 'gso' # only compare values that are in the new request - # This may lead to unexpected results...although I can't think of any - exist_jct = {k: v for k, v in exist_jct.items() if k in j.keys()} + # This does not (always) compare values correctly where you just remove the key. In that case, you'd have to change a different key as well (eg. description) + exist_jct = {k: v for k, v in exist_jct.items() if k in new_jct.keys()} newJSON = json.dumps(new_jct, skipkeys=True, sort_keys=True) logger.debug(f"\nSorted Desired Junction {j['junction_point']}:\n\n {newJSON}\n") @@ -942,7 +937,7 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= return isamAppliance.create_return_object(changed=True, warnings=warnings) else: return isamAppliance.create_return_object(warnings=warnings) -#def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, junction_type="tcp", check_mode=False, force=False, warnings=[], **optionargs): + def junction_server_exists(isamAppliance, srvs, server_hostname: str, server_port, case_sensitive_url='no', isVirtualJunction=False, http_port=None, local_ip=None, From 4b08ab89721a35aefe6528099bd403833ac5d3c0 Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Tue, 20 Feb 2024 09:25:47 +0100 Subject: [PATCH 08/13] fix: issue #410 --- .../isam/web/runtime/federated_directories/stanza.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ibmsecurity/isam/web/runtime/federated_directories/stanza.py b/ibmsecurity/isam/web/runtime/federated_directories/stanza.py index 35d76b02..2a3c45f1 100644 --- a/ibmsecurity/isam/web/runtime/federated_directories/stanza.py +++ b/ibmsecurity/isam/web/runtime/federated_directories/stanza.py @@ -67,7 +67,7 @@ def add(isamAppliance, id, hostname, port, bind_dn, bind_pwd, suffix, use_ssl=Fa 'suffix': suffix } - if ignore_if_down and tools.version_compare(isamAppliance.facts["version"], "10.0.4") >= 0: + if tools.version_compare(isamAppliance.facts["version"], "10.0.4") >= 0: json_data['ignore_if_down'] = ignore_if_down # Do not pass if there is no value - call fails otherwise @@ -86,11 +86,11 @@ def update(isamAppliance, id, hostname, port, bind_dn, bind_pwd, suffix, use_ssl """ Update an existing federated directory """ - if force is True or ( + if force or ( _exists(isamAppliance, id) and _check(isamAppliance, id, hostname, port, bind_dn, bind_pwd, use_ssl, client_cert_label, suffix, ignore_if_down) is False): - if check_mode is True: + if check_mode: return isamAppliance.create_return_object(changed=True) else: json_data = { @@ -101,7 +101,7 @@ def update(isamAppliance, id, hostname, port, bind_dn, bind_pwd, suffix, use_ssl 'use_ssl': use_ssl, 'suffix': suffix } - if ignore_if_down and tools.version_compare(isamAppliance.facts["version"], "10.0.4") >= 0: + if tools.version_compare(isamAppliance.facts["version"], "10.0.4") >= 0: json_data['ignore_if_down'] = ignore_if_down # Do not pass if there is no value - call fails otherwise if client_cert_label is not None: @@ -170,7 +170,7 @@ def _check(isamAppliance, id, hostname, port, bind_dn, bind_pwd, use_ssl, client } if use_ssl is True: set_value['client_cert_label'] = client_cert_label - if ignore_if_down and tools.version_compare(isamAppliance.facts["version"], "10.0.4") >= 0: + if tools.version_compare(isamAppliance.facts["version"], "10.0.4") >= 0: set_value['ignore_if_down'] = ignore_if_down newEntriesJSON = json.dumps(set_value, skipkeys=True, sort_keys=True) From 588ce52cb94128215cf5df10aedd31b129b7b38f Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Fri, 23 Feb 2024 11:15:58 +0100 Subject: [PATCH 09/13] feature: contains a variable to share between junctions.py and junctions_server.py, to avoid circular imports --- .../web/reverse_proxy/junctions_config.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 ibmsecurity/isam/web/reverse_proxy/junctions_config.py diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions_config.py b/ibmsecurity/isam/web/reverse_proxy/junctions_config.py new file mode 100644 index 00000000..c2d0a551 --- /dev/null +++ b/ibmsecurity/isam/web/reverse_proxy/junctions_config.py @@ -0,0 +1,19 @@ +# some shared variables. +# Quick & dirty +server_fields = {'server_hostname': {'type': 'string'}, + 'server_port': {'type': 'number'}, + 'case_sensitive_url': {'type': 'yesno', 'max_version': "10.0.6.0"}, + 'case_insensitive_url': {'type': 'yesno', 'min_version': "10.0.6.0"}, + 'http_port': {'type': 'number'}, + 'local_ip': {'type': 'string'}, + 'query_contents': {'type': 'string', 'alt_name': 'query_content_url'}, + 'server_dn': {'type': 'string'}, + 'server_uuid': {'type': 'ignore'}, + 'virtual_hostname': {'type': 'string', 'alt_name': 'virtual_junction_hostname'}, + 'windows_style_url': {'type': 'yesno'}, + 'current_requests': {'type': 'ignore'}, + 'total_requests': {'type': 'ignore'}, + 'operation_state': {'type': 'ignore'}, + 'server_state': {'type': 'ignore'}, + 'priority': {'type': 'number', 'min_version': "10.0.2.0"}, + 'server_cn': {'type': 'string', 'min_version': "10.0.2.0"}} \ No newline at end of file From 1346c3eae3ebc834e6b50e23eda798572f33328c Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Fri, 23 Feb 2024 11:17:50 +0100 Subject: [PATCH 10/13] feature: corrections in comparing existing junctions --- .../isam/web/reverse_proxy/junctions.py | 290 +++++++++--------- 1 file changed, 138 insertions(+), 152 deletions(-) diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions.py b/ibmsecurity/isam/web/reverse_proxy/junctions.py index be9c6f93..9c7a2fc1 100644 --- a/ibmsecurity/isam/web/reverse_proxy/junctions.py +++ b/ibmsecurity/isam/web/reverse_proxy/junctions.py @@ -2,6 +2,8 @@ from ibmsecurity.utilities import tools import ibmsecurity.isam.web.reverse_proxy.junctions_server as junctions_server import json +from ibmsecurity.isam.web.reverse_proxy.junctions_config import server_fields +from ibmsecurity.utilities.tools import jsonSortedListEncoder try: basestring @@ -17,23 +19,7 @@ requires_modules = ["wga"] requires_version = None -server_fields = {'server_hostname': {'type': 'string'}, - 'server_port': {'type': 'number'}, - 'case_sensitive_url': {'type': 'yesno', 'max_version': "10.0.6.0"}, - 'case_insensitive_url': {'type': 'yesno', 'min_version': "10.0.6.0"}, - 'http_port': {'type': 'number'}, - 'local_ip': {'type': 'string'}, - 'query_contents': {'type': 'string', 'alt_name': 'query_content_url'}, - 'server_dn': {'type': 'string'}, - 'server_uuid': {'type': 'ignore'}, - 'virtual_hostname': {'type': 'string', 'alt_name': 'virtual_junction_hostname'}, - 'windows_style_url': {'type': 'yesno'}, - 'current_requests': {'type': 'ignore'}, - 'total_requests': {'type': 'ignore'}, - 'operation_state': {'type': 'ignore'}, - 'server_state': {'type': 'ignore'}, - 'priority': {'type': 'number', 'min_version': "10.0.2.0"}, - 'server_cn': {'type': 'string', 'min_version': "10.0.2.0"}} + def get_all(isamAppliance, reverseproxy_id, detailed=None, check_mode=False, force=False, warnings=[]): """ @@ -46,7 +32,6 @@ def get_all(isamAppliance, reverseproxy_id, detailed=None, check_mode=False, for :param force: :return: """ - logger = isamAppliance.logger if detailed and tools.version_compare(isamAppliance.facts["version"], "10.0.4") >= 0: try: returnValue = isamAppliance.invoke_get("Retrieving a list of standard and virtual junctions", @@ -388,13 +373,13 @@ def delete(isamAppliance, reverseproxy_id, junctionname, check_mode=False, force return isamAppliance.create_return_object() -def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, junction_type="tcp", +def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port, junction_type: str="tcp", virtual_hostname=None, server_dn=None, query_contents=None, stateful_junction=None, case_sensitive_url=None, windows_style_url=None, https_port=None, http_port=None, proxy_hostname=None, proxy_port=None, sms_environment=None, vhost_label=None, junction_hard_limit=None, junction_soft_limit=None, basic_auth_mode=None, tfim_sso=None, remote_http_header=None, preserve_cookie=None, cookie_include_path=None, transparent_path_junction=None, mutual_auth=None, insert_session_cookies=None, request_encoding=None, - enable_basic_auth=None, key_label=None, gso_resource_group=None, junction_cookie_javascript_block=None, + enable_basic_auth=None, key_label=None, gso_resource_group=None, junction_cookie_javascript_block: str=None, client_ip_http=None, version_two_cookies=None, ltpa_keyfile=None, authz_rules=None, fsso_config_file=None, username=None, password=None, server_uuid=None, local_ip=None, ltpa_keyfile_password=None, delegation_support=None, scripting_support=None, insert_ltpa_cookies=None, check_mode=False, force=False, @@ -402,11 +387,9 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ priority=None, server_cn=None, silent=None, case_insensitive_url=None, servers=None, warnings=[]): """ Setting a standard or virtual junction - compares with existing junction and replaces if changes are detected - TODO: Compare all the parameters in the function - LTPA, BA are some that are not being compared """ add_required = False logger = isamAppliance.logger - servers = None # servers is merely there to allow it as a parameter. # See if it's a virtual or standard junction isVirtualJunction = True if junction_point[:1] == '/': @@ -484,14 +467,11 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ jct_json['junction_soft_limit'] = '0 - using global value' else: jct_json['junction_soft_limit'] = str(junction_soft_limit) - # We could have a comma delimited set of values - so split them into array - if junction_cookie_javascript_block is not None and junction_cookie_javascript_block != '': - jct_json['junction_cookie_javascript_block'] = junction_cookie_javascript_block.split(',') - # Here the list is delimited by space - if 'junction_cookie_javascript_block' in exist_jct and exist_jct[ - 'junction_cookie_javascript_block'] is not None: - exist_jct['junction_cookie_javascript_block'] = exist_jct[ - 'junction_cookie_javascript_block'].split(' ') + # We could have a comma delimited set of values - so split them into array # TODO: NO, we don't. This is a string. + #if junction_cookie_javascript_block is not None and junction_cookie_javascript_block != '': + # jct_json['junction_cookie_javascript_block'] = junction_cookie_javascript_block.split(',') + + if mutual_auth is None: jct_json['mutual_auth'] = 'no' else: @@ -501,15 +481,15 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ jct_json['preserve_cookie'] = 'no' else: jct_json['preserve_cookie'] = preserve_cookie + if remote_http_header is None or remote_http_header == []: jct_json['remote_http_header'] = 'do not insert' - elif isinstance(remote_http_header, basestring) and remote_http_header.lower() == 'all': - jct_json['remote_http_header'] = ['iv-creds', 'iv-groups', 'iv-user'] else: - jct_json['remote_http_header'] = remote_http_header + jct_json['remote_http_header'] = [_word.replace('_', '-') for _word in + list(remote_http_header)] # To allow for multiple header values to be sorted during compare convert retrieved data into array - if exist_jct['remote_http_header'].startswith('insert - '): - exist_jct['remote_http_header'] = [_word.replace('_','-') for _word in (exist_jct['remote_http_header'][9:]).split(' ')] + #if exist_jct['remote_http_header'].startswith('insert - '): + # exist_jct['remote_http_header'] = [_word.replace('_','-') for _word in (exist_jct['remote_http_header'][9:]).split(' ')] if request_encoding is None: jct_json['request_encoding'] = 'UTF-8, URI Encoded' else: @@ -533,7 +513,6 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ if isVirtualJunction: logger.debug("Only for virtual junctions - virtual hostname {0}.".format(virtual_hostname)) - if virtual_hostname: jct_json['virtual_junction_hostname'] = virtual_hostname else: @@ -549,8 +528,8 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ http2_junction = None else: jct_json['http2_junction'] = http2_junction - if 'http2_junction' not in exist_jct: - exist_jct['http2_junction'] = jct_json['http2_junction'] + #if 'http2_junction' not in exist_jct: + # exist_jct['http2_junction'] = jct_json['http2_junction'] if http2_proxy is not None and http2_proxy != "no": if tools.version_compare(isamAppliance.facts["version"], "9.0.4.0") < 0: warnings.append( @@ -559,8 +538,8 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ http2_proxy = None else: jct_json['http2_proxy'] = http2_proxy - if 'http2_proxy' not in exist_jct: - exist_jct['http2_proxy'] = jct_json['http2_proxy'] + #if 'http2_proxy' not in exist_jct: + # exist_jct['http2_proxy'] = jct_json['http2_proxy'] if sni_name is not None: if tools.version_compare(isamAppliance.facts["version"], "9.0.4.0") < 0: warnings.append( @@ -579,36 +558,40 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ jct_json['description'] = description if isVirtualJunction and silent: jct_json['silent'] = silent - if 'silent' not in exist_jct: - exist_jct['silent'] = silent + # if 'silent' not in exist_jct: + # exist_jct['silent'] = silent # TODO: Not sure of how to match following attributes! Need to revisit. # TODO: Not all function parameters are being checked - need to add! - del exist_jct['boolean_rule_header'] - del exist_jct['forms_based_sso'] - del exist_jct['http_header_ident'] - del exist_jct['session_cookie_backend_portal'] + #del exist_jct['boolean_rule_header'] + #del exist_jct['forms_based_sso'] + #del exist_jct['http_header_ident'] + #del exist_jct['session_cookie_backend_portal'] # We are already comparing server details - so remove this from this compare - del exist_jct['servers'] + #del exist_jct['servers'] # Delete dynamic data shown when we get junctions details - del exist_jct['active_worker_threads'] + #del exist_jct['active_worker_threads'] # Missing cookie_include_path in existing json - if not isVirtualJunction and 'cookie_include_path' not in exist_jct: - exist_jct['cookie_include_path'] = jct_json['cookie_include_path'] - if not isVirtualJunction and 'preserve_cookie' not in exist_jct: - exist_jct['preserve_cookie'] = jct_json['preserve_cookie'] - if 'scripting_support' not in exist_jct: - exist_jct['scripting_support'] = jct_json['scripting_support'] - if 'fsso_config_file' not in exist_jct: - exist_jct['fsso_config_file'] = jct_json['fsso_config_file'] - if 'transparent_path_junction' not in exist_jct: - exist_jct['transparent_path_junction'] = jct_json['transparent_path_junction'] - - logger.debug("New Junction JSON: {0}".format(tools.json_sort(jct_json))) - logger.debug("Old Junction JSON: {0}".format(tools.json_sort(exist_jct))) - - if tools.json_sort(jct_json) != tools.json_sort(exist_jct): - logger.debug("Junctions are found to be different. See following JSON for difference.") - add_required = True + #if not isVirtualJunction and 'cookie_include_path' not in exist_jct: + # exist_jct['cookie_include_path'] = jct_json['cookie_include_path'] + #if not isVirtualJunction and 'preserve_cookie' not in exist_jct: + # exist_jct['preserve_cookie'] = jct_json['preserve_cookie'] + #if 'scripting_support' not in exist_jct: + # exist_jct['scripting_support'] = jct_json['scripting_support'] + #if 'fsso_config_file' not in exist_jct: + # exist_jct['fsso_config_file'] = jct_json['fsso_config_file'] + #if 'transparent_path_junction' not in exist_jct: + # exist_jct['transparent_path_junction'] = jct_json['transparent_path_junction'] + + #logger.debug("New Junction JSON: {0}".format(tools.json_sort(jct_json))) + #logger.debug("Old Junction JSON: {0}".format(tools.json_sort(exist_jct))) + + #if tools.json_sort(jct_json) != tools.json_sort(exist_jct): + # logger.debug("Junctions are found to be different. See following JSON for difference.") + # add_required = True + if junction_exists(isamAppliance, exist_jct, jct_json): + add_required = False + else: + add_required = True if add_required and srvs_len > 1: warnings.append( "Junction will be replaced. Existing multiple servers #{0} will be overwritten. Please re-add as needed.".format( @@ -616,7 +599,7 @@ def set(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_ else: add_required = True - if force is True or add_required: + if force or add_required: # Junction force add will replace the junction, no need for delete (force set to True as a result) return add(isamAppliance=isamAppliance, reverseproxy_id=reverseproxy_id, junction_point=junction_point, server_hostname=server_hostname, server_port=server_port, junction_type=junction_type, @@ -690,7 +673,7 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= :param isamAppliance: :param reverseproxy_id: - :param junctions: List of junctions to set + :param junctions: List of junctions to set. This is a list of dicts, with each dict representing a junction :param check_mode: :param force: :return: @@ -701,7 +684,6 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= __markChanged = False # use this bool to indicate if there's been a change or not. - if currentJunctions['rc'] == 0: logger.debug(f"\nCurrent junctions:\n{currentJunctions}") else: @@ -754,6 +736,14 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= j['client_ip_http'] = 'insert' if j.get('junction_type', None) is not None: j['junction_type'] = j.get('junction_type', '').lower() # if junction_type is empty, rest api will fail anyway + # update remote http header logic here + if j.get('remote_http_header', None) is None or j.get('remote_http_header', None) == []: + j['remote_http_header'] = 'do not insert' + elif isinstance(j.get('remote_http_header', None), list): + j['remote_http_header'] = [_word.replace('_', '-') for _word in + j.get('remote_http_header', None)] + else: + j['remote_http_header'] = [j.get('remote_http_header', None)] # convenience. # check that we have the required fields, if not, get them from the first server (if that exists) @@ -796,7 +786,6 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= break elif c.get('id', None) == j['junction_point']: logger.debug(f"The junction at {j['junction_point']} already exists (simple syntax)") - logger.debug(f"Retrieve the details for this junction.") warnings.append(f"Had to use simple get syntax unexpectedly for {j['junction_point']}") _checkUpdate = get(isamAppliance, reverseproxy_id, j['junction_point'], check_mode=False, force=False, warnings=warnings) _checkUpdate = _checkUpdate.get('data', _checkUpdate) @@ -819,82 +808,14 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= break if _checkUpdate: - # perform a comparison. we receive the actual current junction as input here - srvs = _checkUpdate.get('servers', None) - logger.debug(f"\n\nServers in junction {j['junction_point']}:\n{srvs}") - add_required = False - if srvs is not None and not junction_server_exists(isamAppliance, srvs, **j): - add_required = True - if not add_required: - # ok we still need to compare - exist_jct = dict(_checkUpdate) - j.pop('isVirtualJunction', None) - new_jct = dict(j) - # remove the server fields - this has already been compared - for _field, kval in server_fields.items(): - new_jct.pop(_field, None) - # remove the servers for the comparison - new_jct.pop('servers', None) - new_jct.pop('force', None) - # junction_type - if exist_jct.get('junction_type', None) is not None: - exist_jct['junction_type'] = exist_jct.get('junction_type', '').lower() - #insert_ltpa_cookies - if exist_jct.get('insert_ltpa_cookies', None) is None: - exist_jct['insert_ltpa_cookies'] = 'no' - #sms_environment - if exist_jct.get('sms_environment', None) is None: - exist_jct['sms_environment'] = "" - #vhost_label - if exist_jct.get('vhost_label', None) is None: - exist_jct['vhost_label'] = "" - # request_encoding utf8_bin, utf8_uri, lcp_bin, and lcp_uri. - __re = exist_jct.get('request_encoding', None) - if __re is not None: - if __re == 'UTF-8, URI Encoded': - exist_jct['request_encoding'] = 'utf8_uri' - elif __re == 'UTF-8, Binary': - exist_jct['request_encoding'] = 'utf8_bin' - elif __re == 'Local Code Page, Binary': - exist_jct['request_encoding'] = 'lcp_bin' - elif __re == 'Local Code Page, URI Encoded': - exist_jct['request_encoding'] = 'lcp_uri' - - # To allow for multiple header values to be sorted during compare convert retrieved data into array - __rehh = exist_jct.get('remote_http_header', None) - if __rehh is not None: - if __rehh.startswith('insert - '): - exist_jct['remote_http_header'] = [_word.replace('_','-') for _word in (__rehh[9:]).split(' ')] - elif exist_jct['remote_http_header'] == 'do not insert': - exist_jct['remote_http_header'] = [] - - # basic_auth_mode - filter (default), ignore, supply, gso. - __bamode = exist_jct.get('basic_auth_mode', None) - if __bamode is not None: - # GSO - if __bamode == "use GSO": - exist_jct['basic_auth_mode'] = 'gso' - - # only compare values that are in the new request - # This does not (always) compare values correctly where you just remove the key. In that case, you'd have to change a different key as well (eg. description) - exist_jct = {k: v for k, v in exist_jct.items() if k in new_jct.keys()} - - newJSON = json.dumps(new_jct, skipkeys=True, sort_keys=True) - logger.debug(f"\nSorted Desired Junction {j['junction_point']}:\n\n {newJSON}\n") - - oldJSON = json.dumps(exist_jct, skipkeys=True, sort_keys=True) - logger.debug(f"\nSorted Current Junction {exist_jct.get('junction_point', '')}:\n\n {oldJSON}\n") - - if newJSON != oldJSON: - logger.debug("Junctions are found to be different. See JSON for difference.") - add_required = True - if add_required: + if not junction_exists(isamAppliance, _checkUpdate, j): __markChanged = True # Run set() logger.debug("\n\nUpdate junction\n\n") warnings.append(f"Updating junction {j['junction_point']}") j['force'] = True # force create j['warnings'] = warnings + j.pop('isVirtualJunction', None) __result = set(isamAppliance, reverseproxy_id, **j) logger.debug(f"Adding servers") # Also add servers (if servers[] has more than 1 item) @@ -908,7 +829,6 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= else: logger.debug(f"\n\nJunction {j.get('junction_point','')} does not need updating\n\n") warnings.append(f"Junction {j.get('junction_point','')} does not need updating") - else: # Force create - this junction does not exist yet j.pop('isVirtualJunction', None) @@ -932,7 +852,7 @@ def set_all(isamAppliance, reverseproxy_id: str, junctions: list=[], check_mode= for _field, kval in server_fields.items(): if s.get(_field, None) is not None: j[_field] = s.get(_field, None) - return junctions_server.set(isamAppliance, reverseproxy_id, **j) + junctions_server.set(isamAppliance, reverseproxy_id, **j) if __markChanged: return isamAppliance.create_return_object(changed=True, warnings=warnings) else: @@ -1003,17 +923,12 @@ def junction_server_exists(isamAppliance, srvs, server_hostname: str, server_por else: server_json['server_cn'] = server_cn # Delete dynamic data shown when we get junctions details - if 'current_requests' in srv: - del srv['current_requests'] - if 'total_requests' in srv: - del srv['total_requests'] - if 'operation_state' in srv: - del srv['operation_state'] - if 'server_state' in srv: - del srv['server_state'] + srv.pop('current_requests', None) + srv.pop('total_requests', None) + srv.pop('operation_state', None) + srv.pop('server_state', None) # Not sure what this attribute is supposed to contain? - if 'query_contents' in srv: - del srv['query_contents'] + srv.pop('query_contents', None) if not isVirtualJunction: if 'virtual_junction_hostname' not in srv: # this is not in the returned servers object for virtual host junctions, it's in the junction's object @@ -1025,8 +940,79 @@ def junction_server_exists(isamAppliance, srvs, server_hostname: str, server_por logger.debug("Servers are found to be different. See following JSON for difference.") logger.debug("New Server JSON: {0}".format(tools.json_sort(server_json))) logger.debug("Old Server JSON: {0}".format(tools.json_sort(srv))) - break return server_found +def junction_exists(isamAppliance, exist_jct, new_j): + # perform a comparison. we receive the actual current junction as input here + __srvs = exist_jct.get('servers', None) + new_j.pop('isVirtualJunction', None) + logger.debug(f"\n\nServers in junction {new_j['junction_point']}:\n{__srvs}") + __result = True + if __srvs is not None and not junction_server_exists(isamAppliance, __srvs, **new_j): + __result = False + if __result: + # ok we still need to compare + # exist_jct = dict(_checkUpdate) + new_jct = dict(new_j) + # remove the server fields - this has already been compared + for _field, kval in server_fields.items(): + new_jct.pop(_field, None) + # junction_type + if exist_jct.get('junction_type', None) is not None: + exist_jct['junction_type'] = exist_jct.get('junction_type', '').lower() + # insert_ltpa_cookies + if exist_jct.get('insert_ltpa_cookies', None) is None: + exist_jct['insert_ltpa_cookies'] = 'no' + # sms_environment + if exist_jct.get('sms_environment', None) is None: + exist_jct['sms_environment'] = "" + # vhost_label + if exist_jct.get('vhost_label', None) is None: + exist_jct['vhost_label'] = "" + # request_encoding utf8_bin, utf8_uri, lcp_bin, and lcp_uri. + __re = exist_jct.get('request_encoding', None) + if __re is not None: + if __re == 'UTF-8, URI Encoded': + exist_jct['request_encoding'] = 'utf8_uri' + elif __re == 'UTF-8, Binary': + exist_jct['request_encoding'] = 'utf8_bin' + elif __re == 'Local Code Page, Binary': + exist_jct['request_encoding'] = 'lcp_bin' + elif __re == 'Local Code Page, URI Encoded': + exist_jct['request_encoding'] = 'lcp_uri' + + # To allow for multiple header values to be sorted during compare convert retrieved data into array + __rehh = exist_jct.get('remote_http_header', None) + if __rehh is not None: + if __rehh.startswith('insert - '): + exist_jct['remote_http_header'] = [_word.replace('_', '-') for _word in (__rehh[9:]).split(' ')] + # now see if it's actually 'all' - to do that, compare with string ["iv-creds", "iv-groups", "iv-user"] + __nrehh = json.dumps(exist_jct.get('remote_http_header', None), skipkeys=True, sort_keys=True, cls=jsonSortedListEncoder) + logger.debug(f"Sorted string content of remote_http_header {__nrehh}") + if __nrehh == '["iv-creds", "iv-groups", "iv-user"]': + exist_jct['remote_http_header'] = ['all'] + + # basic_auth_mode - filter (default), ignore, supply, gso. + __bamode = exist_jct.get('basic_auth_mode', None) + if __bamode is not None: + # GSO + if __bamode == "use GSO": + exist_jct['basic_auth_mode'] = 'gso' + # Remove servers field for comparing + new_jct.pop("servers", None) + # only compare values that are in the new request + # This does not (always) compare values correctly where you just remove the key. In that case, you'd have to change a different key as well (eg. description) + exist_jct = {k: v for k, v in exist_jct.items() if k in new_jct.keys()} + + newJSON = json.dumps(new_jct, skipkeys=True, sort_keys=True, cls=jsonSortedListEncoder) + logger.debug(f"\nSorted Desired Junction {new_j['junction_point']}:\n\n {newJSON}\n") + + oldJSON = json.dumps(exist_jct, skipkeys=True, sort_keys=True, cls=jsonSortedListEncoder) + logger.debug(f"\nSorted Current Junction {exist_jct.get('junction_point', '')}:\n\n {oldJSON}\n") + + if newJSON != oldJSON: + logger.debug("Junctions are found to be different. See JSON for difference.") + __result = False + return __result From a5b989a0f3d97c2d0bc19871d42bbbc0d6212683 Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Fri, 23 Feb 2024 11:18:13 +0100 Subject: [PATCH 11/13] feature: move variable to shared junctions_config.py file --- .../web/reverse_proxy/junctions_server.py | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/ibmsecurity/isam/web/reverse_proxy/junctions_server.py b/ibmsecurity/isam/web/reverse_proxy/junctions_server.py index 9a2885d4..8aef325b 100644 --- a/ibmsecurity/isam/web/reverse_proxy/junctions_server.py +++ b/ibmsecurity/isam/web/reverse_proxy/junctions_server.py @@ -2,30 +2,13 @@ import ibmsecurity.utilities.tools from ibmsecurity.utilities import tools import ibmsecurity.isam.web.reverse_proxy.junctions +from ibmsecurity.isam.web.reverse_proxy.junctions_config import server_fields import json logger = logging.getLogger(__name__) uri = "/wga/reverseproxy" -server_fields = {'server_hostname': {'type': 'string'}, - 'server_port': {'type': 'number'}, - 'case_sensitive_url': {'type': 'yesno', 'max_version': "10.0.6.0"}, - 'case_insensitive_url': {'type': 'yesno', 'min_version': "10.0.6.0"}, - 'http_port': {'type': 'number'}, - 'local_ip': {'type': 'string'}, - 'query_contents': {'type': 'string', 'alt_name': 'query_content_url'}, - 'server_dn': {'type': 'string'}, - 'server_uuid': {'type': 'ignore'}, - 'virtual_hostname': {'type': 'string', 'alt_name': 'virtual_junction_hostname'}, - 'windows_style_url': {'type': 'yesno'}, - 'current_requests': {'type': 'ignore'}, - 'total_requests': {'type': 'ignore'}, - 'operation_state': {'type': 'ignore'}, - 'server_state': {'type': 'ignore'}, - 'priority': {'type': 'number', 'min_version': "10.0.2.0"}, - 'server_cn': {'type': 'string', 'min_version': "10.0.2.0"}} - def search(isamAppliance, reverseproxy_id, junction_point, server_hostname, server_port): ret_obj_new = isamAppliance.create_return_object() ret_obj = ibmsecurity.isam.web.reverse_proxy.junctions.get(isamAppliance, reverseproxy_id, junction_point) From 66270abcf4dcd2d2217170f8a56386f903075895 Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Fri, 23 Feb 2024 11:19:05 +0100 Subject: [PATCH 12/13] documentation: update changelog.md --- changelog.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/changelog.md b/changelog.md index 12a65981..aa300249 100644 --- a/changelog.md +++ b/changelog.md @@ -1,8 +1,10 @@ ---- # Manual change log ## Unreleased +- feature: set_all function for reverse proxy junctions +- fix: ignore_if_down cannot be set to False (#410) + ## 2023.4.26.0 - fix: remove pyOpenSSL dependency in management_ssl_certificate.py (#366) From 851ee531512bf409895c732cc6b0a172a7637325 Mon Sep 17 00:00:00 2001 From: Tom Bosmans Date: Fri, 23 Feb 2024 11:23:47 +0100 Subject: [PATCH 13/13] documentation: version --- pyproject.toml | 2 +- setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c66fad57..0346337c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" [project] name = "ibmsecurity" -version = "2023.4.26.0" +version = "2024.2.23.0" authors = [ { name="IBM", email="secorch@us.ibm.com" }, ] diff --git a/setup.py b/setup.py index 73847213..34b7d679 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 = '2023.4.26.0', + version = '2024.2.23.0', description = 'Idempotent functions for IBM Security Appliance REST APIs', author='IBM', author_email='secorch@us.ibm.com',