From f2f77e07441e28f890ee125e94058172fbbec76f Mon Sep 17 00:00:00 2001 From: timothyF95 Date: Mon, 22 Jun 2026 16:42:17 +0100 Subject: [PATCH] Force https gateway requests --- cmd/secrets/common/gateway/url.go | 44 ++++++++++++++++++++++++++ cmd/secrets/common/gateway/url_test.go | 35 ++++++++++++++++++++ cmd/secrets/common/handler.go | 3 ++ cmd/secrets/common/handler_test.go | 18 +++++++++++ 4 files changed, 100 insertions(+) create mode 100644 cmd/secrets/common/gateway/url.go create mode 100644 cmd/secrets/common/gateway/url_test.go diff --git a/cmd/secrets/common/gateway/url.go b/cmd/secrets/common/gateway/url.go new file mode 100644 index 00000000..0091730a --- /dev/null +++ b/cmd/secrets/common/gateway/url.go @@ -0,0 +1,44 @@ +package gateway + +import ( + "fmt" + "net" + "net/url" + "strings" +) + +// ValidateGatewayURL rejects vault gateway URLs that are not HTTPS with a host, +// except for HTTP on loopback hosts used by local development and integration tests. +func ValidateGatewayURL(raw string) error { + u, err := url.Parse(strings.TrimSpace(raw)) + if err != nil { + return fmt.Errorf("invalid vault gateway URL: %w", err) + } + if u.Scheme == "" { + return fmt.Errorf("invalid vault gateway URL") + } + if u.Host == "" { + return fmt.Errorf("vault gateway URL is missing a host") + } + + host := u.Hostname() + switch u.Scheme { + case "https": + return nil + case "http": + if isLoopbackHost(host) { + return nil + } + return fmt.Errorf("vault gateway URL must use https://, got %q", u.Scheme) + default: + return fmt.Errorf("vault gateway URL must use https://, got %q", u.Scheme) + } +} + +func isLoopbackHost(host string) bool { + if host == "localhost" { + return true + } + ip := net.ParseIP(host) + return ip != nil && ip.IsLoopback() +} diff --git a/cmd/secrets/common/gateway/url_test.go b/cmd/secrets/common/gateway/url_test.go new file mode 100644 index 00000000..13ee197b --- /dev/null +++ b/cmd/secrets/common/gateway/url_test.go @@ -0,0 +1,35 @@ +package gateway + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestValidateGatewayURL(t *testing.T) { + require.NoError(t, ValidateGatewayURL("https://gateway.example.com/")) + require.NoError(t, ValidateGatewayURL("https://gateway.example.com/v1")) + require.NoError(t, ValidateGatewayURL("http://127.0.0.1:57244")) + require.NoError(t, ValidateGatewayURL("http://localhost:8080")) + require.NoError(t, ValidateGatewayURL("http://[::1]:8080")) + + err := ValidateGatewayURL("http://gateway.example.com/") + require.Error(t, err) + require.Contains(t, err.Error(), "https://") + + err = ValidateGatewayURL("http://10.0.0.1/") + require.Error(t, err) + require.Contains(t, err.Error(), "https://") + + err = ValidateGatewayURL("http://127.0.0.1.evil.com/") + require.Error(t, err) + require.Contains(t, err.Error(), "https://") + + err = ValidateGatewayURL("https://") + require.Error(t, err) + require.Contains(t, err.Error(), "missing a host") + + err = ValidateGatewayURL("not-a-url") + require.Error(t, err) + require.Contains(t, err.Error(), "invalid vault gateway URL") +} diff --git a/cmd/secrets/common/handler.go b/cmd/secrets/common/handler.go index 40f8dfda..c03c3828 100644 --- a/cmd/secrets/common/handler.go +++ b/cmd/secrets/common/handler.go @@ -125,6 +125,9 @@ func NewHandler(execCtx context.Context, ctx *runtime.Context, secretsFilePath, execCtx: execCtx, } h.GatewayURL = gateway.ResolveVaultGatewayURL(ctx.TenantContext, ctx.EnvironmentSet) + if err := gateway.ValidateGatewayURL(h.GatewayURL); err != nil { + return nil, err + } h.Gw = &gateway.HTTPClient{URL: h.GatewayURL, Client: &http.Client{Timeout: 90 * time.Second}} if !IsBrowserFlow(secretsAuth) { diff --git a/cmd/secrets/common/handler_test.go b/cmd/secrets/common/handler_test.go index b7ad82ce..3b404aec 100644 --- a/cmd/secrets/common/handler_test.go +++ b/cmd/secrets/common/handler_test.go @@ -475,4 +475,22 @@ func TestNewHandler_GatewayURL(t *testing.T) { require.True(t, ok) require.Equal(t, "https://env-override.example.com/", gw.URL) }) + + t.Run("rejects non-https remote gateway URL", func(t *testing.T) { + t.Setenv(environments.EnvVarVaultGatewayURL, "") + ctx := *baseCtx + ctx.TenantContext = &tenantctx.EnvironmentContext{VaultGatewayURL: "http://insecure.example.com/"} + _, err := NewHandler(context.Background(), &ctx, "", SecretsAuthBrowser) + require.Error(t, err) + require.Contains(t, err.Error(), "https://") + }) + + t.Run("allows http loopback gateway URL", func(t *testing.T) { + t.Setenv(environments.EnvVarVaultGatewayURL, "") + ctx := *baseCtx + ctx.TenantContext = &tenantctx.EnvironmentContext{VaultGatewayURL: "http://127.0.0.1:1234"} + h, err := NewHandler(context.Background(), &ctx, "", SecretsAuthBrowser) + require.NoError(t, err) + require.Equal(t, "http://127.0.0.1:1234", h.GatewayURL) + }) }