Resolve "(CTTC) Integrate management of Network Access Control Lists (ACLs) created through the NBI and forwarded to SBI"

Closes #114 (closed)

Merge request reports

Loading
+8 −1
Changes for proto/context.proto: 8 added lines, 1 removed line.
Original line number Diff line number Diff line
@@ -566,9 +566,16 @@ message ConfigRule_Custom {
  string resource_value = 2;
}

enum AclDirectionEnum {
  ACLDIRECTION_BOTH    = 0;
  ACLDIRECTION_INGRESS = 1;
  ACLDIRECTION_EGRESS  = 2;
}

message ConfigRule_ACL {
  EndPointId       endpoint_id = 1;
  acl.AclRuleSet rule_set = 2;
  AclDirectionEnum direction   = 2;
  acl.AclRuleSet   rule_set    = 3;
}

message ConfigRule_IPOWDM {
+57 −15
Changes for src/device/service/drivers/gnmi_openconfig/handlers/Acl.py: 57 added lines, 15 removed lines.
Original line number Diff line number Diff line
@@ -12,12 +12,9 @@
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import logging
import json, libyang, logging
from typing import Any, Dict, List, Tuple

import libyang

from common.proto.context_pb2 import AclDirectionEnum
from ._Handler import _Handler
from .YangHandler import YangHandler

@@ -41,6 +38,20 @@ _OC_TFS_FWD_ACTION = {v: k for k, v in _TFS_OC_FWD_ACTION.items()}

# ─────────────────────────────────────────────────────────────────────────

DIRECTION_INGRESS = {
    AclDirectionEnum.ACLDIRECTION_BOTH,
    AclDirectionEnum.Name(AclDirectionEnum.ACLDIRECTION_BOTH),
    AclDirectionEnum.ACLDIRECTION_INGRESS,
    AclDirectionEnum.Name(AclDirectionEnum.ACLDIRECTION_INGRESS),
}

DIRECTION_EGRESS = {
    AclDirectionEnum.ACLDIRECTION_BOTH,
    AclDirectionEnum.Name(AclDirectionEnum.ACLDIRECTION_BOTH),
    AclDirectionEnum.ACLDIRECTION_EGRESS,
    AclDirectionEnum.Name(AclDirectionEnum.ACLDIRECTION_EGRESS),
}


class AclHandler(_Handler):
    def get_resource_key(self) -> str:
@@ -59,8 +70,9 @@ class AclHandler(_Handler):
        rs = resource_value['rule_set']
        rs_name = rs['name']
        oc_type = _TFS_OC_RULE_TYPE[rs['type']]
        device = resource_value['endpoint_id']['device_id']['device_uuid']['uuid']
        #device = resource_value['endpoint_id']['device_id']['device_uuid']['uuid']
        iface = resource_value['endpoint_id']['endpoint_uuid']['uuid']
        direction = resource_value['direction']

        if delete:
            path = f'/acl/acl-sets/acl-set[name={rs_name}][type={oc_type}]'
@@ -77,11 +89,11 @@ class AclHandler(_Handler):
        y_entries = y_set.create_path('acl-entries')
        for entry in rs.get('entries', []):
            seq = int(entry['sequence_id'])
            m_ = entry["match"]
            m_ = entry['match']
            src_address = m_.get('src_address', '0.0.0.0/0')
            dst_address = m_.get('dst_address', '0.0.0.0/0')
            src_port = m_.get("src_port")
            dst_port = m_.get("dst_port")
            src_port = m_.get('src_port')
            dst_port = m_.get('dst_port')
            act = _TFS_OC_FWD_ACTION[entry['action']['forward_action']]

            y_e = y_entries.create_path(f'acl-entry[sequence-id="{seq}"]')
@@ -91,9 +103,12 @@ class AclHandler(_Handler):
            y_ipv4.create_path('config/source-address', src_address)
            y_ipv4.create_path('config/destination-address', dst_address)

            proto = m_.get('protocol')
            if proto is not None:
                y_ipv4.create_path('config/protocol', int(proto))

            if src_port or dst_port:
                proto = m_.get("protocol")
                y_trans = y_e.create_path("transport")
                y_trans = y_e.create_path('transport')
                if src_port:
                    y_trans.create_path("config/source-port", int(src_port))
                if dst_port:
@@ -106,14 +121,41 @@ class AclHandler(_Handler):
        # Interface binding
        y_intfs = yang_acl.create_path('interfaces')
        y_intf = y_intfs.create_path(f'interface[id="{iface}"]')
        y_ing = y_intf.create_path('ingress-acl-sets')
        y_ing_set = y_ing.create_path(f'ingress-acl-set[set-name="{rs_name}"][type="{oc_type}"]')
        y_ing_set.create_path('config/set-name', rs_name)
        y_ing_set.create_path('config/type', oc_type)

        if direction in DIRECTION_INGRESS:
            y_ingress = y_intf.create_path('ingress-acl-sets')
            y_ingress_set = y_ingress.create_path(f'ingress-acl-set[set-name="{rs_name}"][type="{oc_type}"]')
            y_ingress_set.create_path('config/set-name', rs_name)
            y_ingress_set.create_path('config/type', oc_type)

        if direction in DIRECTION_EGRESS:
            y_egress = y_intf.create_path('egress-acl-sets')
            y_egress_set = y_egress.create_path(f'egress-acl-set[set-name="{rs_name}"][type="{oc_type}"]')
            y_egress_set.create_path('config/set-name', rs_name)
            y_egress_set.create_path('config/type', oc_type)

        json_data = yang_acl.print_mem('json')
        #json_data = str(node.print_mem(
        #    fmt='json', with_siblings=True, pretty=True,
        #    keep_empty_containers=True, include_implicit_defaults=True
        #))
        LOGGER.debug('JSON data: %s', json_data)
        json_obj = json.loads(json_data)['openconfig-acl:acl']

        # release generated nodes to prevent side effects
        node_ifs : libyang.DNode = yang_acl.find_path('/openconfig-acl:acl/interfaces')
        #if node is None: return None
        LOGGER.info('node_ifs = {:s}'.format(str(node_ifs)))
        node_ifs.unlink()
        node_ifs.free()

        # release generated nodes to prevent side effects
        node_acls : libyang.DNode = yang_acl.find_path('/openconfig-acl:acl/acl-sets')
        #if node is None: return None
        LOGGER.info('node_acls = {:s}'.format(str(node_acls)))
        node_acls.unlink()
        node_acls.free()

        return '/acl', json.dumps(json_obj)

    def parse(  # pylint: disable=too-many-locals
+4 −3
Changes for src/nbi/service/ietf_acl/Acls.py: 4 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -46,12 +46,11 @@ def compose_interface_direction_acl_rules(
        if acl_set is None:
            MSG = 'Interface({:s})/{:s}/AclSet({:s}) not found'
            raise NotFound(MSG.format(
                str(interface_name), acl_direction_title,
                str(acl_set_name)
                str(interface_name), acl_direction_title, str(acl_set_name)
            ))

        acl_config_rule = config_rule_from_ietf_acl(
            device_name, interface_name, acl_set
            device_name, interface_name, acl_direction, acl_set
        )
        MSG = 'Adding {:s} ACL Config Rule: {:s}'
        LOGGER.info(MSG.format(
@@ -114,12 +113,14 @@ class Acls(Resource):
            interface_data = interface_name__to__interface_data.get(interface_name)
            if interface_data is None: continue

            if 'ingress' in interface_data:
                ingress_acl_config_rules = compose_interface_direction_acl_rules(
                    device_name, interface_name, interface_data, AclDirectionEnum.INGRESS,
                    acl_name__to__acl_data
                )
                device.device_config.config_rules.extend(ingress_acl_config_rules)

            if 'egress' in interface_data:
                egress_acl_config_rules = compose_interface_direction_acl_rules(
                    device_name, interface_name, interface_data, AclDirectionEnum.EGRESS,
                    acl_name__to__acl_data
+12 −2
Changes for src/nbi/service/ietf_acl/ietf_acl_parser.py: 12 added lines, 2 removed lines.
Original line number Diff line number Diff line
@@ -17,10 +17,11 @@ from typing import List, Dict, Optional
from pydantic import BaseModel, Field
from werkzeug.exceptions import NotImplemented
from common.proto.acl_pb2 import AclForwardActionEnum, AclRuleTypeEnum, AclEntry
from common.proto.context_pb2 import ConfigActionEnum, ConfigRule
from common.proto.context_pb2 import ConfigActionEnum, ConfigRule, AclDirectionEnum as Proto_AclDirectionEnum


class AclDirectionEnum(Enum):
    BOTH    = 'both'
    INGRESS = 'ingress'
    EGRESS  = 'egress'

@@ -132,14 +133,23 @@ TFS_IETF_FORWARDING_ACTION_MAPPING = {


def config_rule_from_ietf_acl(
    device_name: str, endpoint_name: str, acl_set_data: Dict
    device_name : str, endpoint_name : str, acl_direction : AclDirectionEnum,
    acl_set_data : Dict
) -> ConfigRule:
    acl_config_rule = ConfigRule()
    acl_config_rule.action = ConfigActionEnum.CONFIGACTION_SET

    acl_endpoint_id = acl_config_rule.acl.endpoint_id
    acl_endpoint_id.device_id.device_uuid.uuid = device_name
    acl_endpoint_id.endpoint_uuid.uuid = endpoint_name

    if acl_direction == AclDirectionEnum.INGRESS:
        acl_config_rule.acl.direction = Proto_AclDirectionEnum.ACLDIRECTION_INGRESS
    elif acl_direction == AclDirectionEnum.EGRESS:
        acl_config_rule.acl.direction = Proto_AclDirectionEnum.ACLDIRECTION_EGRESS
    else:
        acl_config_rule.acl.direction = Proto_AclDirectionEnum.ACLDIRECTION_BOTH

    acl_name = acl_set_data['name']
    acl_type = acl_set_data['type']
    if acl_type.startswith('ietf-access-control-list:'):
+3 −3
Changes for src/osm_client/Dockerfile: 3 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -31,9 +31,9 @@ RUN GRPC_HEALTH_PROBE_VERSION=v0.2.0 && \
    chmod +x /bin/grpc_health_probe

# Get generic Python packages
RUN python3 -m pip install --upgrade pip
RUN python3 -m pip install --upgrade setuptools wheel
RUN python3 -m pip install --upgrade pip-tools
RUN python3 -m pip install --upgrade 'pip==25.2'
RUN python3 -m pip install --upgrade 'setuptools==79.0.0' 'wheel==0.45.1'
RUN python3 -m pip install --upgrade 'pip-tools==7.3.0'

# Get common Python packages
# Note: this step enables sharing the previous Docker build steps among all the Python components
Loading
Loading