mirror of
https://github.com/docker/compose.git
synced 2025-07-22 13:14:29 +02:00
Merge pull request #5364 from nginth/4552-validate-config-subnet
Implement subnet config validation (fixes #4552)
This commit is contained in:
commit
d1633d8e9d
@ -294,7 +294,7 @@
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subnet": {"type": "string"}
|
||||
"subnet": {"type": "string", "format": "subnet_ip_address"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
@ -323,7 +323,7 @@
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subnet": {"type": "string"}
|
||||
"subnet": {"type": "string", "format": "subnet_ip_address"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
@ -368,7 +368,7 @@
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subnet": {"type": "string"}
|
||||
"subnet": {"type": "string", "format": "subnet_ip_address"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
@ -412,7 +412,7 @@
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subnet": {"type": "string"}
|
||||
"subnet": {"type": "string", "format": "subnet_ip_address"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
@ -420,7 +420,7 @@
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subnet": {"type": "string"}
|
||||
"subnet": {"type": "string", "format": "subnet_ip_address"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
@ -418,7 +418,7 @@
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"subnet": {"type": "string"}
|
||||
"subnet": {"type": "string", "format": "subnet_ip_address"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
|
@ -44,6 +44,31 @@ DOCKER_CONFIG_HINTS = {
|
||||
VALID_NAME_CHARS = '[a-zA-Z0-9\._\-]'
|
||||
VALID_EXPOSE_FORMAT = r'^\d+(\-\d+)?(\/[a-zA-Z]+)?$'
|
||||
|
||||
VALID_IPV4_SEG = r'(\d{1,2}|1\d{2}|2[0-4]\d|25[0-5])'
|
||||
VALID_IPV4_ADDR = "({IPV4_SEG}\.){{3}}{IPV4_SEG}".format(IPV4_SEG=VALID_IPV4_SEG)
|
||||
VALID_REGEX_IPV4_CIDR = "^{IPV4_ADDR}/(\d|[1-2]\d|3[0-2])$".format(IPV4_ADDR=VALID_IPV4_ADDR)
|
||||
|
||||
VALID_IPV6_SEG = r'[0-9a-fA-F]{1,4}'
|
||||
VALID_REGEX_IPV6_CIDR = "".join("""
|
||||
^
|
||||
(
|
||||
(({IPV6_SEG}:){{7}}{IPV6_SEG})|
|
||||
(({IPV6_SEG}:){{1,7}}:)|
|
||||
(({IPV6_SEG}:){{1,6}}(:{IPV6_SEG}){{1,1}})|
|
||||
(({IPV6_SEG}:){{1,5}}(:{IPV6_SEG}){{1,2}})|
|
||||
(({IPV6_SEG}:){{1,4}}(:{IPV6_SEG}){{1,3}})|
|
||||
(({IPV6_SEG}:){{1,3}}(:{IPV6_SEG}){{1,4}})|
|
||||
(({IPV6_SEG}:){{1,2}}(:{IPV6_SEG}){{1,5}})|
|
||||
(({IPV6_SEG}:){{1,1}}(:{IPV6_SEG}){{1,6}})|
|
||||
(:((:{IPV6_SEG}){{1,7}}|:))|
|
||||
(fe80:(:{IPV6_SEG}){{0,4}}%[0-9a-zA-Z]{{1,}})|
|
||||
(::(ffff(:0{{1,4}}){{0,1}}:){{0,1}}{IPV4_ADDR})|
|
||||
(({IPV6_SEG}:){{1,4}}:{IPV4_ADDR})
|
||||
)
|
||||
/(\d|[1-9]\d|1[0-1]\d|12[0-8])
|
||||
$
|
||||
""".format(IPV6_SEG=VALID_IPV6_SEG, IPV4_ADDR=VALID_IPV4_ADDR).split())
|
||||
|
||||
|
||||
@FormatChecker.cls_checks(format="ports", raises=ValidationError)
|
||||
def format_ports(instance):
|
||||
@ -64,6 +89,16 @@ def format_expose(instance):
|
||||
return True
|
||||
|
||||
|
||||
@FormatChecker.cls_checks("subnet_ip_address", raises=ValidationError)
|
||||
def format_subnet_ip_address(instance):
|
||||
if isinstance(instance, six.string_types):
|
||||
if not re.match(VALID_REGEX_IPV4_CIDR, instance) and \
|
||||
not re.match(VALID_REGEX_IPV6_CIDR, instance):
|
||||
raise ValidationError("should use the CIDR format")
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def match_named_volumes(service_dict, project_volumes):
|
||||
service_volumes = service_dict.get('volumes', [])
|
||||
for volume_spec in service_volumes:
|
||||
@ -391,7 +426,7 @@ def process_config_schema_errors(error):
|
||||
|
||||
def validate_against_config_schema(config_file):
|
||||
schema = load_jsonschema(config_file)
|
||||
format_checker = FormatChecker(["ports", "expose"])
|
||||
format_checker = FormatChecker(["ports", "expose", "subnet_ip_address"])
|
||||
validator = Draft4Validator(
|
||||
schema,
|
||||
resolver=RefResolver(get_resolver_path(), schema),
|
||||
|
@ -2846,6 +2846,94 @@ class PortsTest(unittest.TestCase):
|
||||
)
|
||||
|
||||
|
||||
class SubnetTest(unittest.TestCase):
|
||||
INVALID_SUBNET_TYPES = [
|
||||
None,
|
||||
False,
|
||||
10,
|
||||
]
|
||||
|
||||
INVALID_SUBNET_MAPPINGS = [
|
||||
"",
|
||||
"192.168.0.1/sdfsdfs",
|
||||
"192.168.0.1/",
|
||||
"192.168.0.1/33",
|
||||
"192.168.0.1/01",
|
||||
"192.168.0.1",
|
||||
"fe80:0000:0000:0000:0204:61ff:fe9d:f156/sdfsdfs",
|
||||
"fe80:0000:0000:0000:0204:61ff:fe9d:f156/",
|
||||
"fe80:0000:0000:0000:0204:61ff:fe9d:f156/129",
|
||||
"fe80:0000:0000:0000:0204:61ff:fe9d:f156/01",
|
||||
"fe80:0000:0000:0000:0204:61ff:fe9d:f156",
|
||||
"ge80:0000:0000:0000:0204:61ff:fe9d:f156/128",
|
||||
"192.168.0.1/31/31",
|
||||
]
|
||||
|
||||
VALID_SUBNET_MAPPINGS = [
|
||||
"192.168.0.1/0",
|
||||
"192.168.0.1/32",
|
||||
"fe80:0000:0000:0000:0204:61ff:fe9d:f156/0",
|
||||
"fe80:0000:0000:0000:0204:61ff:fe9d:f156/128",
|
||||
"1:2:3:4:5:6:7:8/0",
|
||||
"1::/0",
|
||||
"1:2:3:4:5:6:7::/0",
|
||||
"1::8/0",
|
||||
"1:2:3:4:5:6::8/0",
|
||||
"::/0",
|
||||
"::8/0",
|
||||
"::2:3:4:5:6:7:8/0",
|
||||
"fe80::7:8%eth0/0",
|
||||
"fe80::7:8%1/0",
|
||||
"::255.255.255.255/0",
|
||||
"::ffff:255.255.255.255/0",
|
||||
"::ffff:0:255.255.255.255/0",
|
||||
"2001:db8:3:4::192.0.2.33/0",
|
||||
"64:ff9b::192.0.2.33/0",
|
||||
]
|
||||
|
||||
def test_config_invalid_subnet_type_validation(self):
|
||||
for invalid_subnet in self.INVALID_SUBNET_TYPES:
|
||||
with pytest.raises(ConfigurationError) as exc:
|
||||
self.check_config(invalid_subnet)
|
||||
|
||||
assert "contains an invalid type" in exc.value.msg
|
||||
|
||||
def test_config_invalid_subnet_format_validation(self):
|
||||
for invalid_subnet in self.INVALID_SUBNET_MAPPINGS:
|
||||
with pytest.raises(ConfigurationError) as exc:
|
||||
self.check_config(invalid_subnet)
|
||||
|
||||
assert "should use the CIDR format" in exc.value.msg
|
||||
|
||||
def test_config_valid_subnet_format_validation(self):
|
||||
for valid_subnet in self.VALID_SUBNET_MAPPINGS:
|
||||
self.check_config(valid_subnet)
|
||||
|
||||
def check_config(self, subnet):
|
||||
config.load(
|
||||
build_config_details({
|
||||
'version': '3.5',
|
||||
'services': {
|
||||
'web': {
|
||||
'image': 'busybox'
|
||||
}
|
||||
},
|
||||
'networks': {
|
||||
'default': {
|
||||
'ipam': {
|
||||
'config': [
|
||||
{
|
||||
'subnet': subnet
|
||||
}
|
||||
],
|
||||
'driver': 'default'
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
|
||||
class InterpolationTest(unittest.TestCase):
|
||||
|
||||
@mock.patch.dict(os.environ)
|
||||
|
Loading…
x
Reference in New Issue
Block a user