diff --git a/src/cmd/root.go b/src/cmd/root.go index 265cb76c..5c9d85ff 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/phasehq/cli/pkg/config" phaseerrors "github.com/phasehq/cli/pkg/errors" "github.com/phasehq/cli/pkg/version" "github.com/spf13/cobra" @@ -25,9 +26,9 @@ const phaseASCii = ` const description = "Keep Secrets." var rootCmd = &cobra.Command{ - Use: "phase", - Short: description, - Long: description + "\n" + phaseASCii, + Use: "phase", + Short: description, + Long: description + "\n" + phaseASCii, SilenceUsage: true, SilenceErrors: true, } @@ -40,6 +41,13 @@ func Execute() { } func init() { + // Honor PHASE_VERIFY_SSL=False before any command runs so it applies to + // every network call — including pre-auth flows like + // `phase auth --mode aws-iam` / `--mode azure` that don't go through + // NewPhase(). The SDK caches its HTTP client on first use, so this must + // happen before any request is made. + config.ConfigureSSLVerification() + rootCmd.Version = version.Version rootCmd.SetVersionTemplate("{{ .Version }}\n") diff --git a/src/pkg/config/env.go b/src/pkg/config/env.go new file mode 100644 index 00000000..619fc75d --- /dev/null +++ b/src/pkg/config/env.go @@ -0,0 +1,24 @@ +package config + +import ( + "os" + "strings" + + "github.com/phasehq/golang-sdk/v2/phase/misc" +) + +// ConfigureSSLVerification reads the PHASE_VERIFY_SSL environment variable and +// disables TLS certificate verification in the SDK when it is set to "false" +// (case-insensitive). Any other value — or the variable being unset — keeps +// verification enabled. This mirrors the Python CLI's behavior +// (os.environ.get("PHASE_VERIFY_SSL", "True").lower() != "false") and makes +// the hint shown in SSL error messages ("You may set PHASE_VERIFY_SSL=False +// to bypass this check") actually work. +// +// It must run before any SDK network call: the SDK caches its HTTP client on +// first use, so changes to misc.VerifySSL after that have no effect. +func ConfigureSSLVerification() { + if strings.EqualFold(os.Getenv("PHASE_VERIFY_SSL"), "false") { + misc.VerifySSL = false + } +} diff --git a/src/pkg/config/env_test.go b/src/pkg/config/env_test.go new file mode 100644 index 00000000..c5728274 --- /dev/null +++ b/src/pkg/config/env_test.go @@ -0,0 +1,50 @@ +package config + +import ( + "os" + "testing" + + "github.com/phasehq/golang-sdk/v2/phase/misc" +) + +func TestConfigureSSLVerification(t *testing.T) { + tests := []struct { + name string + value string + unset bool + wantVerify bool + }{ + {name: "unset keeps verification enabled", unset: true, wantVerify: true}, + {name: "empty keeps verification enabled", value: "", wantVerify: true}, + {name: "False disables verification", value: "False", wantVerify: false}, + {name: "false disables verification", value: "false", wantVerify: false}, + {name: "FALSE disables verification", value: "FALSE", wantVerify: false}, + {name: "true keeps verification enabled", value: "true", wantVerify: true}, + {name: "True keeps verification enabled", value: "True", wantVerify: true}, + {name: "zero keeps verification enabled", value: "0", wantVerify: true}, + {name: "no keeps verification enabled", value: "no", wantVerify: true}, + {name: "arbitrary value keeps verification enabled", value: "banana", wantVerify: true}, + } + + original := misc.VerifySSL + t.Cleanup(func() { misc.VerifySSL = original }) + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + misc.VerifySSL = true // reset the global before each case + + // t.Setenv registers restoration of the original value; for the + // unset case we additionally clear it after registration. + t.Setenv("PHASE_VERIFY_SSL", tt.value) + if tt.unset { + os.Unsetenv("PHASE_VERIFY_SSL") + } + + ConfigureSSLVerification() + + if misc.VerifySSL != tt.wantVerify { + t.Errorf("PHASE_VERIFY_SSL=%q: misc.VerifySSL = %v, want %v", tt.value, misc.VerifySSL, tt.wantVerify) + } + }) + } +}