registry: support ipv6 addresses
Current registry reference use a subset of dns and IPv4 addresses to represent a registry domain. Since registries are mostly compatible with rfc3986, that defines the URI generic syntax, this adds support for IPv6 enclosed in squared brackets based on the mentioned rfc. The regexp is only expanded to match on IPv6 addreses enclosed between square brackets, considering only regular IPv6 addresses represented as compressed or uncompressed, excluding special IPv6 address representations. Signed-off-by: Antonio Ojea <antonio.ojea.garcia@gmail.com>
This commit is contained in:
parent
3e4f8a0ab1
commit
53a6f7d7aa
5 changed files with 181 additions and 7 deletions
|
@ -22,7 +22,14 @@ func TestValidateReferenceName(t *testing.T) {
|
||||||
"127.0.0.1:5000/docker/docker",
|
"127.0.0.1:5000/docker/docker",
|
||||||
"127.0.0.1:5000/library/debian",
|
"127.0.0.1:5000/library/debian",
|
||||||
"127.0.0.1:5000/debian",
|
"127.0.0.1:5000/debian",
|
||||||
|
"192.168.0.1",
|
||||||
|
"192.168.0.1:80",
|
||||||
|
"192.168.0.1:8/debian",
|
||||||
|
"192.168.0.2:25000/debian",
|
||||||
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
|
"thisisthesongthatneverendsitgoesonandonandonthisisthesongthatnev",
|
||||||
|
"[fc00::1]:5000/docker",
|
||||||
|
"[fc00::1]:5000/docker/docker",
|
||||||
|
"[fc00:1:2:3:4:5:6:7]:5000/library/debian",
|
||||||
|
|
||||||
// This test case was moved from invalid to valid since it is valid input
|
// This test case was moved from invalid to valid since it is valid input
|
||||||
// when specified with a hostname, it removes the ambiguity from about
|
// when specified with a hostname, it removes the ambiguity from about
|
||||||
|
@ -40,6 +47,11 @@ func TestValidateReferenceName(t *testing.T) {
|
||||||
"docker///docker",
|
"docker///docker",
|
||||||
"docker.io/docker/Docker",
|
"docker.io/docker/Docker",
|
||||||
"docker.io/docker///docker",
|
"docker.io/docker///docker",
|
||||||
|
"[fc00::1]",
|
||||||
|
"[fc00::1]:5000",
|
||||||
|
"fc00::1:5000/debian",
|
||||||
|
"[fe80::1%eth0]:5000/debian",
|
||||||
|
"[2001:db8:3:4::192.0.2.33]:5000/debian",
|
||||||
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
"1a3f5e7d9c1b3a5f7e9d1c3b5a7f9e1d3c5b7a9f1e3d5d7c9b1a3f5e7d9c1b3a",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
//
|
//
|
||||||
// reference := name [ ":" tag ] [ "@" digest ]
|
// reference := name [ ":" tag ] [ "@" digest ]
|
||||||
// name := [domain '/'] path-component ['/' path-component]*
|
// name := [domain '/'] path-component ['/' path-component]*
|
||||||
// domain := domain-component ['.' domain-component]* [':' port-number]
|
// domain := host [':' port-number]
|
||||||
|
// host := domain-name | IPv4address | \[ IPv6address \] ; rfc3986 appendix-A
|
||||||
|
// domain-name := domain-component ['.' domain-component]*
|
||||||
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
|
||||||
// port-number := /[0-9]+/
|
// port-number := /[0-9]+/
|
||||||
// path-component := alpha-numeric [separator alpha-numeric]*
|
// path-component := alpha-numeric [separator alpha-numeric]*
|
||||||
|
|
|
@ -171,6 +171,101 @@ func TestReferenceParse(t *testing.T) {
|
||||||
repository: "foo/foo_bar.com",
|
repository: "foo/foo_bar.com",
|
||||||
tag: "8080",
|
tag: "8080",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: "192.168.1.1",
|
||||||
|
repository: "192.168.1.1",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "192.168.1.1:tag",
|
||||||
|
repository: "192.168.1.1",
|
||||||
|
tag: "tag",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "192.168.1.1:5000",
|
||||||
|
repository: "192.168.1.1",
|
||||||
|
tag: "5000",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "192.168.1.1/repo",
|
||||||
|
domain: "192.168.1.1",
|
||||||
|
repository: "192.168.1.1/repo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "192.168.1.1:5000/repo",
|
||||||
|
domain: "192.168.1.1:5000",
|
||||||
|
repository: "192.168.1.1:5000/repo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "192.168.1.1:5000/repo:5050",
|
||||||
|
domain: "192.168.1.1:5000",
|
||||||
|
repository: "192.168.1.1:5000/repo",
|
||||||
|
tag: "5050",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:db8::1]",
|
||||||
|
err: ErrReferenceInvalidFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:db8::1]:5000",
|
||||||
|
err: ErrReferenceInvalidFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:db8::1]:tag",
|
||||||
|
err: ErrReferenceInvalidFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:db8::1]/repo",
|
||||||
|
domain: "[2001:db8::1]",
|
||||||
|
repository: "[2001:db8::1]/repo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:db8:1:2:3:4:5:6]/repo:tag",
|
||||||
|
domain: "[2001:db8:1:2:3:4:5:6]",
|
||||||
|
repository: "[2001:db8:1:2:3:4:5:6]/repo",
|
||||||
|
tag: "tag",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:db8::1]:5000/repo",
|
||||||
|
domain: "[2001:db8::1]:5000",
|
||||||
|
repository: "[2001:db8::1]:5000/repo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:db8::1]:5000/repo:tag",
|
||||||
|
domain: "[2001:db8::1]:5000",
|
||||||
|
repository: "[2001:db8::1]:5000/repo",
|
||||||
|
tag: "tag",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:db8::1]:5000/repo@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
domain: "[2001:db8::1]:5000",
|
||||||
|
repository: "[2001:db8::1]:5000/repo",
|
||||||
|
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:db8::1]:5000/repo:tag@sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
domain: "[2001:db8::1]:5000",
|
||||||
|
repository: "[2001:db8::1]:5000/repo",
|
||||||
|
tag: "tag",
|
||||||
|
digest: "sha256:ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:db8::]:5000/repo",
|
||||||
|
domain: "[2001:db8::]:5000",
|
||||||
|
repository: "[2001:db8::]:5000/repo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[::1]:5000/repo",
|
||||||
|
domain: "[::1]:5000",
|
||||||
|
repository: "[::1]:5000/repo",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[fe80::1%eth0]:5000/repo",
|
||||||
|
err: ErrReferenceInvalidFormat,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[fe80::1%@invalidzone]:5000/repo",
|
||||||
|
err: ErrReferenceInvalidFormat,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
for _, testcase := range referenceTestcases {
|
for _, testcase := range referenceTestcases {
|
||||||
failf := func(format string, v ...interface{}) {
|
failf := func(format string, v ...interface{}) {
|
||||||
|
|
|
@ -23,15 +23,40 @@ var (
|
||||||
alphaNumeric,
|
alphaNumeric,
|
||||||
optional(repeated(separator, alphaNumeric)))
|
optional(repeated(separator, alphaNumeric)))
|
||||||
|
|
||||||
// domainComponent restricts the registry domain component of a
|
// domainNameComponent restricts the registry domain component of a
|
||||||
// repository name to start with a component as defined by DomainRegexp
|
// repository name to start with a component as defined by DomainRegexp.
|
||||||
// and followed by an optional port.
|
domainNameComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`
|
||||||
domainComponent = `(?:[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])`
|
|
||||||
|
|
||||||
|
// ipv6address are enclosed between square brackets and may be represented
|
||||||
|
// in many ways, see rfc5952. Only IPv6 in compressed or uncompressed format
|
||||||
|
// are allowed, IPv6 zone identifiers (rfc6874) or Special addresses such as
|
||||||
|
// IPv4-Mapped are deliberately excluded.
|
||||||
|
ipv6address = expression(
|
||||||
|
literal(`[`), `(?:[a-fA-F0-9:]+)`, literal(`]`),
|
||||||
|
)
|
||||||
|
|
||||||
|
// domainName defines the structure of potential domain components
|
||||||
|
// that may be part of image names. This is purposely a subset of what is
|
||||||
|
// allowed by DNS to ensure backwards compatibility with Docker image
|
||||||
|
// names. This includes IPv4 addresses on decimal format.
|
||||||
|
domainName = expression(
|
||||||
|
domainNameComponent,
|
||||||
|
optional(repeated(literal(`.`), domainNameComponent)),
|
||||||
|
)
|
||||||
|
|
||||||
|
// host defines the structure of potential domains based on the URI
|
||||||
|
// Host subcomponent on rfc3986. It may be a subset of DNS domain name,
|
||||||
|
// or an IPv4 address in decimal format, or an IPv6 address between square
|
||||||
|
// brackets (excluding zone identifiers as defined by rfc6874 or special
|
||||||
|
// addresses such as IPv4-Mapped).
|
||||||
|
host = `(?:` + domainName + `|` + ipv6address + `)`
|
||||||
|
|
||||||
|
// allowed by the URI Host subcomponent on rfc3986 to ensure backwards
|
||||||
|
// compatibility with Docker image names.
|
||||||
domain = expression(
|
domain = expression(
|
||||||
domainComponent,
|
host,
|
||||||
optional(repeated(literal(`.`), domainComponent)),
|
|
||||||
optional(literal(`:`), `[0-9]+`))
|
optional(literal(`:`), `[0-9]+`))
|
||||||
|
|
||||||
// DomainRegexp defines the structure of potential domain components
|
// DomainRegexp defines the structure of potential domain components
|
||||||
// that may be part of image names. This is purposely a subset of what is
|
// that may be part of image names. This is purposely a subset of what is
|
||||||
// allowed by DNS to ensure backwards compatibility with Docker image
|
// allowed by DNS to ensure backwards compatibility with Docker image
|
||||||
|
|
|
@ -115,6 +115,46 @@ func TestDomainRegexp(t *testing.T) {
|
||||||
input: "Asdf.com", // uppercase character
|
input: "Asdf.com", // uppercase character
|
||||||
match: true,
|
match: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: "192.168.1.1:75050", // ipv4
|
||||||
|
match: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "192.168.1.1:750050", // port with more than 5 digits, it will fail on validation
|
||||||
|
match: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[fd00:1:2::3]:75050", // ipv6 compressed
|
||||||
|
match: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[fd00:1:2::3]75050", // ipv6 wrong port separator
|
||||||
|
match: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[fd00:1:2::3]::75050", // ipv6 wrong port separator
|
||||||
|
match: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[fd00:1:2::3%eth0]:75050", // ipv6 with zone
|
||||||
|
match: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[fd00123123123]:75050", // ipv6 wrong format, will fail in validation
|
||||||
|
match: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:75050", // ipv6 long format
|
||||||
|
match: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "[2001:0db8:85a3:0000:0000:8a2e:0370:7334]:750505", // ipv6 long format and invalid port, it will fail in validation
|
||||||
|
match: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "fd00:1:2::3:75050", // bad ipv6 without square brackets
|
||||||
|
match: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
r := regexp.MustCompile(`^` + DomainRegexp.String() + `$`)
|
r := regexp.MustCompile(`^` + DomainRegexp.String() + `$`)
|
||||||
for i := range hostcases {
|
for i := range hostcases {
|
||||||
|
|
Loading…
Reference in a new issue