From bf9f5aabae5faf4da5a7c6f00570041ec116712e Mon Sep 17 00:00:00 2001 From: Dean Chen <862469039@qq.com> Date: Wed, 9 Sep 2026 07:40:11 +0500 Subject: [PATCH] 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. Fixes #4061 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{