diff --git a/cli/go.mod b/cli/go.mod index b2d0c83ad5..5f3992e179 100644 --- a/cli/go.mod +++ b/cli/go.mod @@ -12,7 +12,7 @@ require ( github.com/fatih/semgroup v1.2.0 github.com/gitleaks/go-gitdiff v0.8.0 github.com/h2non/filetype v1.1.3 - github.com/infisical/go-sdk v0.4.8 + github.com/infisical/go-sdk v0.5.1 github.com/infisical/infisical-kmip v0.3.5 github.com/mattn/go-isatty v0.0.20 github.com/muesli/ansi v0.0.0-20221106050444-61f0cd9a192a @@ -34,6 +34,7 @@ require ( golang.org/x/sys v0.31.0 golang.org/x/term v0.30.0 gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( @@ -125,7 +126,6 @@ require ( google.golang.org/grpc v1.64.1 // indirect google.golang.org/protobuf v1.36.1 // indirect gopkg.in/ini.v1 v1.62.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) require ( diff --git a/cli/go.sum b/cli/go.sum index 5f1f369bbf..da221fd6fb 100644 --- a/cli/go.sum +++ b/cli/go.sum @@ -277,8 +277,8 @@ github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1: github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/infisical/go-sdk v0.4.8 h1:aphRnaauC5//PkP1ZbY9RSK2RiT1LjPS5o4CbX0x5OQ= -github.com/infisical/go-sdk v0.4.8/go.mod h1:bMO9xSaBeXkDBhTIM4FkkREAfw2V8mv5Bm7lvo4+uDk= +github.com/infisical/go-sdk v0.5.1 h1:bl0D4A6CmvfL8RwEQTcZh39nsxC6q3HSs76/4J8grWY= +github.com/infisical/go-sdk v0.5.1/go.mod h1:ExjqFLRz7LSpZpGluqDLvFl6dFBLq5LKyLW7GBaMAIs= github.com/infisical/infisical-kmip v0.3.5 h1:QM3s0e18B+mYv3a9HQNjNAlbwZJBzXq5BAJM2scIeiE= github.com/infisical/infisical-kmip v0.3.5/go.mod h1:bO1M4YtKyutNg1bREPmlyZspC5duSR7hyQ3lPmLzrIs= github.com/jedib0t/go-pretty v4.3.0+incompatible h1:CGs8AVhEKg/n9YbUenWmNStRW2PHJzaeDodcfvRAbIo= @@ -858,4 +858,4 @@ honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9 honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= \ No newline at end of file +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/cli/packages/cmd/agent.go b/cli/packages/cmd/agent.go index f4fe94a6e7..b8fc6ed7b8 100644 --- a/cli/packages/cmd/agent.go +++ b/cli/packages/cmd/agent.go @@ -29,7 +29,6 @@ import ( "github.com/Infisical/infisical-merge/packages/config" "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/util" - "github.com/go-resty/resty/v2" "github.com/spf13/cobra" ) @@ -514,7 +513,10 @@ type NewAgentMangerOptions struct { } func NewAgentManager(options NewAgentMangerOptions) *AgentManager { - + customHeaders, err := util.GetInfisicalCustomHeadersMap() + if err != nil { + util.HandleError(err, "Unable to get custom headers") + } return &AgentManager{ filePaths: options.FileDeposits, templates: options.Templates, @@ -529,6 +531,7 @@ func NewAgentManager(options NewAgentMangerOptions) *AgentManager { SiteUrl: config.INFISICAL_URL, UserAgent: api.USER_AGENT, // ? Should we perhaps use a different user agent for the Agent for better analytics? AutoTokenRefresh: false, + CustomHeaders: customHeaders, }), } @@ -716,7 +719,11 @@ func (tm *AgentManager) FetchNewAccessToken() error { // Refreshes the existing access token func (tm *AgentManager) RefreshAccessToken() error { - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + return err + } + httpClient.SetRetryCount(10000). SetRetryMaxWaitTime(20 * time.Second). SetRetryWaitTime(5 * time.Second) diff --git a/cli/packages/cmd/bootstrap.go b/cli/packages/cmd/bootstrap.go index 008debbac7..4582cb0016 100644 --- a/cli/packages/cmd/bootstrap.go +++ b/cli/packages/cmd/bootstrap.go @@ -10,7 +10,6 @@ import ( "github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/util" - "github.com/go-resty/resty/v2" "github.com/rs/zerolog/log" "github.com/spf13/cobra" ) @@ -70,8 +69,12 @@ var bootstrapCmd = &cobra.Command{ return } - httpClient := resty.New(). - SetHeader("Accept", "application/json") + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + log.Error().Msgf("Failed to get resty client with custom headers: %v", err) + return + } + httpClient.SetHeader("Accept", "application/json") bootstrapResponse, err := api.CallBootstrapInstance(httpClient, api.BootstrapInstanceRequest{ Domain: util.AppendAPIEndpoint(domain), diff --git a/cli/packages/cmd/dynamic_secrets.go b/cli/packages/cmd/dynamic_secrets.go index 9e0ea13348..60f3561850 100644 --- a/cli/packages/cmd/dynamic_secrets.go +++ b/cli/packages/cmd/dynamic_secrets.go @@ -14,7 +14,6 @@ import ( // "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/util" // "github.com/Infisical/infisical-merge/packages/visualize" - "github.com/go-resty/resty/v2" "github.com/posthog/posthog-go" "github.com/spf13/cobra" @@ -56,7 +55,10 @@ func getDynamicSecretList(cmd *cobra.Command, args []string) { } var infisicalToken string - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } if projectId == "" { workspaceFile, err := util.GetWorkSpaceFromFile() @@ -85,10 +87,16 @@ func getDynamicSecretList(cmd *cobra.Command, args []string) { httpClient.SetAuthToken(infisicalToken) + customHeaders, err := util.GetInfisicalCustomHeadersMap() + if err != nil { + util.HandleError(err, "Unable to get custom headers") + } + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ SiteUrl: config.INFISICAL_URL, UserAgent: api.USER_AGENT, AutoTokenRefresh: false, + CustomHeaders: customHeaders, }) infisicalClient.Auth().SetAccessToken(infisicalToken) @@ -164,7 +172,10 @@ func createDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { } var infisicalToken string - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } if projectId == "" { workspaceFile, err := util.GetWorkSpaceFromFile() @@ -193,10 +204,16 @@ func createDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { httpClient.SetAuthToken(infisicalToken) + customHeaders, err := util.GetInfisicalCustomHeadersMap() + if err != nil { + util.HandleError(err, "Unable to get custom headers") + } + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ SiteUrl: config.INFISICAL_URL, UserAgent: api.USER_AGENT, AutoTokenRefresh: false, + CustomHeaders: customHeaders, }) infisicalClient.Auth().SetAccessToken(infisicalToken) @@ -286,7 +303,10 @@ func renewDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { } var infisicalToken string - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } if projectId == "" { workspaceFile, err := util.GetWorkSpaceFromFile() @@ -315,10 +335,16 @@ func renewDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { httpClient.SetAuthToken(infisicalToken) + customHeaders, err := util.GetInfisicalCustomHeadersMap() + if err != nil { + util.HandleError(err, "Unable to get custom headers") + } + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ SiteUrl: config.INFISICAL_URL, UserAgent: api.USER_AGENT, AutoTokenRefresh: false, + CustomHeaders: customHeaders, }) infisicalClient.Auth().SetAccessToken(infisicalToken) @@ -384,7 +410,10 @@ func revokeDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { } var infisicalToken string - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } if projectId == "" { workspaceFile, err := util.GetWorkSpaceFromFile() @@ -413,10 +442,16 @@ func revokeDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { httpClient.SetAuthToken(infisicalToken) + customHeaders, err := util.GetInfisicalCustomHeadersMap() + if err != nil { + util.HandleError(err, "Unable to get custom headers") + } + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ SiteUrl: config.INFISICAL_URL, UserAgent: api.USER_AGENT, AutoTokenRefresh: false, + CustomHeaders: customHeaders, }) infisicalClient.Auth().SetAccessToken(infisicalToken) @@ -481,7 +516,10 @@ func listDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { } var infisicalToken string - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } if projectId == "" { workspaceFile, err := util.GetWorkSpaceFromFile() @@ -510,10 +548,16 @@ func listDynamicSecretLeaseByName(cmd *cobra.Command, args []string) { httpClient.SetAuthToken(infisicalToken) + customHeaders, err := util.GetInfisicalCustomHeadersMap() + if err != nil { + util.HandleError(err, "Unable to get custom headers") + } + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ SiteUrl: config.INFISICAL_URL, UserAgent: api.USER_AGENT, AutoTokenRefresh: false, + CustomHeaders: customHeaders, }) infisicalClient.Auth().SetAccessToken(infisicalToken) diff --git a/cli/packages/cmd/init.go b/cli/packages/cmd/init.go index df6bfcc600..e10a11c068 100644 --- a/cli/packages/cmd/init.go +++ b/cli/packages/cmd/init.go @@ -10,7 +10,6 @@ import ( "github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/util" - "github.com/go-resty/resty/v2" "github.com/manifoldco/promptui" "github.com/posthog/posthog-go" "github.com/rs/zerolog/log" @@ -50,7 +49,10 @@ var initCmd = &cobra.Command{ util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again") } - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } httpClient.SetAuthToken(userCreds.UserCredentials.JTWToken) organizationResponse, err := api.CallGetAllOrganizations(httpClient) @@ -81,7 +83,10 @@ var initCmd = &cobra.Command{ for i < 6 { mfaVerifyCode := askForMFACode(tokenResponse.MfaMethod) - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } httpClient.SetAuthToken(tokenResponse.Token) verifyMFAresponse, mfaErrorResponse, requestError := api.CallVerifyMfaToken(httpClient, api.VerifyMfaTokenRequest{ Email: userCreds.UserCredentials.Email, diff --git a/cli/packages/cmd/login.go b/cli/packages/cmd/login.go index 81bb0c7549..b1c868d8cf 100644 --- a/cli/packages/cmd/login.go +++ b/cli/packages/cmd/login.go @@ -27,7 +27,6 @@ import ( "github.com/Infisical/infisical-merge/packages/srp" "github.com/Infisical/infisical-merge/packages/util" "github.com/fatih/color" - "github.com/go-resty/resty/v2" "github.com/manifoldco/promptui" "github.com/posthog/posthog-go" "github.com/rs/cors" @@ -178,10 +177,16 @@ var loginCmd = &cobra.Command{ return } + customHeaders, err := util.GetInfisicalCustomHeadersMap() + if err != nil { + util.HandleError(err, "Unable to get custom headers") + } + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ SiteUrl: config.INFISICAL_URL, UserAgent: api.USER_AGENT, AutoTokenRefresh: false, + CustomHeaders: customHeaders, }) loginMethod, err := cmd.Flags().GetString("method") @@ -359,7 +364,10 @@ func cliDefaultLogin(userCredentialsToBeStored *models.UserCredentials) { for i < 6 { mfaVerifyCode := askForMFACode("email") - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } httpClient.SetAuthToken(loginTwoResponse.Token) verifyMFAresponse, mfaErrorResponse, requestError := api.CallVerifyMfaToken(httpClient, api.VerifyMfaTokenRequest{ Email: email, @@ -726,7 +734,10 @@ func askForLoginCredentials() (email string, password string, err error) { func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2Response, *api.GetLoginTwoV2Response, error) { log.Debug().Msg(fmt.Sprint("getFreshUserCredentials: ", "email", email, "password: ", password)) - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + return nil, nil, err + } httpClient.SetRetryCount(5) params := srp.GetParams(4096) @@ -776,7 +787,10 @@ func getFreshUserCredentials(email string, password string) (*api.GetLoginOneV2R func GetJwtTokenWithOrganizationId(oldJwtToken string, email string) string { log.Debug().Msg(fmt.Sprint("GetJwtTokenWithOrganizationId: ", "oldJwtToken", oldJwtToken)) - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } httpClient.SetAuthToken(oldJwtToken) organizationResponse, err := api.CallGetAllOrganizations(httpClient) @@ -811,7 +825,10 @@ func GetJwtTokenWithOrganizationId(oldJwtToken string, email string) string { for i < 6 { mfaVerifyCode := askForMFACode(selectedOrgRes.MfaMethod) - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } httpClient.SetAuthToken(selectedOrgRes.Token) verifyMFAresponse, mfaErrorResponse, requestError := api.CallVerifyMfaToken(httpClient, api.VerifyMfaTokenRequest{ Email: email, @@ -913,7 +930,14 @@ func askToPasteJwtToken(success chan models.UserCredentials, failure chan error) } // verify JTW - httpClient := resty.New(). + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + failure <- err + fmt.Println("Error getting resty client with custom headers", err) + os.Exit(1) + } + + httpClient. SetAuthToken(userCredentials.JTWToken). SetHeader("Accept", "application/json") diff --git a/cli/packages/cmd/secrets.go b/cli/packages/cmd/secrets.go index ad0fdf055e..fdee3e7c0a 100644 --- a/cli/packages/cmd/secrets.go +++ b/cli/packages/cmd/secrets.go @@ -14,7 +14,6 @@ import ( "github.com/Infisical/infisical-merge/packages/models" "github.com/Infisical/infisical-merge/packages/util" "github.com/Infisical/infisical-merge/packages/visualize" - "github.com/go-resty/resty/v2" "github.com/posthog/posthog-go" "github.com/spf13/cobra" ) @@ -299,8 +298,12 @@ var secretsDeleteCmd = &cobra.Command{ util.HandleError(err, "Unable to parse flag") } - httpClient := resty.New(). - SetHeader("Accept", "application/json") + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } + + httpClient.SetHeader("Accept", "application/json") if projectId == "" { workspaceFile, err := util.GetWorkSpaceFromFile() diff --git a/cli/packages/cmd/ssh.go b/cli/packages/cmd/ssh.go index d7c1f1e269..a3d10fc91d 100644 --- a/cli/packages/cmd/ssh.go +++ b/cli/packages/cmd/ssh.go @@ -315,10 +315,16 @@ func issueCredentials(cmd *cobra.Command, args []string) { } } + customHeaders, err := util.GetInfisicalCustomHeadersMap() + if err != nil { + util.HandleError(err, "Unable to get custom headers") + } + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ SiteUrl: config.INFISICAL_URL, UserAgent: api.USER_AGENT, AutoTokenRefresh: false, + CustomHeaders: customHeaders, }) infisicalClient.Auth().SetAccessToken(infisicalToken) @@ -555,10 +561,16 @@ func signKey(cmd *cobra.Command, args []string) { signedKeyPath = outFilePath } + customHeaders, err := util.GetInfisicalCustomHeadersMap() + if err != nil { + util.HandleError(err, "Unable to get custom headers") + } + infisicalClient := infisicalSdk.NewInfisicalClient(context.Background(), infisicalSdk.Config{ SiteUrl: config.INFISICAL_URL, UserAgent: api.USER_AGENT, AutoTokenRefresh: false, + CustomHeaders: customHeaders, }) infisicalClient.Auth().SetAccessToken(infisicalToken) diff --git a/cli/packages/cmd/tokens.go b/cli/packages/cmd/tokens.go index 531e622e9e..386a7eda5b 100644 --- a/cli/packages/cmd/tokens.go +++ b/cli/packages/cmd/tokens.go @@ -13,7 +13,6 @@ import ( "github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/crypto" "github.com/Infisical/infisical-merge/packages/util" - "github.com/go-resty/resty/v2" "github.com/spf13/cobra" ) @@ -136,7 +135,11 @@ var tokensCreateCmd = &cobra.Command{ } // make a call to the api to save the encrypted symmetric key details - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + util.HandleError(err, "Unable to get resty client with custom headers") + } + httpClient.SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken). SetHeader("Accept", "application/json") diff --git a/cli/packages/gateway/gateway.go b/cli/packages/gateway/gateway.go index d0246e3bc0..d0a25ca9c5 100644 --- a/cli/packages/gateway/gateway.go +++ b/cli/packages/gateway/gateway.go @@ -13,6 +13,7 @@ import ( "github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/systemd" + "github.com/Infisical/infisical-merge/packages/util" "github.com/go-resty/resty/v2" "github.com/pion/dtls/v3" "github.com/pion/logging" @@ -40,7 +41,11 @@ type Gateway struct { } func NewGateway(identityToken string) (Gateway, error) { - httpClient := resty.New() + httpClient, err := util.GetRestyClientWithCustomHeaders() + if err != nil { + return Gateway{}, fmt.Errorf("unable to get client with custom headers [err=%v]", err) + } + httpClient.SetAuthToken(identityToken) return Gateway{ diff --git a/cli/packages/util/common.go b/cli/packages/util/common.go index 55907da9de..07618ae873 100644 --- a/cli/packages/util/common.go +++ b/cli/packages/util/common.go @@ -4,8 +4,11 @@ import ( "fmt" "net/http" "os" + "strings" + "unicode" "github.com/Infisical/infisical-merge/packages/config" + "github.com/go-resty/resty/v2" ) func GetHomeDir() (string, error) { @@ -27,3 +30,88 @@ func ValidateInfisicalAPIConnection() (ok bool) { _, err := http.Get(fmt.Sprintf("%v/status", config.INFISICAL_URL)) return err == nil } + +func GetRestyClientWithCustomHeaders() (*resty.Client, error) { + httpClient := resty.New() + customHeaders := os.Getenv("INFISICAL_CUSTOM_HEADERS") + if customHeaders != "" { + headers, err := GetInfisicalCustomHeadersMap() + if err != nil { + return nil, err + } + + httpClient.SetHeaders(headers) + } + return httpClient, nil +} + +func GetInfisicalCustomHeadersMap() (map[string]string, error) { + customHeaders := os.Getenv("INFISICAL_CUSTOM_HEADERS") + if customHeaders == "" { + return nil, nil + } + + headers := map[string]string{} + + pos := 0 + for pos < len(customHeaders) { + for pos < len(customHeaders) && unicode.IsSpace(rune(customHeaders[pos])) { + pos++ + } + + if pos >= len(customHeaders) { + break + } + + keyStart := pos + for pos < len(customHeaders) && customHeaders[pos] != '=' && !unicode.IsSpace(rune(customHeaders[pos])) { + pos++ + } + + if pos >= len(customHeaders) || customHeaders[pos] != '=' { + return nil, fmt.Errorf("invalid custom header format. Expected \"headerKey1=value1 headerKey2=value2 ....\" but got %v", customHeaders) + } + + key := customHeaders[keyStart:pos] + pos++ + + for pos < len(customHeaders) && unicode.IsSpace(rune(customHeaders[pos])) { + pos++ + } + + var value string + + if pos < len(customHeaders) { + if customHeaders[pos] == '"' || customHeaders[pos] == '\'' { + quoteChar := customHeaders[pos] + pos++ + valueStart := pos + + for pos < len(customHeaders) && + (customHeaders[pos] != quoteChar || + (pos > 0 && customHeaders[pos-1] == '\\')) { + pos++ + } + + if pos < len(customHeaders) { + value = customHeaders[valueStart:pos] + pos++ + } else { + value = customHeaders[valueStart:] + } + } else { + valueStart := pos + for pos < len(customHeaders) && !unicode.IsSpace(rune(customHeaders[pos])) { + pos++ + } + value = customHeaders[valueStart:pos] + } + } + + if key != "" && !strings.EqualFold(key, "User-Agent") && !strings.EqualFold(key, "Accept") { + headers[key] = value + } + } + + return headers, nil +} diff --git a/cli/packages/util/credentials.go b/cli/packages/util/credentials.go index 03722dc414..cd73e47caf 100644 --- a/cli/packages/util/credentials.go +++ b/cli/packages/util/credentials.go @@ -9,7 +9,6 @@ import ( "github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/config" "github.com/Infisical/infisical-merge/packages/models" - "github.com/go-resty/resty/v2" "github.com/zalando/go-keyring" ) @@ -85,7 +84,12 @@ func GetCurrentLoggedInUserDetails(setConfigVariables bool) (LoggedInUserDetails } // check to to see if the JWT is still valid - httpClient := resty.New(). + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return LoggedInUserDetails{}, fmt.Errorf("getCurrentLoggedInUserDetails: unable to get client with custom headers [err=%s]", err) + } + + httpClient. SetAuthToken(userCreds.JTWToken). SetHeader("Accept", "application/json") diff --git a/cli/packages/util/folders.go b/cli/packages/util/folders.go index 4715c71c35..6bba058426 100644 --- a/cli/packages/util/folders.go +++ b/cli/packages/util/folders.go @@ -6,7 +6,6 @@ import ( "github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/models" - "github.com/go-resty/resty/v2" "github.com/rs/zerolog/log" ) @@ -65,7 +64,11 @@ func GetAllFolders(params models.GetAllFoldersParameters) ([]models.SingleFolder func GetFoldersViaJTW(JTWToken string, workspaceId string, environmentName string, foldersPath string) ([]models.SingleFolder, error) { // set up resty client - httpClient := resty.New() + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return nil, err + } + httpClient.SetAuthToken(JTWToken). SetHeader("Accept", "application/json") @@ -100,7 +103,10 @@ func GetFoldersViaServiceToken(fullServiceToken string, workspaceId string, envi serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2]) - httpClient := resty.New() + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return nil, fmt.Errorf("unable to get client with custom headers [err=%v]", err) + } httpClient.SetAuthToken(serviceToken). SetHeader("Accept", "application/json") @@ -143,7 +149,11 @@ func GetFoldersViaServiceToken(fullServiceToken string, workspaceId string, envi } func GetFoldersViaMachineIdentity(accessToken string, workspaceId string, envSlug string, foldersPath string) ([]models.SingleFolder, error) { - httpClient := resty.New() + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return nil, err + } + httpClient.SetAuthToken(accessToken). SetHeader("Accept", "application/json") @@ -191,9 +201,12 @@ func CreateFolder(params models.CreateFolderParameters) (models.SingleFolder, er } // set up resty client - httpClient := resty.New() - httpClient. - SetAuthToken(params.InfisicalToken). + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return models.SingleFolder{}, err + } + + httpClient.SetAuthToken(params.InfisicalToken). SetHeader("Accept", "application/json"). SetHeader("Content-Type", "application/json") @@ -238,9 +251,12 @@ func DeleteFolder(params models.DeleteFolderParameters) ([]models.SingleFolder, } // set up resty client - httpClient := resty.New() - httpClient. - SetAuthToken(params.InfisicalToken). + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return nil, err + } + + httpClient.SetAuthToken(params.InfisicalToken). SetHeader("Accept", "application/json"). SetHeader("Content-Type", "application/json") diff --git a/cli/packages/util/helper.go b/cli/packages/util/helper.go index d3eb0cc346..346122a642 100644 --- a/cli/packages/util/helper.go +++ b/cli/packages/util/helper.go @@ -16,7 +16,6 @@ import ( "github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/models" - "github.com/go-resty/resty/v2" "github.com/spf13/cobra" ) @@ -120,7 +119,11 @@ func GetInfisicalToken(cmd *cobra.Command) (token *models.TokenDetails, err erro } func UniversalAuthLogin(clientId string, clientSecret string) (api.UniversalAuthLoginResponse, error) { - httpClient := resty.New() + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return api.UniversalAuthLoginResponse{}, err + } + httpClient.SetRetryCount(10000). SetRetryMaxWaitTime(20 * time.Second). SetRetryWaitTime(5 * time.Second) @@ -135,7 +138,11 @@ func UniversalAuthLogin(clientId string, clientSecret string) (api.UniversalAuth func RenewMachineIdentityAccessToken(accessToken string) (string, error) { - httpClient := resty.New() + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return "", err + } + httpClient.SetRetryCount(10000). SetRetryMaxWaitTime(20 * time.Second). SetRetryWaitTime(5 * time.Second) diff --git a/cli/packages/util/secrets.go b/cli/packages/util/secrets.go index 02079d656a..53f97f4c9f 100644 --- a/cli/packages/util/secrets.go +++ b/cli/packages/util/secrets.go @@ -14,7 +14,6 @@ import ( "github.com/Infisical/infisical-merge/packages/api" "github.com/Infisical/infisical-merge/packages/crypto" "github.com/Infisical/infisical-merge/packages/models" - "github.com/go-resty/resty/v2" "github.com/rs/zerolog/log" "github.com/zalando/go-keyring" "gopkg.in/yaml.v3" @@ -28,7 +27,10 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str serviceToken := fmt.Sprintf("%v.%v.%v", serviceTokenParts[0], serviceTokenParts[1], serviceTokenParts[2]) - httpClient := resty.New() + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return nil, fmt.Errorf("unable to get client with custom headers [err=%v]", err) + } httpClient.SetAuthToken(serviceToken). SetHeader("Accept", "application/json") @@ -79,7 +81,11 @@ func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment str } func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentName string, secretsPath string, includeImports bool, recursive bool, tagSlugs string, expandSecretReferences bool) (models.PlaintextSecretResult, error) { - httpClient := resty.New() + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return models.PlaintextSecretResult{}, err + } + httpClient.SetAuthToken(accessToken). SetHeader("Accept", "application/json") @@ -122,7 +128,11 @@ func GetPlainTextSecretsV3(accessToken string, workspaceId string, environmentNa } func GetSinglePlainTextSecretByNameV3(accessToken string, workspaceId string, environmentName string, secretsPath string, secretName string) (models.SingleEnvironmentVariable, string, error) { - httpClient := resty.New() + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return models.SingleEnvironmentVariable{}, "", err + } + httpClient.SetAuthToken(accessToken). SetHeader("Accept", "application/json") @@ -153,7 +163,11 @@ func GetSinglePlainTextSecretByNameV3(accessToken string, workspaceId string, en } func CreateDynamicSecretLease(accessToken string, projectSlug string, environmentName string, secretsPath string, slug string, ttl string) (models.DynamicSecretLease, error) { - httpClient := resty.New() + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return models.DynamicSecretLease{}, err + } + httpClient.SetAuthToken(accessToken). SetHeader("Accept", "application/json") @@ -525,7 +539,11 @@ func GetEnvelopmentBasedOnGitBranch(workspaceFile models.WorkspaceConfigFile) st } func GetPlainTextWorkspaceKey(authenticationToken string, receiverPrivateKey string, workspaceId string) ([]byte, error) { - httpClient := resty.New() + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return nil, fmt.Errorf("GetPlainTextWorkspaceKey: unable to get client with custom headers [err=%v]", err) + } + httpClient.SetAuthToken(authenticationToken). SetHeader("Accept", "application/json") @@ -672,9 +690,12 @@ func SetRawSecrets(secretArgs []string, secretType string, environmentName strin getAllEnvironmentVariablesRequest.InfisicalToken = tokenDetails.Token } - httpClient := resty.New(). - SetAuthToken(tokenDetails.Token). - SetHeader("Accept", "application/json") + httpClient, err := GetRestyClientWithCustomHeaders() + if err != nil { + return nil, fmt.Errorf("unable to get client with custom headers [err=%v]", err) + } + + httpClient.SetHeader("Accept", "application/json") // pull current secrets secrets, err := GetAllEnvironmentVariables(getAllEnvironmentVariablesRequest, "") diff --git a/docs/cli/faq.mdx b/docs/cli/faq.mdx index 47e89a48f6..02c539e619 100644 --- a/docs/cli/faq.mdx +++ b/docs/cli/faq.mdx @@ -33,3 +33,28 @@ Yes. This is simply a configuration file and contains no sensitive data. https://app.infisical.com/project//settings ``` + + + + The Infisical CLI supports custom HTTP headers for requests to servers that require additional authentication. Set these headers using the `INFISICAL_CUSTOM_HEADERS` environment variable: + + ```bash + export INFISICAL_CUSTOM_HEADERS="Access-Client-Id=your-client-id Access-Client-Secret=your-client-secret" + ``` + + After setting this environment variable, run your Infisical commands as usual. + + + + + Custom headers are necessary when your Infisical server is protected by services like Cloudflare Access or other reverse proxies that require specific authentication headers. Without this feature, you would need to implement security workarounds that might compromise your security posture. + + + + + Custom headers should be specified in the format `headername1=headervalue1 headername2=headervalue2`, with spaces separating each header-value pair. For example: + + ```bash + export INFISICAL_CUSTOM_HEADERS="Header1=value1 Header2=value2 Header3=value3" + ``` + \ No newline at end of file diff --git a/docs/cli/usage.mdx b/docs/cli/usage.mdx index d5b7acb4a7..a77a648d0d 100644 --- a/docs/cli/usage.mdx +++ b/docs/cli/usage.mdx @@ -120,6 +120,22 @@ The CLI is designed for a variety of secret management applications ranging from + + ## Custom Request Headers + + The Infisical CLI supports custom HTTP headers for requests to servers protected by authentication services such as Cloudflare Access. Configure these headers using the `INFISICAL_CUSTOM_HEADERS` environment variable: + + ```bash + # Syntax: headername1=headervalue1 headername2=headervalue2 + export INFISICAL_CUSTOM_HEADERS="Access-Client-Id=your-client-id Access-Client-Secret=your-client-secret" + + # Execute Infisical commands after setting the environment variable + infisical secrets ls + ``` + + This functionality enables secure interaction with Infisical instances that require specific authentication headers. + + ## History Your terminal keeps a history with the commands you run. When you create Infisical secrets directly from your terminal, they'll stay there for a while. diff --git a/docs/sdks/languages/go.mdx b/docs/sdks/languages/go.mdx index a0bf486874..47bc9a9ea9 100644 --- a/docs/sdks/languages/go.mdx +++ b/docs/sdks/languages/go.mdx @@ -99,6 +99,10 @@ client := infisical.NewInfisicalClient(context.Background(), infisical.Config{ Defines how long certain responses should be cached in memory, in seconds. When set to a positive value, responses from specific methods (like secret fetching) will be cached for this duration. Set to 0 to disable caching. + + + Allows you to pass custom headers to the HTTP requests made by the SDK. Expected format is a map of `Header1: Value1, Header2: Value 2`. +