Skip to content
Merged
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
8 changes: 3 additions & 5 deletions packages/cmd/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,9 @@ var bootstrapCmd = &cobra.Command{
return
}

domain, _ := cmd.Flags().GetString("domain")
if domain == "" {
if envDomain, ok := os.LookupEnv("INFISICAL_API_URL"); ok {
domain = envDomain
}
domain, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "domain", util.DomainEnvNames, "")
if err != nil {
util.HandleError(err, "Unable to parse domain")
}

if domain == "" {
Expand Down
2 changes: 1 addition & 1 deletion packages/cmd/kmip.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ var kmipSystemdInstallCmd = &cobra.Command{
util.HandleError(err, "Unable to parse identity client secret")
}

domain, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "domain", []string{util.INFISICAL_API_URL_ENV_NAME}, "")
domain, err := util.GetCmdFlagOrEnvWithDefaultValue(cmd, "domain", util.DomainEnvNames, "")
if err != nil {
util.HandleError(err, "Unable to parse domain")
}
Expand Down
4 changes: 2 additions & 2 deletions packages/cmd/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -439,7 +439,7 @@ func DomainOverridePrompt() (bool, error) {
//trim the '/' from the end of the domain url
config.INFISICAL_URL_MANUAL_OVERRIDE = strings.TrimRight(config.INFISICAL_URL_MANUAL_OVERRIDE, "/")
optionsPrompt := promptui.Select{
Label: fmt.Sprintf("Current INFISICAL_API_URL Domain Override: %s", config.INFISICAL_URL_MANUAL_OVERRIDE),
Label: fmt.Sprintf("Current Domain Override: %s", config.INFISICAL_URL_MANUAL_OVERRIDE),
Items: options,
Size: 2,
}
Expand Down Expand Up @@ -491,7 +491,7 @@ func usePresetDomain(presetDomain string, domainFlagExplicitlySet bool, shouldPr
boldWhite := whilte.Add(color.Bold)
time.Sleep(time.Second * 1)
if shouldPrintInfo {
boldWhite.Printf("[INFO] Using domain '%s' from domain flag or INFISICAL_API_URL environment variable\n", parsedDomain)
boldWhite.Printf("[INFO] Using domain '%s' from domain flag or INFISICAL_DOMAIN environment variable\n", parsedDomain)
}

return true, nil
Expand Down
6 changes: 4 additions & 2 deletions packages/cmd/login_status.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ func runLoginStatus(cmd *cobra.Command, args []string) {
flagToken = strings.TrimSpace(flagToken)
if flagToken != "" {
if !cmd.Flags().Changed("domain") {
if _, envSet := os.LookupEnv("INFISICAL_API_URL"); !envSet {
util.PrintErrorMessageAndExit("--token requires --domain (or INFISICAL_API_URL) to be set so the status reflects the correct Infisical instance")
_, envSet := util.GetEnvDomain()
workspaceConfig, _ := util.GetWorkSpaceFromFile()
if !envSet && workspaceConfig.Domain == "" {
util.PrintErrorMessageAndExit("--token requires the Infisical instance to be set via --domain, the INFISICAL_DOMAIN env var, or the 'domain' field in .infisical.json so the status reflects the correct instance")
}
Comment thread
PrestigePvP marked this conversation as resolved.
}
ctx, err := buildContextFromToken(flagToken, "--token flag", envDomain)
Expand Down
40 changes: 30 additions & 10 deletions packages/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,21 +88,49 @@ func Execute() {
}
}

// resolveDomain picks the domain by precedence: --domain flag > env > .infisical.json > default (flagValue).
// Must run after flag parsing (PersistentPreRun, not init) so cmd.Flags().Changed is reliable.
func resolveDomain(cmd *cobra.Command, flagValue string) string {
if cmd.Flags().Changed("domain") {
return flagValue
}

if envDomain, ok := util.GetEnvDomain(); ok {
return envDomain
}

workspaceConfig, err := util.GetWorkSpaceFromFile()
Comment thread
PrestigePvP marked this conversation as resolved.
if err != nil || workspaceConfig.Domain == "" {
return flagValue
}

domain := workspaceConfig.Domain
if !strings.HasPrefix(domain, "http://") && !strings.HasPrefix(domain, "https://") {
util.PrintWarningWithWriter("The 'domain' field in .infisical.json is not a valid URL (must start with http:// or https://). It will be ignored.", cmd.ErrOrStderr())
return flagValue
}

// A .infisical.json is usually committed to the repo, so a malicious one could redirect requests
// and credentials. Always surface where traffic is going (even under --silent); it goes to stderr.
util.PrintWarningWithWriter(fmt.Sprintf("Using domain '%s' from .infisical.json; all requests and credentials will be sent there.", domain), cmd.ErrOrStderr())
return domain
Comment thread
PrestigePvP marked this conversation as resolved.
}

func init() {
util.GetStderrWriter = RootCmdStderrWriter
util.GetStdoutWriter = RootCmdStdoutWriter
cobra.OnInitialize(initLog)
RootCmd.PersistentFlags().StringP("log-level", "l", "", "log level (trace, debug, info, warn, error, fatal)")
RootCmd.PersistentFlags().Bool("telemetry", true, "Infisical collects non-sensitive telemetry data to enhance features and improve user experience. Participation is voluntary")
RootCmd.PersistentFlags().StringVar(&config.INFISICAL_URL, "domain", fmt.Sprintf("%s/api", util.INFISICAL_DEFAULT_US_URL), "Point the CLI to your Infisical instance (e.g., https://eu.infisical.com for EU Cloud, or https://your-instance.com for self-hosted). Can also set via INFISICAL_API_URL environment variable. Required for non-US Cloud users.")
RootCmd.PersistentFlags().StringVar(&config.INFISICAL_URL, "domain", fmt.Sprintf("%s/api", util.INFISICAL_DEFAULT_US_URL), "Point the CLI to your Infisical instance (e.g., https://eu.infisical.com for EU Cloud, or https://your-instance.com for self-hosted). Can also set via INFISICAL_DOMAIN environment variable or the 'domain' field in .infisical.json. Required for non-US Cloud users.")
RootCmd.PersistentFlags().Bool("silent", false, "Disable output of tip/info messages. Useful when running in scripts or CI/CD pipelines.")
RootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
silent, err := cmd.Flags().GetBool("silent")
if err != nil {
util.HandleError(err)
}

config.INFISICAL_URL = util.AppendAPIEndpoint(config.INFISICAL_URL)
config.INFISICAL_URL = util.AppendAPIEndpoint(resolveDomain(cmd, config.INFISICAL_URL))

// util.DisplayAptInstallationChangeBannerWithWriter(silent, cmd.ErrOrStderr())
if !util.IsRunningInDocker() && !silent && !isStructuredOutputRequested(cmd) {
Expand All @@ -121,14 +149,6 @@ func init() {

}

// if config.INFISICAL_URL is set to the default value, check if INFISICAL_URL is set in the environment
// this is used to allow overrides of the default value
if !RootCmd.Flag("domain").Changed {
if envInfisicalBackendUrl, ok := os.LookupEnv("INFISICAL_API_URL"); ok {
config.INFISICAL_URL = util.AppendAPIEndpoint(envInfisicalBackendUrl)
}
}

isTelemetryOn, _ := RootCmd.PersistentFlags().GetBool("telemetry")
Telemetry = telemetry.NewTelemetry(isTelemetryOn)
}
Expand Down
1 change: 1 addition & 0 deletions packages/models/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ type WorkspaceConfigFile struct {
WorkspaceId string `json:"workspaceId"`
DefaultEnvironment string `json:"defaultEnvironment"`
GitBranchToEnvironmentMapping map[string]string `json:"gitBranchToEnvironmentMapping"`
Domain string `json:"domain,omitempty"`
}

type SymmetricEncryptionResult struct {
Expand Down
72 changes: 72 additions & 0 deletions packages/util/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package util

import (
"os"
"testing"
)

func TestWorkspaceConfigDomain(t *testing.T) {
cases := []struct {
name string
path string
wantDomain string
}{
{"domain field is parsed", "testdata/infisical-with-domain.json", "https://custom.infisical.com"},
{"existing config without a domain field parses to empty", "testdata/infisical-default-env.json", ""},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
cfg, err := GetWorkspaceConfigByPath(tc.path)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if cfg.Domain != tc.wantDomain {
t.Errorf("Domain = %q, want %q", cfg.Domain, tc.wantDomain)
}
})
}
}

func TestGetEnvDomain(t *testing.T) {
const unset = "\x00" // sentinel: leave the env var unset for this case

cases := []struct {
name string
domain string // INFISICAL_DOMAIN
apiURL string // INFISICAL_API_URL (legacy)
wantVal string
wantOk bool
}{
{"prefers INFISICAL_DOMAIN over legacy", "https://domain.infisical.com", "https://apiurl.infisical.com", "https://domain.infisical.com", true},
{"falls back to legacy INFISICAL_API_URL", unset, "https://apiurl.infisical.com", "https://apiurl.infisical.com", true},
{"blank INFISICAL_DOMAIN falls through to legacy", " ", "https://apiurl.infisical.com", "https://apiurl.infisical.com", true},
{"neither set", unset, unset, "", false},
{"both blank are treated as unset", " ", " ", "", false},
}

setOrUnset := func(t *testing.T, key, val string) {
t.Helper()
t.Setenv(key, "") // register restore-on-cleanup, then mutate freely below
if val == unset {
os.Unsetenv(key)
return
}
os.Setenv(key, val)
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
setOrUnset(t, INFISICAL_DOMAIN_ENV_NAME, tc.domain)
setOrUnset(t, LEGACY_INFISICAL_API_URL_ENV_NAME, tc.apiURL)

got, ok := GetEnvDomain()
if ok != tc.wantOk {
t.Fatalf("ok = %v, want %v", ok, tc.wantOk)
}
if got != tc.wantVal {
t.Errorf("value = %q, want %q", got, tc.wantVal)
}
})
}
}
3 changes: 2 additions & 1 deletion packages/util/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ const (

// Generic env variable used for auth methods that require a machine identity ID
INFISICAL_MACHINE_IDENTITY_ID_NAME = "INFISICAL_MACHINE_IDENTITY_ID"
INFISICAL_API_URL_ENV_NAME = "INFISICAL_API_URL"
INFISICAL_DOMAIN_ENV_NAME = "INFISICAL_DOMAIN"
LEGACY_INFISICAL_API_URL_ENV_NAME = "INFISICAL_API_URL" // superseded by INFISICAL_DOMAIN; kept for backwards compatibility

SECRET_TYPE_PERSONAL = "personal"
SECRET_TYPE_SHARED = "shared"
Expand Down
15 changes: 15 additions & 0 deletions packages/util/helper.go
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,21 @@ func getCurrentBranch() (string, error) {
return path.Base(strings.TrimSpace(out.String())), nil
}

// DomainEnvNames lists the env vars that configure the Infisical domain, in
// precedence order: INFISICAL_DOMAIN first, then the legacy INFISICAL_API_URL.
var DomainEnvNames = []string{INFISICAL_DOMAIN_ENV_NAME, LEGACY_INFISICAL_API_URL_ENV_NAME}

// GetEnvDomain returns the Infisical domain configured via environment
// variables, preferring INFISICAL_DOMAIN over the legacy INFISICAL_API_URL.
func GetEnvDomain() (string, bool) {
for _, env := range DomainEnvNames {
if domain := strings.TrimSpace(os.Getenv(env)); domain != "" {
return domain, true
}
}
return "", false
}

func AppendAPIEndpoint(address string) string {
// if it's empty return as it is
// Ensure the address does not already end with "/api"
Expand Down
6 changes: 6 additions & 0 deletions packages/util/testdata/infisical-with-domain.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"workspaceId": "test-workspace-id",
"defaultEnvironment": "dev",
"gitBranchToEnvironmentMapping": null,
"domain": "https://custom.infisical.com"
}
Loading