Beispiel #1
0
    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)))
Beispiel #4
0
    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())