Example #1
0
def validate_endpoint(config, endpoint):
    """
    Ensures that the supplied endpoint is valid. Once this routine has returned
    successfully, we know that all required fields are present and have valid
    values.

    :param config: configuration structure
    :param endpoint: endpoint dictionary as read from etcd
    :raises ValidationFailed
    """
    issues = []

    if not isinstance(endpoint, dict):
        raise ValidationFailed("Expected endpoint to be a dict.")

    if "state" not in endpoint:
        issues.append("Missing 'state' field.")
    elif endpoint["state"] not in ("active", "inactive"):
        issues.append("Expected 'state' to be one of active/inactive.")

    for field in ["name", "mac", "profile_id"]:
        if field not in endpoint:
            issues.append("Missing '%s' field." % field)
        elif not isinstance(endpoint[field], StringTypes):
            issues.append("Expected '%s' to be a string; got %r." %
                          (field, endpoint[field]))

    if "name" in endpoint:
        if not endpoint["name"].startswith(config.IFACE_PREFIX):
            issues.append("Interface %r does not start with %r." %
                          (endpoint["name"], config.IFACE_PREFIX))

    for version in (4, 6):
        nets = "ipv%d_nets" % version
        if nets not in endpoint:
            issues.append("Missing network %s." % nets)
        else:
            for ip in endpoint.get(nets, []):
                if not common.validate_cidr(ip, version):
                    issues.append("IP address %r is not a valid IPv%d CIDR." %
                                  (ip, version))
                    break

        gw_key = "ipv%d_gateway" % version
        try:
            gw_str = endpoint[gw_key]
            if gw_str is not None and not common.validate_ip_addr(gw_str,
                                                                  version):
                issues.append("%s is not a valid IPv%d gateway address." %
                              (gw_key, version))
        except KeyError:
            pass

    if issues:
        raise ValidationFailed(" ".join(issues))
Example #2
0
    def test_validate_cidr(self):
        self.assertTrue(common.validate_cidr("1.2.3.4", 4))
        self.assertFalse(common.validate_cidr("1.2.3.4.5", 4))
        self.assertTrue(common.validate_cidr("1.2.3.4/32", 4))
        self.assertTrue(common.validate_cidr("1.2.3", 4))
        self.assertFalse(common.validate_cidr("bloop", 4))
        self.assertFalse(common.validate_cidr("::", 4))
        self.assertFalse(common.validate_cidr("2001::abc", 4))
        self.assertFalse(common.validate_cidr("2001::a/64", 4))

        self.assertFalse(common.validate_cidr("1.2.3.4", 6))
        self.assertFalse(common.validate_cidr("1.2.3.4.5", 6))
        self.assertFalse(common.validate_cidr("1.2.3.4/32", 6))
        self.assertFalse(common.validate_cidr("1.2.3", 6))
        self.assertFalse(common.validate_cidr("bloop", 6))
        self.assertTrue(common.validate_cidr("::", 6))
        self.assertTrue(common.validate_cidr("2001::abc", 6))
        self.assertTrue(common.validate_cidr("2001::a/64", 6))

        self.assertTrue(common.validate_cidr("1.2.3.4", None))
        self.assertFalse(common.validate_cidr("1.2.3.4.5", None))
        self.assertTrue(common.validate_cidr("1.2.3.4/32", None))
        self.assertTrue(common.validate_cidr("1.2.3", None))
        self.assertFalse(common.validate_cidr("bloop", None))
        self.assertTrue(common.validate_cidr("::", None))
        self.assertTrue(common.validate_cidr("2001::abc", None))
        self.assertTrue(common.validate_cidr("2001::a/64", None))

        self.assertFalse(common.validate_cidr(None, None))
Example #3
0
    def test_validate_cidr(self):
        self.assertTrue(common.validate_cidr("1.2.3.4", 4))
        self.assertFalse(common.validate_cidr("1.2.3.4.5", 4))
        self.assertTrue(common.validate_cidr("1.2.3.4/32", 4))
        self.assertTrue(common.validate_cidr("1.2.3", 4))
        self.assertFalse(common.validate_cidr("bloop", 4))
        self.assertFalse(common.validate_cidr("::", 4))
        self.assertFalse(common.validate_cidr("2001::abc", 4))
        self.assertFalse(common.validate_cidr("2001::a/64", 4))

        self.assertFalse(common.validate_cidr("1.2.3.4", 6))
        self.assertFalse(common.validate_cidr("1.2.3.4.5", 6))
        self.assertFalse(common.validate_cidr("1.2.3.4/32", 6))
        self.assertFalse(common.validate_cidr("1.2.3", 6))
        self.assertFalse(common.validate_cidr("bloop", 6))
        self.assertTrue(common.validate_cidr("::", 6))
        self.assertTrue(common.validate_cidr("2001::abc", 6))
        self.assertTrue(common.validate_cidr("2001::a/64", 6))

        self.assertTrue(common.validate_cidr("1.2.3.4", None))
        self.assertFalse(common.validate_cidr("1.2.3.4.5", None))
        self.assertTrue(common.validate_cidr("1.2.3.4/32", None))
        self.assertTrue(common.validate_cidr("1.2.3", None))
        self.assertFalse(common.validate_cidr("bloop", None))
        self.assertTrue(common.validate_cidr("::", None))
        self.assertTrue(common.validate_cidr("2001::abc", None))
        self.assertTrue(common.validate_cidr("2001::a/64", None))

        self.assertFalse(common.validate_cidr(None, None))
Example #4
0
def update_ipsets(type, descr, suffix, rule_list, ipset_addr, ipset_port,
                  ipset_icmp, tmp_ipset_addr, tmp_ipset_port, tmp_ipset_icmp):
    """
    Update the ipsets with a given set of rules. If a rule is invalid we do
    not throw an exception or give up, but just log an error and continue.
    """
    for rule in rule_list:
        if rule.get('cidr') is None:
            log.error("Invalid %s rule without cidr for %s : %s", descr,
                      suffix, rule)
            continue

        if ((type == IPV4 and not common.validate_cidr(rule['cidr'], 4)) or
            (type == IPV6 and not common.validate_cidr(rule['cidr'], 6))):
            log.error("Invalid CIDR in %s rule cidr for %s : %s", descr,
                      suffix, rule)
            continue

        #*********************************************************************#
        #* The ipset format is something like "10.11.1.3,udp:1-15"           *#
        #* Further valid examples include                                    *#
        #*   10.11.1.0/24                                                    *#
        #*   10.11.1.0/24,tcp                                                *#
        #*   10.11.1.0/24,80                                                 *#
        #*                                                                   *#
        #*********************************************************************#
        if rule['cidr'].endswith("/0"):
            #*****************************************************************#
            #* We have to handle any CIDR with a "/0" specially, since we    *#
            #* split it into two ipsets entries; ipsets cannot have zero     *#
            #* CIDR length in bits.                                          *#
            #*****************************************************************#
            if type == IPV4:
                cidrs = ["0.0.0.0/1", "128.0.0.0/1"]
            else:
                cidrs = ["::/1", "8000::/1"]
        else:
            cidrs = [rule['cidr']]

        #*********************************************************************#
        #* Now handle the protocol. There are three types of protocol. tcp / *#
        #* sctp /udp / udplite have an optional port. icmp / ipv6-icmp have  *#
        #* an optional type and code. Anything else doesn't have ports.      *#
        #*                                                                   *#
        #* We build the value to insert without the CIDR, then prepend the   *#
        #* CIDR later (since we may need to use two CIDRs).                  *#
        #*********************************************************************#
        protocol = rule.get('protocol')
        port = rule.get('port')
        icmp_type = rule.get('icmp_type')
        icmp_code = rule.get('icmp_code')

        if protocol is None:
            if rule.get('port') is not None:
                # No protocol, so port is not allowed.
                log.error(
                    "Invalid %s rule with port but no protocol for %s : %s",
                    descr, suffix, rule)
                continue
            ipset_value = ""
            ipset = tmp_ipset_addr
        elif protocol in ("tcp", "sctp", "udp", "udplite"):
            if port is None:
                # No port implies port range 1 to 65535.
                ipset_value = ",%s:1-65535" % (protocol)
                ipset = tmp_ipset_port
            elif isinstance(port, list) and len(port) == 2:
                # List of two ports - port range
                if (not common.validate_port(str(port[0]))
                        or not common.validate_port(str(port[1]))):
                    # Port range was not two valid ports.
                    log.error("Invalid port range in %s rule for %s : %s",
                              descr, suffix, rule)
                    continue
                ipset_value = ",%s:%s-%s" % (protocol, port[0], port[1])
                ipset = tmp_ipset_port
            else:
                if not common.validate_port(str(port)):
                    # Port was supplied but was not an integer or range.
                    log.error("Invalid port in %s rule for %s : %s", descr,
                              suffix, rule)
                    continue

                # An integer port was specified.
                ipset_value = ",%s:%s" % (protocol, port)
                ipset = tmp_ipset_port
        elif protocol in ("icmp", "ipv6-icmp"):
            if rule.get('port') is not None:
                # No protocol, so port is not allowed.
                log.error(
                    "Invalid %s rule for %s with port for protocol %s : %s",
                    descr, suffix, protocol, rule)
                continue

            if (icmp_type is None and icmp_code is not None):
                # A code but no type - not allowed.
                log.error(
                    "Invalid %s rule with ICMP code but no type for %s : %s",
                    descr, suffix, rule)
                continue
            if icmp_type is None:
                # No type - all ICMP to / from the cidr, so use the ICMP ipset.
                ipset_value = ""
                ipset = tmp_ipset_icmp
            else:
                try:
                    # Assume integer ICMP type first.
                    int(icmp_type)
                    if icmp_code is None:
                        # Code defaults to 0 if not supplied.
                        icmp_code = 0
                    ipset_value = ",%s:%s/%s" % (protocol, icmp_type,
                                                 icmp_code)
                    ipset = tmp_ipset_port
                except ValueError:
                    # Not an integer ICMP type - must be a string code name.
                    ipset_value = ",%s:%s" % (protocol, icmp_type)
                    ipset = tmp_ipset_port
        else:
            if port is not None:
                # The supplied protocol does not allow ports.
                log.error(
                    "Invalid %s rule with port but no protocol for %s : %s",
                    descr, suffix, rule)
                continue
            # ipsets require a port number of 0 to be specified in a hash:net
            # set for any protocol other than tcp / udp / sctp / udplite.
            ipset_value = ",%s:0" % (protocol)
            ipset = tmp_ipset_port

        # Now add those values to the ipsets.
        for cidr in cidrs:
            try:
                ipsets.add(ipset, cidr + ipset_value)
            except FailedSystemCall:
                log.exception("Failed to add %s rule (%s) for %s", descr,
                              cidr + ipset_value, suffix)

    # Now that we have filled the tmp ipset, swap it with the real one.
    ipsets.swap(tmp_ipset_addr, ipset_addr)
    ipsets.swap(tmp_ipset_port, ipset_port)
    ipsets.swap(tmp_ipset_icmp, ipset_icmp)

    # Get the temporary ipsets clean again - we leave them existing but empty.
    ipsets.flush(tmp_ipset_port)
    ipsets.flush(tmp_ipset_addr)
    ipsets.flush(tmp_ipset_icmp)
Example #5
0
def validate_rules(rules):
    """
    Ensures that the supplied rules are valid. Once this routine has returned
    successfully, we know that all required fields are present and have valid
    values.

    :param rules: rules list as read from etcd
    :raises ValidationFailed
    """
    issues = []

    if not isinstance(rules, dict):
        raise ValidationFailed("Expected rules to be a dict.")

    for dirn in ("inbound_rules", "outbound_rules"):
        if dirn not in rules:
            issues.append("No %s in rules." % dirn)
            continue

        if not isinstance(rules[dirn], list):
            issues.append("Expected rules[%s] to be a dict." % dirn)
            continue

        for rule in rules[dirn]:
            # Absolutely all fields are optional, but some have valid and
            # invalid values.
            protocol = rule.get('protocol')
            if (protocol is not None and
                not protocol in [ "tcp", "udp", "icmp", "icmpv6" ]):
                    issues.append("Invalid protocol in rule %s." % rule)

            ip_version = rule.get('ip_version')
            if (ip_version is not None and
                not ip_version in [ 4, 6 ]):
                # Bad IP version prevents further validation
                issues.append("Invalid ip_version in rule %s." % rule)
                continue

            if ip_version == 4 and protocol == "icmpv6":
                issues.append("Using icmpv6 with IPv4 in rule %s." % rule)
            if ip_version == 6 and protocol == "icmp":
                issues.append("Using icmp with IPv6 in rule %s." % rule)

            # TODO: Validate that src_tag and dst_tag contain only valid characters.

            for key in ("src_net", "dst_net"):
                network = rule.get(key)
                if (network is not None and
                    not common.validate_cidr(rule[key], ip_version)):
                    issues.append("Invalid CIDR (version %s) in rule %s." %
                                  (ip_version, rule))

            for key in ("src_ports", "dst_ports"):
                ports = rule.get(key)
                if (ports is not None and
                    not isinstance(ports, list)):
                    issues.append("Expected ports to be a list in rule %s."
                                  % rule)
                    continue

                if ports is not None:
                    for port in ports:
                        error = validate_rule_port(port)
                        if error:
                            issues.append("Invalid port %s (%s) in rule %s." %
                                          (port, error, rule))

            action = rule.get('action')
            if (action is not None and
                    action not in ("allow", "deny")):
                issues.append("Invalid action in rule %s." % rule)

            icmp_type = rule.get('icmp_type')
            #TODO: firewall the icmp_type too

    if issues:
        raise ValidationFailed(" ".join(issues))