Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions runtime/secrets/converter.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"errors"
"fmt"
"net/url"
"slices"
"strings"

"github.com/go-logr/logr"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -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 {
Expand All @@ -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]
Expand All @@ -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.
Expand Down
74 changes: 74 additions & 0 deletions runtime/secrets/converter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"context"
"crypto/tls"
"fmt"
"strings"
"testing"

"github.com/go-logr/logr"
Expand Down Expand Up @@ -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 {
Expand Down
7 changes: 7 additions & 0 deletions runtime/secrets/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion tests/integration/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading