From e2d156f080b12e390886424b903ed054a984d72a Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Wed, 9 Sep 2026 07:40:11 +0500 Subject: [PATCH 1/4] policy: strip default ports from input.http.host curl and similar tools include :443/:80 on the request URL. Policy then sees input.http.host as example.com:443, so an allow-list of example.com misses it. Drop those well-known ports; leave any other port in place. Signed-off-by: Dean Chen <862469039@qq.com> --- policy/validate.go | 22 ++++++++- policy/validate_test.go | 105 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/policy/validate.go b/policy/validate.go index 5bd41be4a7a0..451ccac734f8 100644 --- a/policy/validate.go +++ b/policy/validate.go @@ -6,6 +6,7 @@ import ( "fmt" "io/fs" "maps" + "net" "net/url" "path" "slices" @@ -532,6 +533,25 @@ func (p *Policy) Print(ctx print.Context, msg string) error { return nil } +// httpHostForPolicy returns the host used in policy input.http.host. +// Well-known default ports (http/80, https/443) are stripped so curl-style +// URLs like https://example.com:443 match allow-lists that contain example.com. +func httpHostForPolicy(scheme, host string) string { + h, port, err := net.SplitHostPort(host) + if err != nil { + return host + } + switch { + case scheme == "https" && port == "443", scheme == "http" && port == "80": + if strings.Contains(h, ":") { + return "[" + h + "]" + } + return h + default: + return host + } +} + func sourceToInput(ctx context.Context, getVerifier PolicyVerifierProvider, src *gwpb.ResolveSourceMetaResponse, platform *ocispecs.Platform, logf func(logrus.Level, string)) (Input, []string, error) { var inp Input var unknowns []string @@ -554,7 +574,7 @@ func sourceToInput(ctx context.Context, getVerifier PolicyVerifierProvider, src inp.HTTP = &HTTP{ URL: src.Source.Identifier, Schema: scheme, - Host: u.Host, + Host: httpHostForPolicy(scheme, u.Host), Path: u.Path, Query: u.Query(), } diff --git a/policy/validate_test.go b/policy/validate_test.go index 6936eac8d30b..3e8ebbed3a61 100644 --- a/policy/validate_test.go +++ b/policy/validate_test.go @@ -23,6 +23,27 @@ import ( "github.com/stretchr/testify/require" ) +func TestHTTPHostForPolicy(t *testing.T) { + tests := []struct { + scheme, host, want string + }{ + {"https", "example.com", "example.com"}, + {"https", "example.com:443", "example.com"}, + {"http", "example.com:80", "example.com"}, + {"https", "example.com:8443", "example.com:8443"}, + {"http", "example.com:8080", "example.com:8080"}, + {"https", "[2001:db8::1]", "[2001:db8::1]"}, + {"https", "[2001:db8::1]:443", "[2001:db8::1]"}, + {"https", "[2001:db8::1]:8443", "[2001:db8::1]:8443"}, + {"https", "example.com:443", "example.com"}, + } + for _, tt := range tests { + t.Run(tt.scheme+" "+tt.host, func(t *testing.T) { + require.Equal(t, tt.want, httpHostForPolicy(tt.scheme, tt.host)) + }) + } +} + func TestSourceToInputSingleSource(t *testing.T) { tm := time.Date(2024, 1, 2, 3, 4, 5, 0, time.UTC) @@ -133,6 +154,90 @@ func TestSourceToInputSingleSource(t *testing.T) { }, }, }, + { + name: "https-default-port-stripped-from-host", + src: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{ + Identifier: "https://example.com:443/foo.tar.gz", + }, + HTTP: &gwpb.ResolveSourceHTTPResponse{ + Checksum: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + }, + }, + expInput: Input{ + HTTP: &HTTP{ + URL: "https://example.com:443/foo.tar.gz", + Schema: "https", + Host: "example.com", + Path: "/foo.tar.gz", + Query: map[string][]string{}, + Checksum: "sha256:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + }, + }, + }, + { + name: "http-default-port-stripped-from-host", + src: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{ + Identifier: "http://example.com:80/foo.tar.gz", + }, + HTTP: &gwpb.ResolveSourceHTTPResponse{ + Checksum: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + }, + }, + expInput: Input{ + HTTP: &HTTP{ + URL: "http://example.com:80/foo.tar.gz", + Schema: "http", + Host: "example.com", + Path: "/foo.tar.gz", + Query: map[string][]string{}, + Checksum: "sha256:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + }, + }, + }, + { + name: "https-non-default-port-kept-on-host", + src: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{ + Identifier: "https://example.com:8443/foo.tar.gz", + }, + HTTP: &gwpb.ResolveSourceHTTPResponse{ + Checksum: "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + }, + }, + expInput: Input{ + HTTP: &HTTP{ + URL: "https://example.com:8443/foo.tar.gz", + Schema: "https", + Host: "example.com:8443", + Path: "/foo.tar.gz", + Query: map[string][]string{}, + Checksum: "sha256:cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + }, + }, + }, + { + name: "https-ipv6-default-port-stripped-from-host", + src: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{ + Identifier: "https://[2001:db8::1]:443/foo.tar.gz", + }, + HTTP: &gwpb.ResolveSourceHTTPResponse{ + Checksum: "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + }, + }, + expInput: Input{ + HTTP: &HTTP{ + URL: "https://[2001:db8::1]:443/foo.tar.gz", + Schema: "https", + Host: "[2001:db8::1]", + Path: "/foo.tar.gz", + Query: map[string][]string{}, + Checksum: "sha256:dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd", + }, + }, + }, { name: "local-source", src: &gwpb.ResolveSourceMetaResponse{ From 65cbef98819c51f1f67da294e3bbf271bd0049e6 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 9 Sep 2026 11:24:13 +0200 Subject: [PATCH 2/4] policy: simplify HTTP host normalization and strengthen coverage Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- policy/validate.go | 22 +++----- policy/validate_test.go | 117 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 120 insertions(+), 19 deletions(-) diff --git a/policy/validate.go b/policy/validate.go index 451ccac734f8..561eca518833 100644 --- a/policy/validate.go +++ b/policy/validate.go @@ -6,7 +6,6 @@ import ( "fmt" "io/fs" "maps" - "net" "net/url" "path" "slices" @@ -533,22 +532,13 @@ func (p *Policy) Print(ctx print.Context, msg string) error { return nil } -// httpHostForPolicy returns the host used in policy input.http.host. -// Well-known default ports (http/80, https/443) are stripped so curl-style -// URLs like https://example.com:443 match allow-lists that contain example.com. -func httpHostForPolicy(scheme, host string) string { - h, port, err := net.SplitHostPort(host) - if err != nil { - return host - } +func normalizeHTTPHost(u *url.URL) string { + port := strings.TrimLeft(u.Port(), "0") switch { - case scheme == "https" && port == "443", scheme == "http" && port == "80": - if strings.Contains(h, ":") { - return "[" + h + "]" - } - return h + case u.Scheme == "http" && port == "80", u.Scheme == "https" && port == "443": + return strings.TrimSuffix(u.Host, ":"+u.Port()) default: - return host + return u.Host } } @@ -574,7 +564,7 @@ func sourceToInput(ctx context.Context, getVerifier PolicyVerifierProvider, src inp.HTTP = &HTTP{ URL: src.Source.Identifier, Schema: scheme, - Host: httpHostForPolicy(scheme, u.Host), + Host: normalizeHTTPHost(u), Path: u.Path, Query: u.Query(), } diff --git a/policy/validate_test.go b/policy/validate_test.go index 3e8ebbed3a61..b2bb19531149 100644 --- a/policy/validate_test.go +++ b/policy/validate_test.go @@ -7,6 +7,7 @@ import ( "encoding/json" "errors" "fmt" + "net/url" "testing" "time" @@ -14,6 +15,8 @@ import ( slsa1 "github.com/in-toto/in-toto-golang/in_toto/slsa_provenance/v1" gwpb "github.com/moby/buildkit/frontend/gateway/pb" "github.com/moby/buildkit/solver/pb" + policyaction "github.com/moby/buildkit/sourcepolicy/pb" + "github.com/moby/buildkit/sourcepolicy/policysession" policyverifier "github.com/moby/policy-helpers" policyimage "github.com/moby/policy-helpers/image" policytypes "github.com/moby/policy-helpers/types" @@ -23,23 +26,34 @@ import ( "github.com/stretchr/testify/require" ) -func TestHTTPHostForPolicy(t *testing.T) { +func TestNormalizeHTTPHost(t *testing.T) { tests := []struct { scheme, host, want string }{ {"https", "example.com", "example.com"}, {"https", "example.com:443", "example.com"}, {"http", "example.com:80", "example.com"}, + {"https", ":443", ""}, + {"http", ":80", ""}, + {"https", "example.com:000443", "example.com"}, + {"http", "example.com:00080", "example.com"}, + {"https", "example.com:00080", "example.com:00080"}, + {"http", "example.com:000443", "example.com:000443"}, + {"https", "example.com:008443", "example.com:008443"}, + {"http", "example.com:000", "example.com:000"}, + {"https", "example.com:80", "example.com:80"}, + {"http", "example.com:443", "example.com:443"}, {"https", "example.com:8443", "example.com:8443"}, {"http", "example.com:8080", "example.com:8080"}, {"https", "[2001:db8::1]", "[2001:db8::1]"}, {"https", "[2001:db8::1]:443", "[2001:db8::1]"}, + {"https", "[2001:db8::1]:000443", "[2001:db8::1]"}, + {"http", "[2001:db8::1]:00080", "[2001:db8::1]"}, {"https", "[2001:db8::1]:8443", "[2001:db8::1]:8443"}, - {"https", "example.com:443", "example.com"}, } for _, tt := range tests { t.Run(tt.scheme+" "+tt.host, func(t *testing.T) { - require.Equal(t, tt.want, httpHostForPolicy(tt.scheme, tt.host)) + require.Equal(t, tt.want, normalizeHTTPHost(&url.URL{Scheme: tt.scheme, Host: tt.host})) }) } } @@ -69,6 +83,20 @@ func TestSourceToInputSingleSource(t *testing.T) { }, expErrMsg: "invalid source identifier: not-a-source", }, + { + name: "http-ipv6-without-brackets", + src: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{Identifier: "http://::1:443"}, + }, + expErrMsg: "failed to parse http source url", + }, + { + name: "https-ipv6-without-brackets", + src: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{Identifier: "https://::1:443"}, + }, + expErrMsg: "failed to parse http source url", + }, { name: "http-source-with-checksum-and-auth", src: &gwpb.ResolveSourceMetaResponse{ @@ -196,6 +224,36 @@ func TestSourceToInputSingleSource(t *testing.T) { }, }, }, + { + name: "http-empty-host-with-default-port", + src: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{Identifier: "http://:80"}, + }, + expInput: Input{ + HTTP: &HTTP{ + URL: "http://:80", + Schema: "http", + Host: "", + Query: map[string][]string{}, + }, + }, + expUnk: []string{"input.http.checksum"}, + }, + { + name: "https-empty-host-with-default-port", + src: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{Identifier: "https://:443"}, + }, + expInput: Input{ + HTTP: &HTTP{ + URL: "https://:443", + Schema: "https", + Host: "", + Query: map[string][]string{}, + }, + }, + expUnk: []string{"input.http.checksum"}, + }, { name: "https-non-default-port-kept-on-host", src: &gwpb.ResolveSourceMetaResponse{ @@ -988,6 +1046,59 @@ func TestSourceToInputSingleSource(t *testing.T) { } } +func TestCheckPolicyHTTPHost(t *testing.T) { + for _, tc := range []struct { + url string + allow bool + }{ + {"https://example.com/", true}, + {"https://example.com:443/", true}, + {"http://example.com:80/", true}, + {"https://example.com:000443/", true}, + {"http://example.com:00080/", true}, + {"https://example.com:00080/", false}, + {"http://example.com:000443/", false}, + {"https://example.com:008443/", false}, + {"https://example.com:80/", false}, + {"http://example.com:443/", false}, + {"https://example.com:8443/", false}, + {"https://[2001:db8::1]:443/", true}, + {"https://[2001:db8::1]:000443/", true}, + {"http://[2001:db8::1]:00080/", true}, + {"https://[2001:db8::1]:8443/", false}, + } { + t.Run(tc.url, func(t *testing.T) { + p := NewPolicy(Opt{ + Files: []File{{ + Filename: "policy.rego", + Data: []byte(` +package docker + +default allow := false +allow if input.http.host in ["example.com", "[2001:db8::1]"] +decision := {"allow": allow} +`), + }}, + }) + + // Exec proxy requests do not include HTTP checksum metadata. + decision, next, err := p.CheckPolicy(t.Context(), &policysession.CheckPolicyRequest{ + Source: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{Identifier: tc.url}, + }, + }) + require.NoError(t, err) + require.Nil(t, next) + require.NotNil(t, decision) + want := policyaction.PolicyAction_DENY + if tc.allow { + want = policyaction.PolicyAction_ALLOW + } + require.Equal(t, want, decision.Action) + }) + } +} + func TestCheckCaps(t *testing.T) { p := NewPolicy(Opt{ Files: []File{{ From f4567efaf6d552e85d2c880f5e9cf9657826c1fc Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Thu, 10 Sep 2026 14:56:14 +0200 Subject: [PATCH 3/4] policy: normalize HTTP scheme and host casing Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- policy/validate.go | 14 ++++++---- policy/validate_test.go | 59 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+), 5 deletions(-) diff --git a/policy/validate.go b/policy/validate.go index 561eca518833..5b1b9d3c5ef9 100644 --- a/policy/validate.go +++ b/policy/validate.go @@ -533,13 +533,16 @@ func (p *Policy) Print(ctx print.Context, msg string) error { } func normalizeHTTPHost(u *url.URL) string { + host := u.Host port := strings.TrimLeft(u.Port(), "0") - switch { - case u.Scheme == "http" && port == "80", u.Scheme == "https" && port == "443": - return strings.TrimSuffix(u.Host, ":"+u.Port()) - default: - return u.Host + if u.Scheme == "http" && port == "80" || u.Scheme == "https" && port == "443" { + host = strings.TrimSuffix(host, ":"+u.Port()) + } + // Preserve IPv6 zone identifiers, which may be case-sensitive interface names. + if i := strings.IndexByte(host, '%'); strings.HasPrefix(host, "[") && i >= 0 { + return strings.ToLower(host[:i]) + host[i:] } + return strings.ToLower(host) } func sourceToInput(ctx context.Context, getVerifier PolicyVerifierProvider, src *gwpb.ResolveSourceMetaResponse, platform *ocispecs.Platform, logf func(logrus.Level, string)) (Input, []string, error) { @@ -554,6 +557,7 @@ func sourceToInput(ctx context.Context, getVerifier PolicyVerifierProvider, src if !ok { return inp, nil, errors.Errorf("invalid source identifier: %s", src.Source.Identifier) } + scheme = strings.ToLower(scheme) switch scheme { case "http", "https": diff --git a/policy/validate_test.go b/policy/validate_test.go index b2bb19531149..51a713c7f570 100644 --- a/policy/validate_test.go +++ b/policy/validate_test.go @@ -31,6 +31,9 @@ func TestNormalizeHTTPHost(t *testing.T) { scheme, host, want string }{ {"https", "example.com", "example.com"}, + {"http", "Example.com", "example.com"}, + {"https", "EXAMPLE.COM:000443", "example.com"}, + {"https", "eXaMpLe.CoM:8443", "example.com:8443"}, {"https", "example.com:443", "example.com"}, {"http", "example.com:80", "example.com"}, {"https", ":443", ""}, @@ -46,6 +49,9 @@ func TestNormalizeHTTPHost(t *testing.T) { {"https", "example.com:8443", "example.com:8443"}, {"http", "example.com:8080", "example.com:8080"}, {"https", "[2001:db8::1]", "[2001:db8::1]"}, + {"https", "[2001:DB8::ABCD]:443", "[2001:db8::abcd]"}, + {"https", "[FE80::ABCD%Eth0]:000443", "[fe80::abcd%Eth0]"}, + {"https", "[FE80::ABCD%Eth0]:8443", "[fe80::abcd%Eth0]:8443"}, {"https", "[2001:db8::1]:443", "[2001:db8::1]"}, {"https", "[2001:db8::1]:000443", "[2001:db8::1]"}, {"http", "[2001:db8::1]:00080", "[2001:db8::1]"}, @@ -97,6 +103,54 @@ func TestSourceToInputSingleSource(t *testing.T) { }, expErrMsg: "failed to parse http source url", }, + { + name: "http-mixed-case-scheme-and-host", + src: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{Identifier: "hTtP://User:PaSs@eXaMpLe.CoM:00080/Case/Path?Key=VaLuE#Frag"}, + }, + expInput: Input{ + HTTP: &HTTP{ + URL: "hTtP://User:PaSs@eXaMpLe.CoM:00080/Case/Path?Key=VaLuE#Frag", + Schema: "http", + Host: "example.com", + Path: "/Case/Path", + Query: map[string][]string{"Key": {"VaLuE"}}, + }, + }, + expUnk: []string{"input.http.checksum"}, + }, + { + name: "https-mixed-case-host-with-non-default-port", + src: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{Identifier: "HTTPS://EXAMPLE.COM:8443/Case?Key=Value"}, + }, + expInput: Input{ + HTTP: &HTTP{ + URL: "HTTPS://EXAMPLE.COM:8443/Case?Key=Value", + Schema: "https", + Host: "example.com:8443", + Path: "/Case", + Query: map[string][]string{"Key": {"Value"}}, + }, + }, + expUnk: []string{"input.http.checksum"}, + }, + { + name: "https-ipv6-zone-case-preserved", + src: &gwpb.ResolveSourceMetaResponse{ + Source: &pb.SourceOp{Identifier: "HTTPS://[FE80::ABCD%25Eth0]:000443/Case"}, + }, + expInput: Input{ + HTTP: &HTTP{ + URL: "HTTPS://[FE80::ABCD%25Eth0]:000443/Case", + Schema: "https", + Host: "[fe80::abcd%Eth0]", + Path: "/Case", + Query: map[string][]string{}, + }, + }, + expUnk: []string{"input.http.checksum"}, + }, { name: "http-source-with-checksum-and-auth", src: &gwpb.ResolveSourceMetaResponse{ @@ -1052,6 +1106,11 @@ func TestCheckPolicyHTTPHost(t *testing.T) { allow bool }{ {"https://example.com/", true}, + {"Http://Example.com/", true}, + {"hTtP://eXaMpLe.CoM/", true}, + {"HTTP://EXAMPLE.COM/", true}, + {"HTTPS://EXAMPLE.COM:000443/", true}, + {"HTTPS://EXAMPLE.COM:8443/", false}, {"https://example.com:443/", true}, {"http://example.com:80/", true}, {"https://example.com:000443/", true}, From 7fb9d3d315182e003d59294cf7012cf89823b1e6 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Thu, 10 Sep 2026 17:54:21 +0200 Subject: [PATCH 4/4] policy: canonicalize HTTP hosts and preserve Unicode destinations Signed-off-by: CrazyMax <1951866+crazy-max@users.noreply.github.com> --- policy/validate.go | 34 ++++++++++++++++++++++++++-------- policy/validate_test.go | 30 +++++++++++++++++++++++++++--- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/policy/validate.go b/policy/validate.go index 5b1b9d3c5ef9..f9da79cd7277 100644 --- a/policy/validate.go +++ b/policy/validate.go @@ -6,12 +6,15 @@ import ( "fmt" "io/fs" "maps" + "net" + "net/netip" "net/url" "path" "slices" "strings" "sync" "time" + "unicode/utf8" "github.com/containerd/platforms" "github.com/distribution/reference" @@ -533,16 +536,31 @@ func (p *Policy) Print(ctx print.Context, msg string) error { } func normalizeHTTPHost(u *url.URL) string { - host := u.Host - port := strings.TrimLeft(u.Port(), "0") - if u.Scheme == "http" && port == "80" || u.Scheme == "https" && port == "443" { - host = strings.TrimSuffix(host, ":"+u.Port()) + host := u.Hostname() + addr, err := netip.ParseAddr(host) + if err == nil { + host = addr.String() + } else if utf8.ValidString(host) { + // Unicode case folding can change the IDNA destination (for example, İ to i). + host = strings.Map(func(r rune) rune { + if 'A' <= r && r <= 'Z' { + return r + ('a' - 'A') + } + return r + }, host) + } + port := u.Port() + normalizedPort := strings.TrimLeft(port, "0") + if u.Scheme == "http" && normalizedPort == "80" || u.Scheme == "https" && normalizedPort == "443" { + port = "" + } + if port != "" || strings.HasSuffix(u.Host, ":") { + return net.JoinHostPort(host, port) } - // Preserve IPv6 zone identifiers, which may be case-sensitive interface names. - if i := strings.IndexByte(host, '%'); strings.HasPrefix(host, "[") && i >= 0 { - return strings.ToLower(host[:i]) + host[i:] + if addr.Is6() { + return "[" + host + "]" } - return strings.ToLower(host) + return host } func sourceToInput(ctx context.Context, getVerifier PolicyVerifierProvider, src *gwpb.ResolveSourceMetaResponse, platform *ocispecs.Platform, logf func(logrus.Level, string)) (Input, []string, error) { diff --git a/policy/validate_test.go b/policy/validate_test.go index 51a713c7f570..e32764733663 100644 --- a/policy/validate_test.go +++ b/policy/validate_test.go @@ -31,7 +31,11 @@ func TestNormalizeHTTPHost(t *testing.T) { scheme, host, want string }{ {"https", "example.com", "example.com"}, + {"https", "example.com:", "example.com:"}, {"http", "Example.com", "example.com"}, + {"https", "\u0130.EXAMPLE.COM:443", "\u0130.example.com"}, + {"https", "\u00c9XAMPLE.COM", "\u00c9xample.com"}, + {"https", "\x94.EXAMPLE.COM", "\x94.EXAMPLE.COM"}, {"https", "EXAMPLE.COM:000443", "example.com"}, {"https", "eXaMpLe.CoM:8443", "example.com:8443"}, {"https", "example.com:443", "example.com"}, @@ -50,6 +54,15 @@ func TestNormalizeHTTPHost(t *testing.T) { {"http", "example.com:8080", "example.com:8080"}, {"https", "[2001:db8::1]", "[2001:db8::1]"}, {"https", "[2001:DB8::ABCD]:443", "[2001:db8::abcd]"}, + {"https", "[2001:0DB8:0000:0000:0001:0000:0000:0001]", "[2001:db8::1:0:0:1]"}, + {"https", "[0000:0000:0000:0000:0000:0000:0000:0001]:000443", "[::1]"}, + {"https", "[0000:0000:0000:0000:0000:0000:0000:0001]:008443", "[::1]:008443"}, + {"https", "[::1]:", "[::1]:"}, + {"https", "[FE80:0000:0000:0000:0000:0000:0000:ABCD%Eth0]:000443", "[fe80::abcd%Eth0]"}, + {"https", "[FE80:0000:0000:0000:0000:0000:0000:ABCD%Eth0]:8443", "[fe80::abcd%Eth0]:8443"}, + {"https", "192.0.2.128:443", "192.0.2.128"}, + {"https", "[::FFFF:192.0.2.128]:443", "[::ffff:192.0.2.128]"}, + {"https", "[::FFFF:C000:0280]:443", "[::ffff:192.0.2.128]"}, {"https", "[FE80::ABCD%Eth0]:000443", "[fe80::abcd%Eth0]"}, {"https", "[FE80::ABCD%Eth0]:8443", "[fe80::abcd%Eth0]:8443"}, {"https", "[2001:db8::1]:443", "[2001:db8::1]"}, @@ -138,11 +151,11 @@ func TestSourceToInputSingleSource(t *testing.T) { { name: "https-ipv6-zone-case-preserved", src: &gwpb.ResolveSourceMetaResponse{ - Source: &pb.SourceOp{Identifier: "HTTPS://[FE80::ABCD%25Eth0]:000443/Case"}, + Source: &pb.SourceOp{Identifier: "HTTPS://[FE80:0000:0000:0000:0000:0000:0000:ABCD%25Eth0]:000443/Case"}, }, expInput: Input{ HTTP: &HTTP{ - URL: "HTTPS://[FE80::ABCD%25Eth0]:000443/Case", + URL: "HTTPS://[FE80:0000:0000:0000:0000:0000:0000:ABCD%25Eth0]:000443/Case", Schema: "https", Host: "[fe80::abcd%Eth0]", Path: "/Case", @@ -1106,6 +1119,9 @@ func TestCheckPolicyHTTPHost(t *testing.T) { allow bool }{ {"https://example.com/", true}, + {"https://i.example.com/", true}, + {"https://\u0130.example.com/", false}, + {"https://%C4%B0.example.com/", false}, {"Http://Example.com/", true}, {"hTtP://eXaMpLe.CoM/", true}, {"HTTP://EXAMPLE.COM/", true}, @@ -1122,6 +1138,14 @@ func TestCheckPolicyHTTPHost(t *testing.T) { {"http://example.com:443/", false}, {"https://example.com:8443/", false}, {"https://[2001:db8::1]:443/", true}, + {"https://[2001:0DB8:0000:0000:0000:0000:0000:0001]/", true}, + {"https://[2001:0DB8:0000:0000:0000:0000:0000:0001]:000443/", true}, + {"https://[2001:0DB8:0000:0000:0000:0000:0000:0001]:8443/", false}, + {"https://[FE80:0000:0000:0000:0000:0000:0000:ABCD%25Eth0]:000443/", true}, + {"https://[FE80:0000:0000:0000:0000:0000:0000:ABCD%25eth0]:000443/", false}, + {"https://192.0.2.128:443/", true}, + {"https://[::FFFF:192.0.2.128]:443/", false}, + {"https://[::FFFF:C000:0280]:443/", false}, {"https://[2001:db8::1]:000443/", true}, {"http://[2001:db8::1]:00080/", true}, {"https://[2001:db8::1]:8443/", false}, @@ -1134,7 +1158,7 @@ func TestCheckPolicyHTTPHost(t *testing.T) { package docker default allow := false -allow if input.http.host in ["example.com", "[2001:db8::1]"] +allow if input.http.host in ["example.com", "i.example.com", "[2001:db8::1]", "[fe80::abcd%Eth0]", "192.0.2.128"] decision := {"allow": allow} `), }},