diff --git a/runtime/secrets/converter.go b/runtime/secrets/converter.go index b312c55b..59c07fd4 100644 --- a/runtime/secrets/converter.go +++ b/runtime/secrets/converter.go @@ -23,6 +23,8 @@ import ( "errors" "fmt" "net/url" + "slices" + "strings" "github.com/go-logr/logr" corev1 "k8s.io/api/core/v1" @@ -132,6 +134,9 @@ func TLSConfigFromSecret(ctx context.Context, secret *corev1.Secret, opts ...TLS // The function expects the secret to contain an "address" field with the // proxy URL. Optional "username" and "password" fields can be provided // for proxy authentication. +// +// Supported proxy schemes are: http, https, and socks5. +// The proxy URL must not exceed 2048 characters. func ProxyURLFromSecret(ctx context.Context, secret *corev1.Secret) (*url.URL, error) { addressData, exists := secret.Data[KeyAddress] if !exists { @@ -144,10 +149,10 @@ func ProxyURLFromSecret(ctx context.Context, secret *corev1.Secret) (*url.URL, e return nil, fmt.Errorf("secret '%s': proxy address is empty", ref) } - proxyURL, err := url.Parse(address) + proxyURL, err := parseProxyURL(address) if err != nil { ref := client.ObjectKeyFromObject(secret) - return nil, fmt.Errorf("secret '%s': failed to parse proxy address '%s': %w", ref, address, err) + return nil, fmt.Errorf("secret '%s': %w", ref, err) } username, hasUsername := secret.Data[KeyUsername] @@ -162,6 +167,39 @@ func ProxyURLFromSecret(ctx context.Context, secret *corev1.Secret) (*url.URL, e return proxyURL, nil } +func parseProxyURL(address string) (*url.URL, error) { + if err := validateProxyURLString(address); err != nil { + return nil, err + } + + u, err := url.Parse(address) + if err != nil { + return nil, fmt.Errorf("failed to parse proxy address '%s': %w", address, err) + } + + if err := validateProxyURLStruct(u); err != nil { + return nil, err + } + + return u, nil +} + +func validateProxyURLString(address string) error { + if len(address) > MaxProxyURLLength { + return fmt.Errorf("proxy URL exceeds maximum length of %d characters", MaxProxyURLLength) + } + return nil +} + +func validateProxyURLStruct(u *url.URL) error { + if !slices.Contains(supportedProxySchemes, u.Scheme) { + supportedSchemes := strings.Join(supportedProxySchemes, ", ") + return fmt.Errorf("proxy URL must use one of the supported schemes (%s), got '%s'", + supportedSchemes, u.Scheme) + } + return nil +} + // BasicAuthFromSecret retrieves basic authentication credentials from a Kubernetes secret. // // The function expects the secret to contain "username" and "password" fields. diff --git a/runtime/secrets/converter_test.go b/runtime/secrets/converter_test.go index 30f54d00..9ba69df3 100644 --- a/runtime/secrets/converter_test.go +++ b/runtime/secrets/converter_test.go @@ -20,6 +20,7 @@ import ( "context" "crypto/tls" "fmt" + "strings" "testing" "github.com/go-logr/logr" @@ -752,6 +753,79 @@ func TestProxyURLFromSecret(t *testing.T) { ), errMsg: "secret 'default/proxy-secret': failed to parse proxy address", }, + { + name: "socks5 proxy", + secret: testSecret( + withName("proxy-secret"), + withData(map[string][]byte{ + secrets.KeyAddress: []byte("socks5://socks-proxy.example.com:1080"), + }), + ), + wantURL: "socks5://socks-proxy.example.com:1080", + }, + { + name: "socks5 proxy with authentication", + secret: testSecret( + withName("proxy-secret"), + withData(map[string][]byte{ + secrets.KeyAddress: []byte("socks5://socks-proxy.example.com:1080"), + secrets.KeyUsername: []byte("sockuser"), + secrets.KeyPassword: []byte("sockpass"), + }), + ), + wantURL: "socks5://sockuser:sockpass@socks-proxy.example.com:1080", + }, + { + name: "unsupported scheme - ftp", + secret: testSecret( + withName("proxy-secret"), + withData(map[string][]byte{ + secrets.KeyAddress: []byte("ftp://ftp.example.com:21"), + }), + ), + errMsg: "proxy URL must use one of the supported schemes (http, https, socks5), got 'ftp'", + }, + { + name: "unsupported scheme - socks4", + secret: testSecret( + withName("proxy-secret"), + withData(map[string][]byte{ + secrets.KeyAddress: []byte("socks4://proxy.example.com:1080"), + }), + ), + errMsg: "proxy URL must use one of the supported schemes (http, https, socks5), got 'socks4'", + }, + { + name: "URL exceeds maximum length", + secret: testSecret( + withName("proxy-secret"), + withData(map[string][]byte{ + secrets.KeyAddress: []byte("http://" + strings.Repeat("a", 2050)), + }), + ), + errMsg: "proxy URL exceeds maximum length of 2048 characters", + }, + { + name: "URL at maximum length boundary", + secret: testSecret( + withName("proxy-secret"), + withData(map[string][]byte{ + // Create a URL exactly 2048 characters (http:// = 7 chars, so 2041 'a's) + secrets.KeyAddress: []byte("http://" + strings.Repeat("a", 2041)), + }), + ), + wantURL: "http://" + strings.Repeat("a", 2041), + }, + { + name: "missing scheme", + secret: testSecret( + withName("proxy-secret"), + withData(map[string][]byte{ + secrets.KeyAddress: []byte("//proxy.example.com:8080"), + }), + ), + errMsg: "proxy URL must use one of the supported schemes (http, https, socks5), got ''", + }, } for _, tt := range tests { diff --git a/runtime/secrets/secrets.go b/runtime/secrets/secrets.go index f7fa5e36..38c53c72 100644 --- a/runtime/secrets/secrets.go +++ b/runtime/secrets/secrets.go @@ -69,8 +69,15 @@ const ( KeySSHPublicKey = "identity.pub" // KeySSHKnownHosts is the key for SSH known hosts data in secrets. KeySSHKnownHosts = "known_hosts" + + // MaxProxyURLLength is the maximum allowed length for proxy URLs. + MaxProxyURLLength = 2048 ) +// supportedProxySchemes defines the officially supported proxy URL schemes. +// See https://fluxcd.io/flux/installation/configuration/proxy-setting for more information. +var supportedProxySchemes = []string{"http", "https", "socks5"} + // AuthMethods holds all available authentication methods detected from a secret. type AuthMethods struct { Basic *BasicAuth diff --git a/tests/integration/go.mod b/tests/integration/go.mod index 40e413e2..6b515239 100644 --- a/tests/integration/go.mod +++ b/tests/integration/go.mod @@ -22,7 +22,7 @@ require ( github.com/fluxcd/pkg/cache v0.12.0 github.com/fluxcd/pkg/git v0.38.0 github.com/fluxcd/pkg/git/gogit v0.42.0 - github.com/fluxcd/pkg/runtime v0.91.0 + github.com/fluxcd/pkg/runtime v0.92.0 github.com/fluxcd/test-infra/tftestenv v0.0.0-20250626232827-e0ca9c3f8d7b github.com/go-git/go-git/v5 v5.16.3 github.com/google/go-containerregistry v0.20.6