def test_are_network_users_equal_in_flow(self): assert_that( IsEqualToFlowComparisonLogic.are_network_users_equal_in_flow( ["user1", "user2"], {"networkUsers": [{ "name": "user1" }, { "name": "user2" }]}), is_(equal_to(True))) assert_that( IsEqualToFlowComparisonLogic.are_network_users_equal_in_flow( ["user1", "UnknownUser"], {"networkUsers": [{ "name": "user1" }, { "name": "user2" }]}), is_(equal_to(False))) # Test the case where the network users are set to ANY on the server assert_that( IsEqualToFlowComparisonLogic.are_network_users_equal_in_flow( ["user1"], {"networkUsers": [ANY_OBJECT]}), is_(equal_to(False))) assert_that( IsEqualToFlowComparisonLogic.are_network_users_equal_in_flow( [], {"networkUsers": [ANY_OBJECT]}), is_(equal_to(True)))
def test__are_network_services_equal_in_flow(self): # TODO: Make sure that we have no issues with case sensitiveness of TCP/80 vs tcp/80 for any of the protocols assert_that( IsEqualToFlowComparisonLogic._are_network_services_equal_in_flow( ["service1", "service2"], [{ "name": "service2" }, { "name": "service1" }]), is_(equal_to(True))) assert_that( IsEqualToFlowComparisonLogic._are_network_services_equal_in_flow( ["service2"], [{ "name": "service1" }], ), is_(equal_to(False)))
def test__are_destinations_equal_in_flow(self): assert_that( IsEqualToFlowComparisonLogic._are_destinations_equal_in_flow( ["objectName1", "objectName2"], [{ "name": "objectName1" }, { "name": "objectName2" }], ), is_(equal_to(True))) assert_that( IsEqualToFlowComparisonLogic._are_destinations_equal_in_flow( ["objectName1"], [{ "name": "UnknownObjectName" }], ), is_(equal_to(False)))
def test_are_sources_equal_in_flow(self): assert_that( IsEqualToFlowComparisonLogic.are_sources_equal_in_flow( ["objectName1", "objectName2"], { "sources": [{ "name": "objectName1" }, { "name": "objectName2" }] }, ), is_(equal_to(True))) assert_that( IsEqualToFlowComparisonLogic.are_sources_equal_in_flow( ["objectName1"], {"sources": [{ "name": "UnknownObjectName" }]}, ), is_(equal_to(False)))
def test__is_equal_all_fields_are_checked( self, m_are_sources_equal_in_flow, m_are_destinations_equal_in_flow, m_are_network_services_equal_in_flow, m_are_network_applications_equal_in_flow, m_are_network_users_equal_in_flow, ): requested_flow = Mock() server_flow = Mock() server_flow.__getitem__ = Mock() IsEqualToFlowComparisonLogic.is_equal(requested_flow, server_flow) m_are_sources_equal_in_flow.assert_called_once_with( requested_flow.sources, server_flow['sources']) m_are_destinations_equal_in_flow.assert_called_once_with( requested_flow.destinations, server_flow['destinations']) m_are_network_services_equal_in_flow.assert_called_once_with( requested_flow.network_services, server_flow['services']) m_are_network_applications_equal_in_flow.assert_called_once_with( requested_flow.network_applications, server_flow.get('networkApplications', [])) m_are_network_users_equal_in_flow.assert_called_once_with( requested_flow.network_users, server_flow.get('networkUsers', []))
def main(): module = AnsibleModule( # TODO: Support the check mode actually within the module supports_check_mode=True, argument_spec=dict( # arguments used for creating the flow ip_address=dict(required=True), user=dict(required=True, aliases=["username"]), password=dict(aliases=["pass", "pwd"], required=True, no_log=True), app_name=dict(required=True), name=dict(required=True), sources=dict(type="list", required=True), destinations=dict(type="list", required=True), services=dict(type="list", required=True), users=dict(type="list", required=False, default=[]), network_applications=dict(type="list", required=False, default=[]), comment=dict(required=False, default="Flow created by AlgosecAnsible"), apply_draft=dict(type="bool", default=True), ), ) if not HAS_LIB: module.fail_json(msg="algoec package is required for this module") app_name = module.params["app_name"] flow_name = module.params["name"] try: api = AlgosecBusinessFlowAPIClient( module.params["ip_address"], module.params["user"], module.params["password"], ) latest_revision_id = api.get_application_revision_id_by_name(app_name) requested_flow = RequestedFlow( name=flow_name, sources=module.params["sources"], destinations=module.params["destinations"], network_users=module.params["users"], network_applications=module.params["network_applications"], network_services=module.params["services"], comment=module.params["comment"], ) # requested_flow.populate(api) try: flow = api.get_flow_by_name(latest_revision_id, flow_name) if IsEqualToFlowComparisonLogic.is_equal(requested_flow, flow): # Flow exists and is equal to the requested flow delete, create = False, False else: # Flow exists and is different than the requested flow delete, create = True, True except EmptyFlowSearch: # Flow does not exist, create it delete, create = False, True if not create: changed = False message = "Flow already exists on AlgoSec BusinessFlow." elif module.check_mode: changed = False message = "Flow creation/update postponed since check mode is on" else: if delete: api.delete_flow_by_name(latest_revision_id, flow_name) latest_revision_id = api.get_application_revision_id_by_name( app_name) api.create_application_flow(latest_revision_id, requested_flow) # to finalize the application flow creation, The application"s draft version is applied if module.params["apply_draft"]: try: latest_revision_id = api.get_application_revision_id_by_name( app_name) api.apply_application_draft(latest_revision_id) except AlgosecAPIError, e: module.fail_json( msg="Exception while trying to apply application draft. " "It is possible that another draft was just applied. " "You can run the module with apply_draft=False.\nResponse Json: {}" .format(e.response_json)) changed = True message = "Flow created successfully!" module.exit_json(changed=changed, msg=message)
def main(): module = AnsibleModule( supports_check_mode=True, argument_spec=dict( ip_address=dict(required=True), user=dict(required=True, aliases=["username"]), password=dict(aliases=["pass", "pwd"], required=True, no_log=True), certify_ssl=dict(type="bool", default=False), app_name=dict(required=True), # flow name --> flow definition app_flows=dict(type="dict", required=True), check_connectivity=dict(type="bool", default=False)), ) if not HAS_LIB: module.fail_json(msg="algoec package is required for this module") # Verify the data structure of the requested app flows validate_app_flows(module.params["app_flows"]) try: api = BusinessFlowAPIClient( module.params["ip_address"], module.params["user"], module.params["password"], module.params["certify_ssl"], ) app_name = module.params["app_name"] requested_flows = { flow_name: RequestedFlow( name=flow_name, sources=flow["sources"], destinations=flow["destinations"], network_services=flow["services"], # Non required flow fields network_users=flow.get("users", []), network_applications=flow.get("network_applications", []), comment=flow.get("comment", ""), ) for flow_name, flow in module.params["app_flows"].items() } app_revision_id = api.get_application_revision_id_by_name(app_name) # Converted to flow_name --> flow_data current_app_flows = { flow["name"]: flow for flow in api.get_application_flows(app_revision_id) } current_flow_names = set(current_app_flows.keys()) requested_flow_names = set(requested_flows.keys()) # Find the flows to remove, create and modify flows_to_delete = current_flow_names - requested_flow_names flows_to_create = requested_flow_names - current_flow_names # Get list of all flow names that have been modified in the new definition # Process only flow names that are both defined on ABF and present in the new flow definition modified_flows = { flow_name for flow_name in current_flow_names.intersection( requested_flow_names) if not IsEqualToFlowComparisonLogic.is_equal( requested_flows[flow_name], current_app_flows[flow_name]) } unchanged_flows = current_flow_names - flows_to_delete.union( modified_flows) change_is_needed = any( [flows_to_delete, flows_to_create, modified_flows]) if not change_is_needed: msg = "Application flows are up-to-date on on AlgoSec BusinessFlow." changed = False elif module.check_mode: changed = False msg = "Check mode is on - flows update postponed" else: # Once we make the first change to the app, a new app revision would be created # We need to fetch it's ID and send all the consecutive API calls for the new revision ID # But, we want to update this info only once, to avoid redundant API calls. is_draft_revision = False # Delete all flows marked for deletion or modification for flow_name_to_delete in sorted(flows_to_delete | modified_flows): api.delete_flow_by_id( app_revision_id, current_app_flows[flow_name_to_delete]["flowID"]) # update application revision if draft revision was created if not is_draft_revision: app_revision_id = api.get_application_revision_id_by_name( app_name) is_draft_revision = True current_app_flows = { flow["name"]: flow for flow in api.get_application_flows(app_revision_id) } # Create all flows marked for creation or modification for flow_name_to_create in sorted(flows_to_create | modified_flows): api.create_application_flow( app_revision_id, requested_flows[flow_name_to_create]) # update application revision if draft revision was created if not is_draft_revision: app_revision_id = api.get_application_revision_id_by_name( app_name) is_draft_revision = True try: # Apply the application draft api.apply_application_draft(app_revision_id) changed = True msg = "App flows updated successfully and application draft was applied!" except AlgoSecAPIError as e: return module.fail_json( msg="Exception while trying to apply application draft: {}" .format(e.response_content)) # Query for the list of blocking flows (check only flows that were not changed) if module.params["check_connectivity"]: blocked_flows = [] for flow_name in unchanged_flows: flow_connectivity = api.get_flow_connectivity( app_revision_id, current_app_flows[flow_name]["flowID"]) if flow_connectivity["status"] != ALLOWED_FLOW_CONNECTIVITY: blocked_flows.append(flow_name) if blocked_flows: module.fail_json( app_name=app_name, deleted_flows=len(flows_to_delete), created_flows=len(flows_to_create), modified_flows=len(modified_flows), unchanged_flows=len(unchanged_flows), blocked_flows=blocked_flows, changed=changed, msg= "Flows defined successfully but connectivity check failed.", ) module.exit_json( app_name=app_name, deleted_flows=len(flows_to_delete), created_flows=len(flows_to_create), modified_flows=len(modified_flows), unchanged_flows=len(unchanged_flows), changed=changed, msg=msg, ) except AlgoSecAPIError: module.fail_json(msg=traceback.format_exc())