def validate(self) -> bool:
        """
        Is the rule valid
        """
        valid = super().validate()

        if not hasattr(self, "type"):
            self.add_validation_error("{0} does not have type".format(str(self)))
            valid = False
        elif (
            not hasattr(self, "inside-address")
            and getattr(self, "type") != "masquerade"
        ):
            self.add_validation_error(
                "{0} does not have inside address".format(str(self))
            )
            valid = False

        port_groups = [
            group.name for group in secondary_configs.get_port_groups(self.config_path)
        ]
        for connection in ["source", "destination"]:
            if hasattr(self, connection) and "port" in getattr(self, connection):
                if (
                    not type_checker.is_number(getattr(self, connection)["port"])
                    and getattr(self, connection)["port"] not in port_groups
                ):
                    self.add_validation_error(
                        "{0} has nonexistent {1} port group {2}".format(
                            str(self), connection, getattr(self, connection)["port"],
                        )
                    )
                    valid = False

        return valid
Esempio n. 2
0
    def commands(self) -> List[str]:
        """
        Get the command for this rule
        """
        command_base = "firewall name {0} rule {1} ".format(
            self.firewall_name, self.number)

        commands = [
            command_base + "action " + getattr(self, "action", "accept")
        ]

        if hasattr(self, "description"):
            # pylint: disable=no-member
            description = shlex.quote(self.description)
            if description[0] not in ["'", '"']:
                description = '"{0}"'.format(description)

            commands.append(command_base + "description " + description)

        for part in ["log", "protocol"]:
            if hasattr(self, part):
                commands.append(command_base + part + " " +
                                str(getattr(self, part)))

        if hasattr(self, "state"):
            # pylint: disable=no-member
            for state, enabled in self.state.items():
                commands.append(command_base +
                                "state {0} {1}".format(state, enabled))

        connections = ["source", "destination"]
        for connection in connections:
            if hasattr(self, connection):
                data = getattr(self, connection)

                if "address" in data:
                    if type_checker.is_ip_address(
                            data["address"]) or type_checker.is_cidr(
                                data["address"]):
                        commands.append(command_base + connection +
                                        " address " + data["address"])
                    else:
                        # Address groups defined by hosts or statically, so can't know
                        # this exhaustively, unlike port groups
                        # So have to assume user knows what they're doing on this one
                        commands.append(command_base + connection +
                                        " group address-group " +
                                        data["address"])

                if "port" in data:
                    if type_checker.is_number(data["port"]):
                        commands.append(command_base + connection + " port " +
                                        str(data["port"]))
                    else:
                        commands.append(command_base + connection +
                                        " group port-group " + data["port"])

        return commands
Esempio n. 3
0
 def _load_rules(self):
     """
     Load rules for this firewall
     """
     for rule_path in file_paths.get_config_files(
         [self.config_path, file_paths.NAT_FOLDER,]
     ):
         if type_checker.is_number(rule_path.split(path.sep)[-1].rstrip(".yaml")):
             self.add_rule(
                 {
                     "number": rule_path.split(path.sep)[-1].rstrip(".yaml"),
                     "config_path": self.config_path,
                     **(file_paths.load_yaml_from_file(rule_path)),
                 }
             )
def test_is_number():
    """
    .
    """
    assert not type_checker.is_number([1]), "Array not number"
    assert not type_checker.is_number({1: 2}), "Dict not number"
    assert not type_checker.is_number("abc"), "String is not number"
    assert type_checker.is_number("123"), "String is number"
    assert type_checker.is_number(1), "Is number"
    assert type_checker.is_number(-1), "Negative is number"
Esempio n. 5
0
    def validate(self) -> bool:
        """
        Is the rule valid
        """
        valid = super().validate()

        port_groups = [
            group.name
            for group in secondary_configs.get_port_groups(self.config_path)
        ]
        for connection in ["source", "destination"]:
            if hasattr(self, connection) and "port" in getattr(
                    self, connection):
                if (not type_checker.is_number(
                        getattr(self, connection)["port"]) and getattr(
                            self, connection)["port"] not in port_groups):
                    self.add_validation_error(
                        "Rule {0} has nonexistent {1} port group {2}".format(
                            self.number, connection,
                            getattr(self, connection)["port"]))
                    valid = False

        return valid
    def commands(self) -> List[str]:
        """
        Get the command for this rule
        """
        commands = []
        command_base = "service nat rule {0} ".format(self.number)

        if hasattr(self, "description"):
            # pylint: disable=no-member
            description = shlex.quote(self.description)
            if description[0] not in ["'", '"']:
                description = '"{0}"'.format(description)

            commands.append(command_base + "description " + description)

        for part in [
            "log",
            "protocol",
            "type",
            "inbound-interface",
            "outbound-interface",
        ]:
            if hasattr(self, part):
                commands.append(command_base + part + " " + str(getattr(self, part)))

        connections = ["source", "destination", "inside-address"]
        for connection in connections:
            if hasattr(self, connection):
                data = getattr(self, connection)

                if "address" in data:
                    if type_checker.is_ip_address(
                        data["address"]
                    ) or type_checker.is_cidr(data["address"]):
                        commands.append(
                            command_base + connection + " address " + data["address"]
                        )
                    # Address groups defined by hosts or statically, so can't know
                    # this exhaustively, unlike port groups
                    # So have to assume user knows what they're doing on this one
                    else:
                        commands.append(
                            command_base
                            + connection
                            + " group address-group "
                            + data["address"]
                        )

                if "port" in data:
                    if type_checker.is_number(data["port"]):
                        commands.append(
                            command_base + connection + " port " + str(data["port"])
                        )
                    else:
                        commands.append(
                            command_base
                            + connection
                            + " group port-group "
                            + data["port"]
                        )

        return commands
Esempio n. 7
0
    def is_consistent(self) -> bool:
        """
        Check configuration for consistency
        """
        consistent = True
        port_groups = secondary_configs.get_port_groups(self.config_path)
        port_group_names = [group.name for group in port_groups]
        for port in getattr(self, "forward-ports", []):
            if (not type_checker.is_number(port)
                    and not isinstance(port, dict)
                    and port not in port_group_names):
                self.add_validation_error(
                    "Port Group {0} not defined for forwarding in {1}".format(
                        port, str(self)))
                consistent = False

        for hairpin in getattr(self, "hairpin-ports", []):
            for port in [
                    hairpin["connection"].get("destination",
                                              {}).get("port", 0),
                    hairpin["connection"].get("source", {}).get("port", 0),
            ]:
                if not type_checker.is_number(
                        port) and port not in port_group_names:
                    self.add_validation_error(
                        "Port Group {0} not defined for hairpin in {1}".format(
                            port, str(self)))
                    consistent = False

        for connection in getattr(self, "connections", []):
            source_port = connection.get("source", {}).get("port", 0)
            if (not type_checker.is_number(source_port)
                    and source_port not in port_group_names):
                self.add_validation_error(
                    "Source Port Group {0} not defined for "
                    "{1} connection in {2}".format(
                        source_port,
                        "allowed" if connection["allow"] else "blocked",
                        str(self),
                    ))
                consistent = False

            destination_port = connection.get("destination", {}).get("port", 0)
            if (not type_checker.is_number(destination_port)
                    and destination_port not in port_group_names):
                self.add_validation_error(
                    "Destination Port Group {0} not defined for "
                    "{1} connection in {2}".format(
                        destination_port,
                        "allowed" if connection["allow"] else "blocked",
                        str(self),
                    ))
                consistent = False

        # Check for duplicate rule values
        duplicate_rules = list(
            filter(
                lambda rule: rule is not None,
                utility.get_duplicates([
                    connection["rule"] if "rule" in connection else None
                    for connection in self.connections
                ]),
            ))
        if duplicate_rules:
            self.add_validation_error(
                str(self) + " has duplicate firewall rules: " +
                ", ".join([str(rule) for rule in duplicate_rules]))
            consistent = False

        for connection in self.connections:
            rule = connection.get("rule", None)
            if not rule:
                continue

            # Ensure the firewall rule numbers don't conflict with the numbers
            # set in a host
            for firewall in self.network.firewalls_by_direction.values():
                if any([
                        firewall_rule.number == rule
                        for firewall_rule in firewall.rules
                ]):
                    self.add_validation_error(
                        "{0} has conflicting connection rule with {1}, "
                        "rule number {2}".format(str(self), str(firewall),
                                                 rule))
                    consistent = False

            # Ensure either the source or destination contains this host
            # in each connection, otherwise can't know what firewall to add a rule to
            source = None
            destination = None
            if "source" in connection and "address" in connection["source"]:
                source = connection["source"]["address"]
            if "destination" in connection and "address" in connection[
                    "destination"]:
                destination = connection["destination"]["address"]

            if source is None and destination is None:
                self.add_validation_error(
                    str(self) +
                    " has connection with no source address or destination address"
                )
                consistent = False
            elif (self.address not in [source, destination] and not any([
                    group for group in getattr(self, "address-groups", [])
                    if group in [source, destination]
            ]) and not utility.address_in_subnet(source, self.address) and
                  not utility.address_in_subnet(destination, self.address)):
                self.add_validation_error(
                    str(self) +
                    " has connection where its address is not used in source or destination"
                )
                consistent = False

        return consistent
Esempio n. 8
0
    def add_firewall_rules(self):
        """
        Add rules to the firewalls in the network for the host's connections
        """
        for connection in self.connections:
            # Must be either source or destination to be valid, so
            # only have to check for the source to match
            connection_is_source = "source" in connection and (
                connection["source"].get("address", "") == self.address
                or any([
                    group == connection["source"].get("address", "")
                    for group in getattr(self, "address-groups", [])
                ]) or utility.address_in_subnet(
                    connection["source"].get("address", ""), self.address))

            rule_properties = {
                "action": "accept" if connection["allow"] else "drop",
                "protocol": connection.get("protocol", "tcp_udp"),
                "log": "enable" if connection.get("log", False) else "disable",
                "config_path": self.config_path,
            }

            for attribute in ["rule", "description", "source", "destination"]:
                if attribute in connection:
                    rule_attribute = attribute if attribute != "rule" else "number"
                    rule_properties[rule_attribute] = connection[attribute]

            self.network.firewalls_by_direction[
                "in" if connection_is_source else "out"].add_rule(
                    rule_properties)

        for forward in getattr(self, "forward-ports", []):
            nat_rule_properties = {
                "description":
                "Forward port {0} to {1}".format(
                    list(forward.keys())[0]
                    if isinstance(forward, dict) else forward,
                    self.name,
                ),
                "config_path":
                self.config_path,
                "type":
                "destination",
                "protocol":
                "tcp_udp",
                "inbound-interface":
                "eth0",
                "inside-address": {
                    "address": self.address
                },
            }

            if type_checker.is_string(forward) or type_checker.is_number(
                    forward):
                nat_rule_properties["destination"] = {"port": forward}
            elif type_checker.is_translated_port(forward):
                nat_rule_properties["destination"] = {
                    "port": list(forward.keys())[0]
                }
                nat_rule_properties["inside-address"]["port"] = list(
                    forward.values())[0]

            self.network.nat.add_rule(nat_rule_properties)

        for hairpin in getattr(self, "hairpin-ports", []):
            nat_rule_properties = {
                "description": hairpin.get("description", ""),
                "config_path": self.config_path,
                "type": "destination",
                "protocol": "tcp_udp",
                "inside-address": {
                    "address": self.address
                },
                "inbound-interface": hairpin["interface"],
                # Hairpin should default to the external addresses address group
                # Otherwise, it will redirect ALL traffic bound for those ports
                # to this host, which is likely NEVER what is desired
                # This can be overridden using a setting on the rule, however
                "destination": {
                    "address": "external-addresses"
                },
            }

            if "inside-port" in hairpin:
                nat_rule_properties["inside-address"]["port"] = hairpin[
                    "inside-port"]

            for conn in ["source", "destination"]:
                if hairpin["connection"].get(conn, {}):
                    if conn not in nat_rule_properties:
                        nat_rule_properties[conn] = {}

                    nat_rule_properties[conn].update(
                        hairpin["connection"][conn])

            self.network.nat.add_rule(nat_rule_properties)
Esempio n. 9
0
from ubiquiti_config_generator import secondary_configs, type_checker, utility
from ubiquiti_config_generator.nodes.validatable import Validatable

HOST_TYPES = {
    "name":
    type_checker.is_name,
    "address":
    type_checker.is_ip_address,
    "mac":
    type_checker.is_mac,
    "address-groups":
    lambda groups: all([type_checker.is_string(group) for group in groups]),
    "forward-ports":
    lambda ports: all([
        type_checker.is_number(port) or type_checker.is_string(port) or
        type_checker.is_translated_port(port) for port in ports
    ]),
    # Hairpin needs to contain:
    # connection (at least one of):
    #     source: address and/or port
    #     destination: address and/or port
    # description
    # interface: ethernet interface to redirect
    "hairpin-ports":
    lambda ports: all([
        type_checker.is_source_destination(port["connection"]) and type_checker
        .is_string(port["interface"]) and type_checker.is_string(port[
            "description"]) and type_checker.is_number(
                port.get("inside-port", 0)) for port in ports
    ]),
Esempio n. 10
0
Contains port groups
"""
import shlex
from typing import List

from ubiquiti_config_generator.nodes.validatable import Validatable
from ubiquiti_config_generator import type_checker, utility

PORT_GROUP_TYPES = {
    "name":
    type_checker.is_name,
    "description":
    type_checker.is_description,
    "ports":
    lambda ports: ports and all(
        [type_checker.is_number(port) for port in ports]),
}


class PortGroup(Validatable):
    """
    Represents a named grouping of ports
    """
    def __init__(self,
                 name: str,
                 ports: List[int] = None,
                 description: str = None):
        super().__init__(PORT_GROUP_TYPES, ["ports"])
        self.__name = name
        self.__ports = ports or []
        if description: