Example #1
0
class PaloAltoAddress(BaseAddress):
    """Palo Alto Address

    Inherits from BaseAddress and provides access to the underlying pandevice object.
    Also provides and mapper for the type field.
    """

    TypeMap = bidict({
        BaseAddress.AddressTypes.IPv4: 'ip-netmask',
        BaseAddress.AddressTypes.RANGE: 'ip-range',
        BaseAddress.AddressTypes.DNS: 'fqdn',
        BaseAddress.AddressTypes.ANY: 'any'
    })

    def __init__(self, pandevice_object):

        self.pandevice_object = pandevice_object

        super(PaloAltoAddress, self).__init__(name=pandevice_object.name, value=pandevice_object.value,
                                              a_type=PaloAltoAddress.TypeMap[pandevice_object.type])

    @classmethod
    def from_criteria(cls, criteria):
        """Create an instance from the provided criteria
        """

        pandevice_object = objects.AddressObject()
        pandevice_object.name = criteria['name']
        pandevice_object.value = criteria['value']
        pandevice_object.type = criteria['type']

        return cls(pandevice_object)
Example #2
0
class JuniperSRXPolicy(BasePolicy):
    """Juniper SRX Policy

    Inherits from Base Policy
    """

    ActionMap = bidict({
        BasePolicy.Action.ALLOW: 'permit',
        BasePolicy.Action.DENY: 'deny',
        BasePolicy.Action.REJECT: 'reject',
    })

    LoggingMap = bidict({
        BasePolicy.Logging.START: 'session-init',
        BasePolicy.Logging.END: 'session-close',
    })

    def __init__(self, name, action, description, logging):

        super(JuniperSRXPolicy, self).__init__(name, action, description,
                                               logging)

    def to_xml(self):
        """Map Juniper SRX Policy Object into xml config element
        """

        policy_element = create_element('policy')
        create_element('name', text=self.name, parent=policy_element)
        match_element = create_element('match', parent=policy_element)
        for s in self.src_addresses:
            create_element('source-address', text=s.name, parent=match_element)
        for d in self.dst_addresses:
            create_element('destination-address',
                           text=d.name,
                           parent=match_element)
        then_element = create_element('then', parent=policy_element)
        create_element(JuniperSRXPolicy.ActionMap[self.action],
                       parent=then_element)
        log_element = create_element('log', parent=then_element)
        for log_type in self.logging:
            create_element(JuniperSRXPolicy.LoggingMap[log_type],
                           parent=log_element)

        return policy_element
Example #3
0
class BaseAddress(BaseObject):

    AddressTypes = enum('IPv4', 'DNS', 'RANGE', 'ANY')
    TypeMap = bidict({
        AddressTypes.IPv4: 'ipv4',
        AddressTypes.DNS: 'dns',
        AddressTypes.RANGE: 'range',
        AddressTypes.ANY: 'any',
    })

    def __init__(self, name, value, a_type):
        """init address object"""

        self.name = name
        self.value = value
        self.a_type = a_type

    def __getattr__(self, item):

        if item == 'value':
            return self.value
        else:
            raise AttributeError

    def table_value(self, with_names):
        if with_names:
            return self.name + " - " + self.value
        else:
            return self.value

    @classmethod
    def from_criteria(cls, criteria):
        """Create an instance from the provided criteria
        """

        return cls(criteria['name'], criteria['value'],
                   cls.TypeMap[criteria['type']])

    def serialize(self):
        """Searialize self to a json acceptable data structure
        """
        return {
            'name': self.name,
            'type': self.TypeMap[self.a_type],
            'value': self.value,
        }
Example #4
0
class PaloAltoApplication(BaseObject):
    """Palo Alto Appliciation
    """

    IdentTypes = enum('PORT', 'PROTOCOL', 'ICMP', 'ICMP6')
    IdentTypeMap = bidict({
        IdentTypes.PORT: 'port',
        IdentTypes.PROTOCOL: 'ident-by-ip-protocol',
        IdentTypes.ICMP: 'ident-by-icmp-type',
        IdentTypes.ICMP6: 'ident-by-icmp6-type',
        None: None,
    })

    WellKnownServiceMap = bidict({
        ('tcp', 80): 'web-browsing',
        ('tcp', 443): 'ssl',
        ('tcp', 22): 'ssh',
        ('udp', 123): 'ntp',
        ('tcp', 21): 'ftp',
        ('tcp', 23): 'telnet',
        ('tcp', 25): 'smtp',
        ('tcp', 1521): 'oracle',
        ('tcp', 1433): 'mssql-db',
        ('tcp', 445): 'ms-ds-smb',
    })

    def __init__(self, pandevice_object):

        self.pandevice_object = pandevice_object

        self.name = self.pandevice_object.name
        self.default_type = PaloAltoApplication.IdentTypeMap[
            self.pandevice_object.default_type]
        self.services = []

        if self.default_type == PaloAltoApplication.IdentTypes.PORT:
            for p in self.pandevice_object.default_port:
                protocol = p.split('/')[0]
                port = p.split('/')[1]
                for port_element in port.split(','):
                    self.services.append(
                        BaseService(name="{0}-{1}".format(
                            self.name, port_element),
                                    port=port_element,
                                    protocol=protocol))

    def match_service(self, service_tuple):
        """Return true if self contains the given service, else false
        """
        for service in self.services:
            if service.protocol == service_tuple[
                    0] and service.port == service_tuple[1]:
                return True
        return False

    def __getattr__(self, item):

        if item == 'value':
            return self.name
        else:
            raise AttributeError

    def table_value(self):
        return self.name

    def serialize(self):
        """Searialize self to a json acceptable data structure
        """

        services = []
        for s in self.services:
            services.append(s.serialize())

        return {
            'name': self.name,
            'default_type': self.default_type,
            'services': services,
        }
Example #5
0
class PaloAltoPolicy(BasePolicy):

    ActionMap = bidict({
        BasePolicy.Action.ALLOW: 'allow',
        BasePolicy.Action.DROP: 'drop',
        BasePolicy.Action.DENY: 'deny'
    })

    def __init__(self, pandevice_object=None):

        # this is the actual object provided by the 'pandevice' library
        self.pandevice_object = pandevice_object

        logging = []
        if pandevice_object.log_start:
            logging.append(BasePolicy.Logging.START)
        if pandevice_object.log_end:
            logging.append(BasePolicy.Logging.END)

        self._applications = []

        super(PaloAltoPolicy,
              self).__init__(name=pandevice_object.name,
                             action=self.ActionMap[pandevice_object.action],
                             description=pandevice_object.description,
                             logging=logging)

    def add_application(self, app):
        self._applications.append(app)

    def serialize(self):
        """Searialize self to a json acceptable data structure
        """

        # we need only update the super with local applications

        super_dict = super(PaloAltoPolicy, self).serialize()
        super_dict.update({
            'applications':
            list(map(lambda x: x.serialize(), self._applications))
        })

        return super_dict

    def __getattr__(self, item):
        """add applications access or call super"""

        if item == 'applications':
            return set(flatten([a.value for a in self._applications]))
        else:
            return super(PaloAltoPolicy, self).__getattr__(item)

    @staticmethod
    def table_service_cell(services, with_names=False):
        """handle the application-default case"""
        if services[0].name == "application-default":
            return "application-default\n"
        else:
            return super(PaloAltoPolicy, PaloAltoPolicy).table_service_cell(
                services, with_names=with_names)

    def table_application_cell(self):
        return "\n".join([a.table_value() for a in self._applications]) + '\n'

    def table_header(self):
        """Return the table header for the policy
        """
        return [
            "Src Zones", "Src Addresses", "Dst Zones", "Dst Addresses",
            "Applications", "Services", "Action"
        ]

    def table_row(self, with_names=False):
        """Return the table row for the based policy
        """
        s_zones = self.table_zone_cell(self.src_zones)
        d_zones = self.table_zone_cell(self.dst_zones)
        s_addresses = self.table_address_cell(self.src_addresses, with_names)
        d_addresses = self.table_address_cell(self.dst_addresses, with_names)
        services = self.table_service_cell(self._services, with_names)
        applications = self.table_application_cell()

        return [
            s_zones, s_addresses, d_zones, d_addresses, applications, services,
            self.ActionMap[self.action]
        ]

    @classmethod
    def from_criteria(cls, criteria):
        """Create an instance from the provided criteria
        """

        logging_criteria = criteria.get('logging', [])

        pandevice_object = policies.SecurityRule()
        pandevice_object.name = criteria['name']
        pandevice_object.description = criteria.get('description', '')
        pandevice_object.action = criteria[
            'action']  # we expect it to be human readable at this point, map it later

        if 'start' in logging_criteria or 'both' in logging_criteria:
            pandevice_object.log_start = True
        if 'end' in logging_criteria or 'both' in logging_criteria:
            pandevice_object.log_end = True

        return cls(pandevice_object)
Example #6
0
class CandidatePolicy(BaseObject):
    """
    candidate policy stores the target element(s) or new policy and a list of the best matched policies
    that can be appended to.
    """

    # implementation methods
    Method = enum('NEW_POLICY', 'APPEND', 'TAG')
    MethodMap = bidict({
        Method.NEW_POLICY: 'NEW_POLICY',
        Method.APPEND: 'APPEND',
        Method.TAG: 'TAG',
    })

    def __init__(self,
                 policy_criteria,
                 matched_policies=None,
                 method=Method.NEW_POLICY):

        self.policy_criteria = policy_criteria
        self.matched_policies = matched_policies or []
        self.policy = None
        self.method = method
        self.tag_options = {}
        self.tag_choices = {}
        self.linked_objects = {}
        self.new_objects = {}

        # Palo Alto/Panorama specific params
        self.address_group_tag_options = {}
        self.shared_namespace = True
        self.context = None
        self.post_rulebase = True

        if self.method != self.Method.NEW_POLICY:
            # this will be an addendum to an existing policy default to the first policy
            self.set_base_policy(matched_policies[0])

    def set_base_policy(self, policy):
        """Set or change the base policy"""
        if policy in self.matched_policies:
            self.policy = policy
        else:
            raise ValueError("Policy is not valid for this candidate policy")

    def serialize(self):
        """Serialize self into json compatible data structures
        """

        linked_objects = {}
        for key, value in self.linked_objects.iteritems():
            linked_objects[key] = {}
            for k, v in value.iteritems():
                if key == 'services' and k != 'any':
                    k = k[0] + "/" + k[1]
                if hasattr(v, 'serialize'):
                    linked_objects[key][k] = v.serialize()
                else:
                    linked_objects[key][k] = v

        new_objects = {}
        for key, value in self.new_objects.iteritems():
            new_objects[key] = {}
            for k, v in value.iteritems():
                if key == 'services' and k != 'any':
                    k = k[0] + "/" + k[1]
                if hasattr(v, 'serialize'):
                    new_objects[key][k] = v.serialize()
                else:
                    new_objects[key][k] = v

        if self.context:
            context = self.context.device_group.name
        else:
            context = None

        if self.matched_policies:
            matched_policies = list(
                map(lambda x: x.serialize(), self.matched_policies))
        else:
            matched_policies = []

        if self.policy:
            policy = self.policy.serialize()
        else:
            policy = None

        return {
            'policy_criteria': self.policy_criteria,
            'matched_policies': matched_policies,
            'policy': policy,
            'method': self.MethodMap[self.method],
            'tag_options': self.tag_options,
            'tag_choices': self.tag_choices,
            'linked_objects': linked_objects,
            'new_objects': new_objects,
            'context': context,
            'address_group_tag_options': self.address_group_tag_options,
            'shared_namespace': self.shared_namespace,
            'post_rulebase': self.post_rulebase
        }
Example #7
0
class BasePolicy(BaseObject):

    Action = enum('ALLOW', 'DENY', 'REJECT', 'DROP')
    Logging = enum('START', 'END')

    ActionMap = bidict({
        Action.ALLOW: "Allow",
        Action.DENY: "Deny",
        Action.REJECT: "Reject",
        Action.DROP: 'Drop',
    })

    def __init__(self, name, action, description, logging):
        """init policy"""

        self.name = name
        self.src_zones = list()
        self.dst_zones = list()
        self.src_addresses = list()
        self.dst_addresses = list()
        self._services = list()
        self.action = action
        self.description = description
        self.logging = logging

    def add_src_zone(self, zone):
        self.src_zones.append(zone)

    def add_dst_zone(self, zone):
        self.dst_zones.append(zone)

    def add_src_address(self, address):
        self.src_addresses.append(address)

    def add_dst_address(self, address):
        self.dst_addresses.append(address)

    def add_service(self, service):
        self._services.append(service)

    def serialize(self):
        """Searialize self to a json acceptable data structure
        """

        s_addrs = list(map(lambda x: x.serialize(), self.src_addresses))
        d_addrs = list(map(lambda x: x.serialize(), self.dst_addresses))
        services = list(map(lambda x: x.serialize(), self._services))

        return {
            'name': self.name,
            'source_zones': self.src_zones,
            'source_addresses': s_addrs,
            'destination_zones': self.dst_zones,
            'destination_addresses': d_addrs,
            'services': services,
            'action': self.ActionMap[self.action],
            'description': self.description,
            'logging': self.logging
        }

    def __getattr__(self, item):
        """
        return a tuple representation of the policy with normalized values
        """

        # s_addrs = set(flatten([a.value for a in self.src_addresses]))
        # d_addrs = set(flatten([a.value for a in self.dst_addresses]))
        # services = set(flatten([s.value for s in self._services]))

        if item == 'value':
            # return self.src_zones, self.dst_zones, list(s_addrs), list(d_addrs), list(services), self.action
            pass

        # for use in policy match element reducing
        elif item == 'source_zones':
            return self.src_zones
        elif item == 'destination_zones':
            return self.dst_zones
        elif item == 'source_addresses':
            return set(flatten([a.value for a in self.src_addresses]))
        elif item == 'destination_addresses':
            return set(flatten([a.value for a in self.dst_addresses]))
        elif item == 'services':
            return set(flatten([s.value for s in self._services]))
        elif item == 'services_objects':
            return self._services

        else:
            raise AttributeError()

    @staticmethod
    def _in_network(value, p_value, exact_match=False):
        """

        """

        # 'any' address is an automatic match if we are exact
        if exact_match and 'any' in p_value:
            return True

        addresses = [
            IPRange(a.split('-')[0],
                    a.split('-')[1]) if '-' in a else IPNetwork(a)
            for a in value if is_ipv4(a)
        ]
        fqdns = [a for a in value if not is_ipv4(a)]
        p_addresses = [
            IPRange(a.split('-')[0],
                    a.split('-')[1]) if '-' in a else IPNetwork(a)
            for a in p_value if is_ipv4(a)
        ]
        p_fqdns = [a for a in p_value if not is_ipv4(a)]

        # network containment implies exact match... i think?
        for a in addresses:
            addr_result = any(a == b or a in b for b in p_addresses)
            if not addr_result:
                return False

        # now match the fqdns
        if exact_match:
            fqdn_result = set(fqdns) == set(p_fqdns)
        else:
            fqdn_result = set(p_fqdns).issubset(set(fqdns))

        return fqdn_result

    def match(self,
              match_criteria,
              exact=False,
              match_containing_networks=True):
        """
        determine if self is a match for the given criteria
        """
        for key, value in match_criteria.iteritems():
            if not value:
                # this key was included but has no value so skip it
                continue

            p_value = getattr(self, key, [])

            if p_value == 'any' and key in [
                    'source_addresses', 'destination_addresses', 'services'
            ]:
                # 'any' as address constitutes a match, so move on
                continue
            elif key == 'action':
                # compare against the converted value
                if self.ActionMap[value] != p_value:
                    return False
            elif len(value) > len(p_value):
                # more values in the match than the policy, fail
                return False
            elif match_containing_networks and key in [
                    'source_addresses', 'destination_addresses'
            ]:
                if not self._in_network(value, p_value, exact_match=True):
                    return False
            elif exact and not set(p_value) == set(value):
                return False
            elif not exact and not set(value).issubset(set(p_value)):
                return False

        return True

    def candidate_match(self,
                        match_criteria,
                        exact=False,
                        match_containing_networks=True):
        """
        wrap the match method in some extra logic to determine if this is a candidate for policy addition
        """
        unique_key = None
        for key, value in match_criteria.iteritems():
            match = self.match({key: value}, exact, match_containing_networks)
            if not match:
                if unique_key:
                    # the unique key is already set so we fail
                    return False
                else:
                    # found a unique key
                    unique_key = key

        # if we survived, this is a candidate policy so return which key is unique
        return unique_key

    @staticmethod
    def table_address_cell(addresses, with_names=False):
        return "\n".join([a.table_value(with_names) for a in addresses]) + '\n'

    @staticmethod
    def table_service_cell(services, with_names=False):
        return "\n".join([s.table_value(with_names) for s in services]) + '\n'

    @staticmethod
    def table_zone_cell(zones):
        return "\n".join([z for z in zones]) + '\n'

    def table_header(self):
        """Return the table header for the based policy
        """
        return [
            "Src Zones", "Src Addresses", "Dst Zones", "Dst Addresses",
            "Services", "Action"
        ]

    def table_row(self, with_names=False):
        """Return the table row for the based policy
        """
        s_zones = self.table_zone_cell(self.src_zones)
        d_zones = self.table_zone_cell(self.dst_zones)
        s_addresses = self.table_address_cell(self.src_addresses, with_names)
        d_addresses = self.table_address_cell(self.dst_addresses, with_names)
        services = self.table_service_cell(self._services, with_names)

        return [
            s_zones, s_addresses, d_zones, d_addresses, services,
            self.ActionMap[self.action]
        ]

    def to_table(self, with_names=False):
        """Return the policy as an ascii tables

        Args:
            with_names (bool): Include object names
        """
        table_header = self.table_header()

        table_row = self.table_row(with_names=with_names)

        table = AsciiTable([table_header, table_row])
        table.title = "Policy: " + self.name

        return table.table

    @classmethod
    def from_criteria(cls, criteria):
        """Create an instance from the provided criteria
        """

        logging = []
        logging_criteria = criteria.get('logging', [])

        if 'start' in logging_criteria or 'both' in logging_criteria:
            logging.append(cls.Logging.START)
        if 'end' in logging_criteria or 'both' in logging_criteria:
            logging.append(cls.Logging.END)

        return cls(criteria['name'], cls.ActionMap[criteria['action']],
                   criteria.get('description'), logging)
class PaloAltoBaseDriver(BaseDriver):
    """Palo Alto Base class
    """

    PolicyClass = PaloAltoPolicy

    ALLOWED_POLICY_KEYS = (
        'source_zones',
        'destination_zones',
        'source_addresses',
        'destination_addresses',
        'services',
        'action',
        'logging',
        'applications',
    )

    # in a given namespace there may be two related object types
    # here we map those together with a bidict
    NamespaceSisterTypes = bidict({
        PaloAltoAddress: PaloAltoAddressGroup,
        PaloAltoService: PaloAltoServiceGroup,
        PaloAltoApplication: PaloAltoApplicationGroup,
    })

    def open_connection(self, username, password, host, **kwargs):
        """
        We need additional information for this driver
        """
        # create a pan device object
        self.device = PanDevice.create_from_device(host, api_username=username, api_password=password,
                                                   api_key=kwargs.get('apikey'))

        self._connected = True

    @staticmethod
    def tag_delta(expression, tag_list):
        """Take in a tag expression and a list of tags and give the delta of tags to meet the expression

        :return tuple( list( "required tags" ),  list( "tuple of options" ) )
        """

        if tag_list is None:
            tag_list = []

        required_tags = []
        optional_tags = []

        def parse_and(tokens):
            args = tokens[0][0::2]
            extend_list = filter(lambda x: isinstance(x, str) and x not in tag_list, args)
            required_tags.extend(extend_list)

        def parse_or(tokens):
            args = tokens[0][0::2]
            append_list = filter(lambda x: isinstance(x, str) and x not in tag_list, args)
            if append_list == args:
                optional_tags.append(tuple(append_list))

        identifier = pp.Word(pp.alphanums + "_" + "-" + "'")

        expr = pp.infixNotation(identifier, [
            ("AND", 2, pp.opAssoc.LEFT, parse_and),
            ("OR", 2, pp.opAssoc.LEFT, parse_or),
            ("and", 2, pp.opAssoc.LEFT, parse_and),
            ("or", 2, pp.opAssoc.LEFT, parse_or),
        ])

        expr.parseString(expression)

        if expression and not required_tags and expression not in tag_list:
            # single tag in the expression
            required_tags.append(expression)

        return required_tags, optional_tags

    @staticmethod
    def _apply_object(_panobject, pandevice_object):
        """Apply (destructive) the given pandevice_object on the given pandevice
        """
        _panobject.add(pandevice_object)
        pandevice_object.apply()

    @staticmethod
    def _create_object(_panobject, pandevice_object):
        """Create (non-destructive) the given pandevice_object on the given pandevice
        """
        _panobject.add(pandevice_object)
        pandevice_object.create()

    @staticmethod
    def _link_policy_objects(pandevice_policy, objs, string_values):
        """Link the objects to the policy
        """

        def _transform(l, v):
            transformed_value = l
            if v != 'any':
                # temporary fix until fixed in pandevice
                if l == ['any']:
                    l = 'any'
                if isinstance(l, str):
                    if l == 'any':
                        transformed_value = []
                    else:
                        transformed_value = [l]
                    transformed_value.append(v)
                elif v not in l:
                    transformed_value.append(v)
            else:
                transformed_value = v
            return transformed_value

        for key, value in objs.iteritems():
            for obj in value.values():
                if key == 'source_addresses':
                    pandevice_policy.source = _transform(pandevice_policy.source, obj.name)
                elif key == 'destination_addresses':
                    pandevice_policy.destination = _transform(pandevice_policy.destination, obj.name)
                elif key == 'applications':
                    pandevice_policy.application = _transform(pandevice_policy.application, obj.name)
                elif key == 'services':
                    pandevice_policy.service = _transform(pandevice_policy.service, obj.name)

        for key, value in string_values.iteritems():
            if isinstance(value, list):
                # these keys are guaranteed to ne list values
                for lv in value:
                    if key == 'source_zones':
                        pandevice_policy.fromzone = _transform(pandevice_policy.fromzone, lv)
                    elif key == 'destination_zones':
                        pandevice_policy.tozone = _transform(pandevice_policy.tozone, lv)
            elif key == 'action':
                pandevice_policy.action = value
            elif key == 'name':
                pandevice_policy.name = value
            elif key == 'description':
                pandevice_policy.description = pandevice_policy.description + '\n' + value
            elif key == 'logging':
                if value == 'start' or value == 'both':
                    pandevice_policy.log_start = True
                if value == 'end' or value == 'both':
                    pandevice_policy.log_end = True

    @abc.abstractmethod
    def _get_config(self):

        # base and predefined refresh, this may take some time
        self.device.refresh()
        self.device.predefined.refreshall()

    @abc.abstractmethod
    def _parse_addresses(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def _parse_address_groups(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def _parse_services(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def _parse_service_groups(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def _parse_policies(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def apply_candidate_policy(self, candidate_policy, commit=False):
        raise NotImplementedError()

    @abc.abstractmethod
    def apply_policy(self, policy, commit=False):
        raise NotImplementedError()

    @abc.abstractmethod
    def _parse_applications(self):
        raise NotImplementedError()

    @abc.abstractmethod
    def _parse_application_groups(self):
        raise NotImplementedError()