mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-07 16:27:40 +00:00
Merge pull request #1981 from Infisical/daniel/unify-cli-auth-methods
feat(cli): Unify CLI auth methods
This commit is contained in:
@@ -391,6 +391,7 @@ func CallCreateSecretsV3(httpClient *resty.Client, request CreateSecretV3Request
|
|||||||
}
|
}
|
||||||
|
|
||||||
func CallDeleteSecretsV3(httpClient *resty.Client, request DeleteSecretV3Request) error {
|
func CallDeleteSecretsV3(httpClient *resty.Client, request DeleteSecretV3Request) error {
|
||||||
|
|
||||||
var secretsResponse GetEncryptedSecretsV3Response
|
var secretsResponse GetEncryptedSecretsV3Response
|
||||||
response, err := httpClient.
|
response, err := httpClient.
|
||||||
R().
|
R().
|
||||||
@@ -566,3 +567,39 @@ func CallCreateDynamicSecretLeaseV1(httpClient *resty.Client, request CreateDyna
|
|||||||
|
|
||||||
return createDynamicSecretLeaseResponse, nil
|
return createDynamicSecretLeaseResponse, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func CallCreateRawSecretsV3(httpClient *resty.Client, request CreateRawSecretV3Request) error {
|
||||||
|
response, err := httpClient.
|
||||||
|
R().
|
||||||
|
SetHeader("User-Agent", USER_AGENT).
|
||||||
|
SetBody(request).
|
||||||
|
Post(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("CallCreateRawSecretsV3: Unable to complete api request [err=%w]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.IsError() {
|
||||||
|
return fmt.Errorf("CallCreateRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func CallUpdateRawSecretsV3(httpClient *resty.Client, request UpdateRawSecretByNameV3Request) error {
|
||||||
|
response, err := httpClient.
|
||||||
|
R().
|
||||||
|
SetHeader("User-Agent", USER_AGENT).
|
||||||
|
SetBody(request).
|
||||||
|
Patch(fmt.Sprintf("%v/v3/secrets/raw/%s", config.INFISICAL_URL, request.SecretName))
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("CallUpdateRawSecretsV3: Unable to complete api request [err=%w]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if response.IsError() {
|
||||||
|
return fmt.Errorf("CallUpdateRawSecretsV3: Unsuccessful response [%v %v] [status-code=%v] [response=%v]", response.Request.Method, response.Request.URL, response.StatusCode(), response.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -161,6 +161,14 @@ type Secret struct {
|
|||||||
PlainTextKey string `json:"plainTextKey"`
|
PlainTextKey string `json:"plainTextKey"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type RawSecret struct {
|
||||||
|
SecretKey string `json:"secretKey,omitempty"`
|
||||||
|
SecretValue string `json:"secretValue,omitempty"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
SecretComment string `json:"secretComment,omitempty"`
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type GetEncryptedWorkspaceKeyRequest struct {
|
type GetEncryptedWorkspaceKeyRequest struct {
|
||||||
WorkspaceId string `json:"workspaceId"`
|
WorkspaceId string `json:"workspaceId"`
|
||||||
}
|
}
|
||||||
@@ -410,12 +418,23 @@ type CreateSecretV3Request struct {
|
|||||||
SecretPath string `json:"secretPath"`
|
SecretPath string `json:"secretPath"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type CreateRawSecretV3Request struct {
|
||||||
|
SecretName string `json:"-"`
|
||||||
|
WorkspaceID string `json:"workspaceId"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
Environment string `json:"environment"`
|
||||||
|
SecretPath string `json:"secretPath,omitempty"`
|
||||||
|
SecretValue string `json:"secretValue"`
|
||||||
|
SecretComment string `json:"secretComment,omitempty"`
|
||||||
|
SkipMultilineEncoding bool `json:"skipMultilineEncoding,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type DeleteSecretV3Request struct {
|
type DeleteSecretV3Request struct {
|
||||||
SecretName string `json:"secretName"`
|
SecretName string `json:"secretName"`
|
||||||
WorkspaceId string `json:"workspaceId"`
|
WorkspaceId string `json:"workspaceId"`
|
||||||
Environment string `json:"environment"`
|
Environment string `json:"environment"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type,omitempty"`
|
||||||
SecretPath string `json:"secretPath"`
|
SecretPath string `json:"secretPath,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type UpdateSecretByNameV3Request struct {
|
type UpdateSecretByNameV3Request struct {
|
||||||
@@ -428,6 +447,15 @@ type UpdateSecretByNameV3Request struct {
|
|||||||
SecretValueTag string `json:"secretValueTag"`
|
SecretValueTag string `json:"secretValueTag"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type UpdateRawSecretByNameV3Request struct {
|
||||||
|
SecretName string `json:"-"`
|
||||||
|
WorkspaceID string `json:"workspaceId"`
|
||||||
|
Environment string `json:"environment"`
|
||||||
|
SecretPath string `json:"secretPath,omitempty"`
|
||||||
|
SecretValue string `json:"secretValue"`
|
||||||
|
Type string `json:"type,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
type GetSingleSecretByNameV3Request struct {
|
type GetSingleSecretByNameV3Request struct {
|
||||||
SecretName string `json:"secretName"`
|
SecretName string `json:"secretName"`
|
||||||
WorkspaceId string `json:"workspaceId"`
|
WorkspaceId string `json:"workspaceId"`
|
||||||
|
|||||||
@@ -55,6 +55,11 @@ var exportCmd = &cobra.Command{
|
|||||||
util.HandleError(err)
|
util.HandleError(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
token, err := util.GetInfisicalToken(cmd)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
format, err := cmd.Flags().GetString("format")
|
format, err := cmd.Flags().GetString("format")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err)
|
util.HandleError(err)
|
||||||
@@ -70,11 +75,6 @@ var exportCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := util.GetInfisicalToken(cmd)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Unable to parse flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
tagSlugs, err := cmd.Flags().GetString("tags")
|
tagSlugs, err := cmd.Flags().GetString("tags")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
@@ -169,9 +169,9 @@ func init() {
|
|||||||
exportCmd.Flags().StringP("format", "f", "dotenv", "Set the format of the output file (dotenv, json, csv)")
|
exportCmd.Flags().StringP("format", "f", "dotenv", "Set the format of the output file (dotenv, json, csv)")
|
||||||
exportCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
exportCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
||||||
exportCmd.Flags().Bool("include-imports", true, "Imported linked secrets")
|
exportCmd.Flags().Bool("include-imports", true, "Imported linked secrets")
|
||||||
exportCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
exportCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
exportCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs")
|
exportCmd.Flags().StringP("tags", "t", "", "filter secrets by tag slugs")
|
||||||
exportCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets from")
|
exportCmd.Flags().String("projectId", "", "manually set the projectId to export secrets from")
|
||||||
exportCmd.Flags().String("path", "/", "get secrets within a folder path")
|
exportCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||||
exportCmd.Flags().String("template", "", "The path to the template file used to render secrets")
|
exportCmd.Flags().String("template", "", "The path to the template file used to render secrets")
|
||||||
}
|
}
|
||||||
|
|||||||
+49
-16
@@ -1,6 +1,7 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/Infisical/infisical-merge/packages/models"
|
"github.com/Infisical/infisical-merge/packages/models"
|
||||||
@@ -71,10 +72,6 @@ var getCmd = &cobra.Command{
|
|||||||
var createCmd = &cobra.Command{
|
var createCmd = &cobra.Command{
|
||||||
Use: "create",
|
Use: "create",
|
||||||
Short: "Create a folder",
|
Short: "Create a folder",
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
|
||||||
util.RequireLogin()
|
|
||||||
util.RequireLocalWorkspaceFile()
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
environmentName, _ := cmd.Flags().GetString("env")
|
environmentName, _ := cmd.Flags().GetString("env")
|
||||||
if !cmd.Flags().Changed("env") {
|
if !cmd.Flags().Changed("env") {
|
||||||
@@ -84,6 +81,16 @@ var createCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
token, err := util.GetInfisicalToken(cmd)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
projectId, err := cmd.Flags().GetString("projectId")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
folderPath, err := cmd.Flags().GetString("path")
|
folderPath, err := cmd.Flags().GetString("path")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
@@ -95,19 +102,31 @@ var createCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if folderName == "" {
|
if folderName == "" {
|
||||||
util.HandleError(fmt.Errorf("Invalid folder name"), "Folder name cannot be empty")
|
util.HandleError(errors.New("invalid folder name, folder name cannot be empty"))
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to get workspace file")
|
util.HandleError(err, "Unable to get workspace file")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if projectId == "" {
|
||||||
|
workspaceFile, err := util.GetWorkSpaceFromFile()
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to get workspace file")
|
||||||
|
}
|
||||||
|
|
||||||
|
projectId = workspaceFile.WorkspaceId
|
||||||
|
}
|
||||||
|
|
||||||
params := models.CreateFolderParameters{
|
params := models.CreateFolderParameters{
|
||||||
FolderName: folderName,
|
FolderName: folderName,
|
||||||
WorkspaceId: workspaceFile.WorkspaceId,
|
|
||||||
Environment: environmentName,
|
Environment: environmentName,
|
||||||
FolderPath: folderPath,
|
FolderPath: folderPath,
|
||||||
|
WorkspaceId: projectId,
|
||||||
|
}
|
||||||
|
|
||||||
|
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||||
|
params.InfisicalToken = token.Token
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = util.CreateFolder(params)
|
_, err = util.CreateFolder(params)
|
||||||
@@ -124,10 +143,6 @@ var createCmd = &cobra.Command{
|
|||||||
var deleteCmd = &cobra.Command{
|
var deleteCmd = &cobra.Command{
|
||||||
Use: "delete",
|
Use: "delete",
|
||||||
Short: "Delete a folder",
|
Short: "Delete a folder",
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
|
||||||
util.RequireLogin()
|
|
||||||
util.RequireLocalWorkspaceFile()
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
|
||||||
environmentName, _ := cmd.Flags().GetString("env")
|
environmentName, _ := cmd.Flags().GetString("env")
|
||||||
@@ -138,6 +153,16 @@ var deleteCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
token, err := util.GetInfisicalToken(cmd)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
projectId, err := cmd.Flags().GetString("projectId")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
folderPath, err := cmd.Flags().GetString("path")
|
folderPath, err := cmd.Flags().GetString("path")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
@@ -149,21 +174,29 @@ var deleteCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if folderName == "" {
|
if folderName == "" {
|
||||||
util.HandleError(fmt.Errorf("Invalid folder name"), "Folder name cannot be empty")
|
util.HandleError(errors.New("invalid folder name, folder name cannot be empty"))
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
if projectId == "" {
|
||||||
if err != nil {
|
workspaceFile, err := util.GetWorkSpaceFromFile()
|
||||||
util.HandleError(err, "Unable to get workspace file")
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to get workspace file")
|
||||||
|
}
|
||||||
|
|
||||||
|
projectId = workspaceFile.WorkspaceId
|
||||||
}
|
}
|
||||||
|
|
||||||
params := models.DeleteFolderParameters{
|
params := models.DeleteFolderParameters{
|
||||||
FolderName: folderName,
|
FolderName: folderName,
|
||||||
WorkspaceId: workspaceFile.WorkspaceId,
|
WorkspaceId: projectId,
|
||||||
Environment: environmentName,
|
Environment: environmentName,
|
||||||
FolderPath: folderPath,
|
FolderPath: folderPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||||
|
params.InfisicalToken = token.Token
|
||||||
|
}
|
||||||
|
|
||||||
_, err = util.DeleteFolder(params)
|
_, err = util.DeleteFolder(params)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to delete folder")
|
util.HandleError(err, "Unable to delete folder")
|
||||||
|
|||||||
@@ -237,8 +237,8 @@ func filterReservedEnvVars(env map[string]models.SingleEnvironmentVariable) {
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
rootCmd.AddCommand(runCmd)
|
rootCmd.AddCommand(runCmd)
|
||||||
runCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
runCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
runCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
|
runCmd.Flags().String("projectId", "", "manually set the project ID to fetch secrets from when using machine identity based auth")
|
||||||
runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from")
|
runCmd.Flags().StringP("env", "e", "dev", "Set the environment (dev, prod, etc.) from which your secrets should be pulled from")
|
||||||
runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
runCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
||||||
runCmd.Flags().Bool("include-imports", true, "Import linked secrets ")
|
runCmd.Flags().Bool("include-imports", true, "Import linked secrets ")
|
||||||
|
|||||||
+70
-232
@@ -4,23 +4,17 @@ Copyright (c) 2023 Infisical Inc.
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/sha256"
|
|
||||||
"encoding/base64"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"github.com/Infisical/infisical-merge/packages/api"
|
"github.com/Infisical/infisical-merge/packages/api"
|
||||||
"github.com/Infisical/infisical-merge/packages/crypto"
|
|
||||||
"github.com/Infisical/infisical-merge/packages/models"
|
"github.com/Infisical/infisical-merge/packages/models"
|
||||||
"github.com/Infisical/infisical-merge/packages/util"
|
"github.com/Infisical/infisical-merge/packages/util"
|
||||||
"github.com/Infisical/infisical-merge/packages/visualize"
|
"github.com/Infisical/infisical-merge/packages/visualize"
|
||||||
"github.com/go-resty/resty/v2"
|
"github.com/go-resty/resty/v2"
|
||||||
"github.com/posthog/posthog-go"
|
"github.com/posthog/posthog-go"
|
||||||
"github.com/rs/zerolog/log"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -160,14 +154,19 @@ var secretsSetCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
secretsPath, err := cmd.Flags().GetString("path")
|
token, err := util.GetInfisicalToken(cmd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
projectId, err := cmd.Flags().GetString("projectId")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to get your local config details")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
secretsPath, err := cmd.Flags().GetString("path")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
secretType, err := cmd.Flags().GetString("type")
|
secretType, err := cmd.Flags().GetString("type")
|
||||||
@@ -175,196 +174,18 @@ var secretsSetCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to parse secret type")
|
util.HandleError(err, "Unable to parse secret type")
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
|
var secretOperations []models.SecretSetOperation
|
||||||
|
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||||
|
secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, token)
|
||||||
|
} else {
|
||||||
|
util.RequireLogin()
|
||||||
|
util.RequireLocalWorkspaceFile()
|
||||||
|
|
||||||
|
secretOperations, err = util.SetEncryptedSecrets(args, secretType, environmentName, secretsPath)
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to authenticate")
|
util.HandleError(err, "Unable to set secrets")
|
||||||
}
|
|
||||||
|
|
||||||
if loggedInUserDetails.LoginExpired {
|
|
||||||
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
httpClient := resty.New().
|
|
||||||
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
|
|
||||||
SetHeader("Accept", "application/json")
|
|
||||||
|
|
||||||
request := api.GetEncryptedWorkspaceKeyRequest{
|
|
||||||
WorkspaceId: workspaceFile.WorkspaceId,
|
|
||||||
}
|
|
||||||
|
|
||||||
workspaceKeyResponse, err := api.CallGetEncryptedWorkspaceKey(httpClient, request)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "unable to get your encrypted workspace key")
|
|
||||||
}
|
|
||||||
|
|
||||||
encryptedWorkspaceKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey)
|
|
||||||
encryptedWorkspaceKeySenderPublicKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Sender.PublicKey)
|
|
||||||
encryptedWorkspaceKeyNonce, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce)
|
|
||||||
currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(loggedInUserDetails.UserCredentials.PrivateKey)
|
|
||||||
|
|
||||||
if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 {
|
|
||||||
log.Debug().Msgf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
|
|
||||||
util.PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again")
|
|
||||||
}
|
|
||||||
|
|
||||||
// decrypt workspace key
|
|
||||||
plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
|
|
||||||
|
|
||||||
infisicalTokenEnv := os.Getenv(util.INFISICAL_TOKEN_NAME)
|
|
||||||
|
|
||||||
// pull current secrets
|
|
||||||
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath, InfisicalToken: infisicalTokenEnv}, "")
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "unable to retrieve secrets")
|
|
||||||
}
|
|
||||||
|
|
||||||
type SecretSetOperation struct {
|
|
||||||
SecretKey string
|
|
||||||
SecretValue string
|
|
||||||
SecretOperation string
|
|
||||||
}
|
|
||||||
|
|
||||||
secretsToCreate := []api.Secret{}
|
|
||||||
secretsToModify := []api.Secret{}
|
|
||||||
secretOperations := []SecretSetOperation{}
|
|
||||||
|
|
||||||
sharedSecretMapByName := make(map[string]models.SingleEnvironmentVariable, len(secrets))
|
|
||||||
personalSecretMapByName := make(map[string]models.SingleEnvironmentVariable, len(secrets))
|
|
||||||
|
|
||||||
for _, secret := range secrets {
|
|
||||||
if secret.Type == util.SECRET_TYPE_PERSONAL {
|
|
||||||
personalSecretMapByName[secret.Key] = secret
|
|
||||||
} else {
|
|
||||||
sharedSecretMapByName[secret.Key] = secret
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, arg := range args {
|
|
||||||
splitKeyValueFromArg := strings.SplitN(arg, "=", 2)
|
|
||||||
if splitKeyValueFromArg[0] == "" || splitKeyValueFromArg[1] == "" {
|
|
||||||
util.PrintErrorMessageAndExit("ensure that each secret has a none empty key and value. Modify the input and try again")
|
|
||||||
}
|
|
||||||
|
|
||||||
if unicode.IsNumber(rune(splitKeyValueFromArg[0][0])) {
|
|
||||||
util.PrintErrorMessageAndExit("keys of secrets cannot start with a number. Modify the key name(s) and try again")
|
|
||||||
}
|
|
||||||
|
|
||||||
// Key and value from argument
|
|
||||||
key := splitKeyValueFromArg[0]
|
|
||||||
value := splitKeyValueFromArg[1]
|
|
||||||
|
|
||||||
hashedKey := fmt.Sprintf("%x", sha256.Sum256([]byte(key)))
|
|
||||||
encryptedKey, err := crypto.EncryptSymmetric([]byte(key), []byte(plainTextEncryptionKey))
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "unable to encrypt your secrets")
|
|
||||||
}
|
|
||||||
|
|
||||||
hashedValue := fmt.Sprintf("%x", sha256.Sum256([]byte(value)))
|
|
||||||
encryptedValue, err := crypto.EncryptSymmetric([]byte(value), []byte(plainTextEncryptionKey))
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "unable to encrypt your secrets")
|
|
||||||
}
|
|
||||||
|
|
||||||
var existingSecret models.SingleEnvironmentVariable
|
|
||||||
var doesSecretExist bool
|
|
||||||
|
|
||||||
if secretType == util.SECRET_TYPE_SHARED {
|
|
||||||
existingSecret, doesSecretExist = sharedSecretMapByName[key]
|
|
||||||
} else {
|
|
||||||
existingSecret, doesSecretExist = personalSecretMapByName[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
if doesSecretExist {
|
|
||||||
// case: secret exists in project so it needs to be modified
|
|
||||||
encryptedSecretDetails := api.Secret{
|
|
||||||
ID: existingSecret.ID,
|
|
||||||
SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText),
|
|
||||||
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
|
|
||||||
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
|
|
||||||
SecretValueHash: hashedValue,
|
|
||||||
PlainTextKey: key,
|
|
||||||
Type: existingSecret.Type,
|
|
||||||
}
|
|
||||||
|
|
||||||
// Only add to modifications if the value is different
|
|
||||||
if existingSecret.Value != value {
|
|
||||||
secretsToModify = append(secretsToModify, encryptedSecretDetails)
|
|
||||||
secretOperations = append(secretOperations, SecretSetOperation{
|
|
||||||
SecretKey: key,
|
|
||||||
SecretValue: value,
|
|
||||||
SecretOperation: "SECRET VALUE MODIFIED",
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
// Current value is same as exisitng so no change
|
|
||||||
secretOperations = append(secretOperations, SecretSetOperation{
|
|
||||||
SecretKey: key,
|
|
||||||
SecretValue: value,
|
|
||||||
SecretOperation: "SECRET VALUE UNCHANGED",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// case: secret doesn't exist in project so it needs to be created
|
|
||||||
encryptedSecretDetails := api.Secret{
|
|
||||||
SecretKeyCiphertext: base64.StdEncoding.EncodeToString(encryptedKey.CipherText),
|
|
||||||
SecretKeyIV: base64.StdEncoding.EncodeToString(encryptedKey.Nonce),
|
|
||||||
SecretKeyTag: base64.StdEncoding.EncodeToString(encryptedKey.AuthTag),
|
|
||||||
SecretKeyHash: hashedKey,
|
|
||||||
SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText),
|
|
||||||
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
|
|
||||||
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
|
|
||||||
SecretValueHash: hashedValue,
|
|
||||||
Type: secretType,
|
|
||||||
PlainTextKey: key,
|
|
||||||
}
|
|
||||||
secretsToCreate = append(secretsToCreate, encryptedSecretDetails)
|
|
||||||
secretOperations = append(secretOperations, SecretSetOperation{
|
|
||||||
SecretKey: key,
|
|
||||||
SecretValue: value,
|
|
||||||
SecretOperation: "SECRET CREATED",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, secret := range secretsToCreate {
|
|
||||||
createSecretRequest := api.CreateSecretV3Request{
|
|
||||||
WorkspaceID: workspaceFile.WorkspaceId,
|
|
||||||
Environment: environmentName,
|
|
||||||
SecretName: secret.PlainTextKey,
|
|
||||||
SecretKeyCiphertext: secret.SecretKeyCiphertext,
|
|
||||||
SecretKeyIV: secret.SecretKeyIV,
|
|
||||||
SecretKeyTag: secret.SecretKeyTag,
|
|
||||||
SecretValueCiphertext: secret.SecretValueCiphertext,
|
|
||||||
SecretValueIV: secret.SecretValueIV,
|
|
||||||
SecretValueTag: secret.SecretValueTag,
|
|
||||||
Type: secret.Type,
|
|
||||||
SecretPath: secretsPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = api.CallCreateSecretsV3(httpClient, createSecretRequest)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Unable to process new secret creations")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, secret := range secretsToModify {
|
|
||||||
updateSecretRequest := api.UpdateSecretByNameV3Request{
|
|
||||||
WorkspaceID: workspaceFile.WorkspaceId,
|
|
||||||
Environment: environmentName,
|
|
||||||
SecretValueCiphertext: secret.SecretValueCiphertext,
|
|
||||||
SecretValueIV: secret.SecretValueIV,
|
|
||||||
SecretValueTag: secret.SecretValueTag,
|
|
||||||
Type: secret.Type,
|
|
||||||
SecretPath: secretsPath,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = api.CallUpdateSecretsV3(httpClient, updateSecretRequest, secret.PlainTextKey)
|
|
||||||
if err != nil {
|
|
||||||
util.HandleError(err, "Unable to process secret update request")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Print secret operations
|
// Print secret operations
|
||||||
@@ -395,6 +216,16 @@ var secretsDeleteCmd = &cobra.Command{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
token, err := util.GetInfisicalToken(cmd)
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
projectId, err := cmd.Flags().GetString("projectId")
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to parse flag")
|
||||||
|
}
|
||||||
|
|
||||||
secretsPath, err := cmd.Flags().GetString("path")
|
secretsPath, err := cmd.Flags().GetString("path")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
@@ -405,33 +236,44 @@ var secretsDeleteCmd = &cobra.Command{
|
|||||||
util.HandleError(err, "Unable to parse flag")
|
util.HandleError(err, "Unable to parse flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
|
httpClient := resty.New().
|
||||||
if err != nil {
|
SetHeader("Accept", "application/json")
|
||||||
util.HandleError(err, "Unable to authenticate")
|
|
||||||
|
if projectId == "" {
|
||||||
|
workspaceFile, err := util.GetWorkSpaceFromFile()
|
||||||
|
if err != nil {
|
||||||
|
util.HandleError(err, "Unable to get local project details")
|
||||||
|
}
|
||||||
|
projectId = workspaceFile.WorkspaceId
|
||||||
}
|
}
|
||||||
|
|
||||||
if loggedInUserDetails.LoginExpired {
|
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
|
||||||
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
httpClient.SetAuthToken(token.Token)
|
||||||
}
|
} else {
|
||||||
|
util.RequireLogin()
|
||||||
|
util.RequireLocalWorkspaceFile()
|
||||||
|
|
||||||
workspaceFile, err := util.GetWorkSpaceFromFile()
|
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to get local project details")
|
util.HandleError(err, "Unable to authenticate")
|
||||||
|
}
|
||||||
|
|
||||||
|
if loggedInUserDetails.LoginExpired {
|
||||||
|
util.PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient.SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, secretName := range args {
|
for _, secretName := range args {
|
||||||
request := api.DeleteSecretV3Request{
|
request := api.DeleteSecretV3Request{
|
||||||
WorkspaceId: workspaceFile.WorkspaceId,
|
WorkspaceId: projectId,
|
||||||
Environment: environmentName,
|
Environment: environmentName,
|
||||||
SecretName: secretName,
|
SecretName: secretName,
|
||||||
Type: secretType,
|
Type: secretType,
|
||||||
SecretPath: secretsPath,
|
SecretPath: secretsPath,
|
||||||
}
|
}
|
||||||
|
|
||||||
httpClient := resty.New().
|
|
||||||
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
|
|
||||||
SetHeader("Accept", "application/json")
|
|
||||||
|
|
||||||
err = api.CallDeleteSecretsV3(httpClient, request)
|
err = api.CallDeleteSecretsV3(httpClient, request)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
util.HandleError(err, "Unable to complete your delete request")
|
util.HandleError(err, "Unable to complete your delete request")
|
||||||
@@ -789,13 +631,13 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod
|
|||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
secretsGenerateExampleEnvCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
|
secretsGenerateExampleEnvCmd.Flags().String("projectId", "", "manually set the projectId when using machine identity based auth")
|
||||||
secretsGenerateExampleEnvCmd.Flags().String("path", "/", "Fetch secrets from within a folder path")
|
secretsGenerateExampleEnvCmd.Flags().String("path", "/", "Fetch secrets from within a folder path")
|
||||||
secretsCmd.AddCommand(secretsGenerateExampleEnvCmd)
|
secretsCmd.AddCommand(secretsGenerateExampleEnvCmd)
|
||||||
|
|
||||||
secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
secretsGetCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
secretsGetCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
|
secretsGetCmd.Flags().String("projectId", "", "manually set the project ID to fetch secrets from when using machine identity based auth")
|
||||||
secretsGetCmd.Flags().String("path", "/", "get secrets within a folder path")
|
secretsGetCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||||
secretsGetCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
secretsGetCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
||||||
secretsGetCmd.Flags().Bool("raw-value", false, "Returns only the value of secret, only works with one secret")
|
secretsGetCmd.Flags().Bool("raw-value", false, "Returns only the value of secret, only works with one secret")
|
||||||
@@ -804,41 +646,37 @@ func init() {
|
|||||||
|
|
||||||
secretsCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
secretsCmd.Flags().Bool("secret-overriding", true, "Prioritizes personal secrets, if any, with the same name over shared secrets")
|
||||||
secretsCmd.AddCommand(secretsSetCmd)
|
secretsCmd.AddCommand(secretsSetCmd)
|
||||||
|
secretsSetCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
|
secretsSetCmd.Flags().String("projectId", "", "manually set the project ID to for setting secrets when using machine identity based auth")
|
||||||
secretsSetCmd.Flags().String("path", "/", "set secrets within a folder path")
|
secretsSetCmd.Flags().String("path", "/", "set secrets within a folder path")
|
||||||
secretsSetCmd.Flags().String("type", util.SECRET_TYPE_SHARED, "the type of secret to create: personal or shared")
|
secretsSetCmd.Flags().String("type", util.SECRET_TYPE_SHARED, "the type of secret to create: personal or shared")
|
||||||
|
|
||||||
// Only supports logged in users (JWT auth)
|
|
||||||
secretsSetCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
|
||||||
util.RequireLogin()
|
|
||||||
util.RequireLocalWorkspaceFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
secretsDeleteCmd.Flags().String("type", "personal", "the type of secret to delete: personal or shared (default: personal)")
|
secretsDeleteCmd.Flags().String("type", "personal", "the type of secret to delete: personal or shared (default: personal)")
|
||||||
|
secretsDeleteCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
|
secretsDeleteCmd.Flags().String("projectId", "", "manually set the projectId to delete secrets from when using machine identity based auth")
|
||||||
secretsDeleteCmd.Flags().String("path", "/", "get secrets within a folder path")
|
secretsDeleteCmd.Flags().String("path", "/", "get secrets within a folder path")
|
||||||
secretsCmd.AddCommand(secretsDeleteCmd)
|
secretsCmd.AddCommand(secretsDeleteCmd)
|
||||||
|
|
||||||
// Only supports logged in users (JWT auth)
|
|
||||||
secretsDeleteCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
|
|
||||||
util.RequireLogin()
|
|
||||||
util.RequireLocalWorkspaceFile()
|
|
||||||
}
|
|
||||||
|
|
||||||
// *** Folders sub command ***
|
// *** Folders sub command ***
|
||||||
folderCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on")
|
folderCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on")
|
||||||
|
|
||||||
// Add getCmd, createCmd and deleteCmd flags here
|
// Add getCmd, createCmd and deleteCmd flags here
|
||||||
getCmd.Flags().StringP("path", "p", "/", "The path from where folders should be fetched from")
|
getCmd.Flags().StringP("path", "p", "/", "The path from where folders should be fetched from")
|
||||||
getCmd.Flags().String("token", "", "Fetch folders using the infisical token")
|
getCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
getCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
|
getCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from when using machine identity based auth")
|
||||||
folderCmd.AddCommand(getCmd)
|
folderCmd.AddCommand(getCmd)
|
||||||
|
|
||||||
// Add createCmd flags here
|
// Add createCmd flags here
|
||||||
createCmd.Flags().StringP("path", "p", "/", "Path to where the folder should be created")
|
createCmd.Flags().StringP("path", "p", "/", "Path to where the folder should be created")
|
||||||
createCmd.Flags().StringP("name", "n", "", "Name of the folder to be created in selected `--path`")
|
createCmd.Flags().StringP("name", "n", "", "Name of the folder to be created in selected `--path`")
|
||||||
|
createCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
|
createCmd.Flags().String("projectId", "", "manually set the project ID for creating folders in when using machine identity based auth")
|
||||||
folderCmd.AddCommand(createCmd)
|
folderCmd.AddCommand(createCmd)
|
||||||
|
|
||||||
// Add deleteCmd flags here
|
// Add deleteCmd flags here
|
||||||
deleteCmd.Flags().StringP("path", "p", "/", "Path to the folder to be deleted")
|
deleteCmd.Flags().StringP("path", "p", "/", "Path to the folder to be deleted")
|
||||||
|
deleteCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
|
deleteCmd.Flags().String("projectId", "", "manually set the projectId to delete folders when using machine identity based auth")
|
||||||
deleteCmd.Flags().StringP("name", "n", "", "Name of the folder to be deleted within selected `--path`")
|
deleteCmd.Flags().StringP("name", "n", "", "Name of the folder to be deleted within selected `--path`")
|
||||||
folderCmd.AddCommand(deleteCmd)
|
folderCmd.AddCommand(deleteCmd)
|
||||||
|
|
||||||
@@ -846,8 +684,8 @@ func init() {
|
|||||||
|
|
||||||
// ** End of folders sub command
|
// ** End of folders sub command
|
||||||
|
|
||||||
secretsCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
|
secretsCmd.Flags().String("token", "", "Fetch secrets using service token or machine identity access token")
|
||||||
secretsCmd.Flags().String("projectId", "", "manually set the projectId to fetch folders from for machine identity")
|
secretsCmd.Flags().String("projectId", "", "manually set the projectId to fetch secrets when using machine identity based auth")
|
||||||
secretsCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on")
|
secretsCmd.PersistentFlags().String("env", "dev", "Used to select the environment name on which actions should be taken on")
|
||||||
secretsCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
secretsCmd.Flags().Bool("expand", true, "Parse shell parameter expansions in your secrets")
|
||||||
secretsCmd.Flags().Bool("include-imports", true, "Imported linked secrets ")
|
secretsCmd.Flags().Bool("include-imports", true, "Imported linked secrets ")
|
||||||
|
|||||||
@@ -134,3 +134,9 @@ type MachineIdentityCredentials struct {
|
|||||||
ClientId string
|
ClientId string
|
||||||
ClientSecret string
|
ClientSecret string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type SecretSetOperation struct {
|
||||||
|
SecretKey string
|
||||||
|
SecretValue string
|
||||||
|
SecretOperation string
|
||||||
|
}
|
||||||
|
|||||||
@@ -172,19 +172,28 @@ func GetFoldersViaMachineIdentity(accessToken string, workspaceId string, envSlu
|
|||||||
|
|
||||||
// CreateFolder creates a folder in Infisical
|
// CreateFolder creates a folder in Infisical
|
||||||
func CreateFolder(params models.CreateFolderParameters) (models.SingleFolder, error) {
|
func CreateFolder(params models.CreateFolderParameters) (models.SingleFolder, error) {
|
||||||
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()
|
|
||||||
if err != nil {
|
|
||||||
return models.SingleFolder{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if loggedInUserDetails.LoginExpired {
|
// If no token is provided, we will try to get the token from the current logged in user
|
||||||
PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
if params.InfisicalToken == "" {
|
||||||
|
RequireLogin()
|
||||||
|
RequireLocalWorkspaceFile()
|
||||||
|
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return models.SingleFolder{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if loggedInUserDetails.LoginExpired {
|
||||||
|
PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
params.InfisicalToken = loggedInUserDetails.UserCredentials.JTWToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up resty client
|
// set up resty client
|
||||||
httpClient := resty.New()
|
httpClient := resty.New()
|
||||||
httpClient.
|
httpClient.
|
||||||
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
|
SetAuthToken(params.InfisicalToken).
|
||||||
SetHeader("Accept", "application/json").
|
SetHeader("Accept", "application/json").
|
||||||
SetHeader("Content-Type", "application/json")
|
SetHeader("Content-Type", "application/json")
|
||||||
|
|
||||||
@@ -209,19 +218,29 @@ func CreateFolder(params models.CreateFolderParameters) (models.SingleFolder, er
|
|||||||
}
|
}
|
||||||
|
|
||||||
func DeleteFolder(params models.DeleteFolderParameters) ([]models.SingleFolder, error) {
|
func DeleteFolder(params models.DeleteFolderParameters) ([]models.SingleFolder, error) {
|
||||||
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if loggedInUserDetails.LoginExpired {
|
// If no token is provided, we will try to get the token from the current logged in user
|
||||||
PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
if params.InfisicalToken == "" {
|
||||||
|
RequireLogin()
|
||||||
|
RequireLocalWorkspaceFile()
|
||||||
|
|
||||||
|
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if loggedInUserDetails.LoginExpired {
|
||||||
|
PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
params.InfisicalToken = loggedInUserDetails.UserCredentials.JTWToken
|
||||||
}
|
}
|
||||||
|
|
||||||
// set up resty client
|
// set up resty client
|
||||||
httpClient := resty.New()
|
httpClient := resty.New()
|
||||||
httpClient.
|
httpClient.
|
||||||
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
|
SetAuthToken(params.InfisicalToken).
|
||||||
SetHeader("Accept", "application/json").
|
SetHeader("Accept", "application/json").
|
||||||
SetHeader("Content-Type", "application/json")
|
SetHeader("Content-Type", "application/json")
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package util
|
package util
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
@@ -9,6 +10,7 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
|
||||||
"github.com/Infisical/infisical-merge/packages/api"
|
"github.com/Infisical/infisical-merge/packages/api"
|
||||||
"github.com/Infisical/infisical-merge/packages/crypto"
|
"github.com/Infisical/infisical-merge/packages/crypto"
|
||||||
@@ -806,3 +808,336 @@ func GetPlainTextWorkspaceKey(authenticationToken string, receiverPrivateKey str
|
|||||||
|
|
||||||
return crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey), nil
|
return crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func SetEncryptedSecrets(secretArgs []string, secretType string, environmentName string, secretsPath string) ([]models.SecretSetOperation, error) {
|
||||||
|
|
||||||
|
workspaceFile, err := GetWorkSpaceFromFile()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to get your local config details [err=%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
loggedInUserDetails, err := GetCurrentLoggedInUserDetails()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to authenticate [err=%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if loggedInUserDetails.LoginExpired {
|
||||||
|
PrintErrorMessageAndExit("Your login session has expired, please run [infisical login] and try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := resty.New().
|
||||||
|
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
|
||||||
|
SetHeader("Accept", "application/json")
|
||||||
|
|
||||||
|
request := api.GetEncryptedWorkspaceKeyRequest{
|
||||||
|
WorkspaceId: workspaceFile.WorkspaceId,
|
||||||
|
}
|
||||||
|
|
||||||
|
workspaceKeyResponse, err := api.CallGetEncryptedWorkspaceKey(httpClient, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to get your encrypted workspace key [err=%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
encryptedWorkspaceKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.EncryptedKey)
|
||||||
|
encryptedWorkspaceKeySenderPublicKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Sender.PublicKey)
|
||||||
|
encryptedWorkspaceKeyNonce, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.Nonce)
|
||||||
|
currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(loggedInUserDetails.UserCredentials.PrivateKey)
|
||||||
|
|
||||||
|
if len(currentUsersPrivateKey) == 0 || len(encryptedWorkspaceKeySenderPublicKey) == 0 {
|
||||||
|
log.Debug().Msgf("Missing credentials for generating plainTextEncryptionKey: [currentUsersPrivateKey=%s] [encryptedWorkspaceKeySenderPublicKey=%s]", currentUsersPrivateKey, encryptedWorkspaceKeySenderPublicKey)
|
||||||
|
PrintErrorMessageAndExit("Some required user credentials are missing to generate your [plainTextEncryptionKey]. Please run [infisical login] then try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
// decrypt workspace key
|
||||||
|
plainTextEncryptionKey := crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
|
||||||
|
|
||||||
|
infisicalTokenEnv := os.Getenv(INFISICAL_TOKEN_NAME)
|
||||||
|
|
||||||
|
// pull current secrets
|
||||||
|
secrets, err := GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath, InfisicalToken: infisicalTokenEnv}, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to retrieve secrets [err=%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secretsToCreate := []api.Secret{}
|
||||||
|
secretsToModify := []api.Secret{}
|
||||||
|
secretOperations := []models.SecretSetOperation{}
|
||||||
|
|
||||||
|
sharedSecretMapByName := make(map[string]models.SingleEnvironmentVariable, len(secrets))
|
||||||
|
personalSecretMapByName := make(map[string]models.SingleEnvironmentVariable, len(secrets))
|
||||||
|
|
||||||
|
for _, secret := range secrets {
|
||||||
|
if secret.Type == SECRET_TYPE_PERSONAL {
|
||||||
|
personalSecretMapByName[secret.Key] = secret
|
||||||
|
} else {
|
||||||
|
sharedSecretMapByName[secret.Key] = secret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arg := range secretArgs {
|
||||||
|
splitKeyValueFromArg := strings.SplitN(arg, "=", 2)
|
||||||
|
if splitKeyValueFromArg[0] == "" || splitKeyValueFromArg[1] == "" {
|
||||||
|
PrintErrorMessageAndExit("ensure that each secret has a none empty key and value. Modify the input and try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
if unicode.IsNumber(rune(splitKeyValueFromArg[0][0])) {
|
||||||
|
PrintErrorMessageAndExit("keys of secrets cannot start with a number. Modify the key name(s) and try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key and value from argument
|
||||||
|
key := splitKeyValueFromArg[0]
|
||||||
|
value := splitKeyValueFromArg[1]
|
||||||
|
|
||||||
|
hashedKey := fmt.Sprintf("%x", sha256.Sum256([]byte(key)))
|
||||||
|
encryptedKey, err := crypto.EncryptSymmetric([]byte(key), []byte(plainTextEncryptionKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to encrypt your secrets [err=%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
hashedValue := fmt.Sprintf("%x", sha256.Sum256([]byte(value)))
|
||||||
|
encryptedValue, err := crypto.EncryptSymmetric([]byte(value), []byte(plainTextEncryptionKey))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to encrypt your secrets [err=%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
var existingSecret models.SingleEnvironmentVariable
|
||||||
|
var doesSecretExist bool
|
||||||
|
|
||||||
|
if secretType == SECRET_TYPE_SHARED {
|
||||||
|
existingSecret, doesSecretExist = sharedSecretMapByName[key]
|
||||||
|
} else {
|
||||||
|
existingSecret, doesSecretExist = personalSecretMapByName[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
if doesSecretExist {
|
||||||
|
// case: secret exists in project so it needs to be modified
|
||||||
|
encryptedSecretDetails := api.Secret{
|
||||||
|
ID: existingSecret.ID,
|
||||||
|
SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText),
|
||||||
|
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
|
||||||
|
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
|
||||||
|
SecretValueHash: hashedValue,
|
||||||
|
PlainTextKey: key,
|
||||||
|
Type: existingSecret.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add to modifications if the value is different
|
||||||
|
if existingSecret.Value != value {
|
||||||
|
secretsToModify = append(secretsToModify, encryptedSecretDetails)
|
||||||
|
secretOperations = append(secretOperations, models.SecretSetOperation{
|
||||||
|
SecretKey: key,
|
||||||
|
SecretValue: value,
|
||||||
|
SecretOperation: "SECRET VALUE MODIFIED",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Current value is same as exisitng so no change
|
||||||
|
secretOperations = append(secretOperations, models.SecretSetOperation{
|
||||||
|
SecretKey: key,
|
||||||
|
SecretValue: value,
|
||||||
|
SecretOperation: "SECRET VALUE UNCHANGED",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// case: secret doesn't exist in project so it needs to be created
|
||||||
|
encryptedSecretDetails := api.Secret{
|
||||||
|
SecretKeyCiphertext: base64.StdEncoding.EncodeToString(encryptedKey.CipherText),
|
||||||
|
SecretKeyIV: base64.StdEncoding.EncodeToString(encryptedKey.Nonce),
|
||||||
|
SecretKeyTag: base64.StdEncoding.EncodeToString(encryptedKey.AuthTag),
|
||||||
|
SecretKeyHash: hashedKey,
|
||||||
|
SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText),
|
||||||
|
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
|
||||||
|
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
|
||||||
|
SecretValueHash: hashedValue,
|
||||||
|
Type: secretType,
|
||||||
|
PlainTextKey: key,
|
||||||
|
}
|
||||||
|
secretsToCreate = append(secretsToCreate, encryptedSecretDetails)
|
||||||
|
secretOperations = append(secretOperations, models.SecretSetOperation{
|
||||||
|
SecretKey: key,
|
||||||
|
SecretValue: value,
|
||||||
|
SecretOperation: "SECRET CREATED",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, secret := range secretsToCreate {
|
||||||
|
createSecretRequest := api.CreateSecretV3Request{
|
||||||
|
WorkspaceID: workspaceFile.WorkspaceId,
|
||||||
|
Environment: environmentName,
|
||||||
|
SecretName: secret.PlainTextKey,
|
||||||
|
SecretKeyCiphertext: secret.SecretKeyCiphertext,
|
||||||
|
SecretKeyIV: secret.SecretKeyIV,
|
||||||
|
SecretKeyTag: secret.SecretKeyTag,
|
||||||
|
SecretValueCiphertext: secret.SecretValueCiphertext,
|
||||||
|
SecretValueIV: secret.SecretValueIV,
|
||||||
|
SecretValueTag: secret.SecretValueTag,
|
||||||
|
Type: secret.Type,
|
||||||
|
SecretPath: secretsPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.CallCreateSecretsV3(httpClient, createSecretRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to process new secret creations [err=%v]", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, secret := range secretsToModify {
|
||||||
|
updateSecretRequest := api.UpdateSecretByNameV3Request{
|
||||||
|
WorkspaceID: workspaceFile.WorkspaceId,
|
||||||
|
Environment: environmentName,
|
||||||
|
SecretValueCiphertext: secret.SecretValueCiphertext,
|
||||||
|
SecretValueIV: secret.SecretValueIV,
|
||||||
|
SecretValueTag: secret.SecretValueTag,
|
||||||
|
Type: secret.Type,
|
||||||
|
SecretPath: secretsPath,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.CallUpdateSecretsV3(httpClient, updateSecretRequest, secret.PlainTextKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to process secret update request [err=%v]", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return secretOperations, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetRawSecrets(secretArgs []string, secretType string, environmentName string, secretsPath string, projectId string, tokenDetails *models.TokenDetails) ([]models.SecretSetOperation, error) {
|
||||||
|
|
||||||
|
if tokenDetails == nil {
|
||||||
|
return nil, fmt.Errorf("unable to process set secret operations, token details are missing")
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllEnvironmentVariablesRequest := models.GetAllSecretsParameters{Environment: environmentName, SecretsPath: secretsPath, WorkspaceId: projectId}
|
||||||
|
if tokenDetails.Type == UNIVERSAL_AUTH_TOKEN_IDENTIFIER {
|
||||||
|
getAllEnvironmentVariablesRequest.UniversalAuthAccessToken = tokenDetails.Token
|
||||||
|
} else {
|
||||||
|
getAllEnvironmentVariablesRequest.InfisicalToken = tokenDetails.Token
|
||||||
|
}
|
||||||
|
|
||||||
|
httpClient := resty.New().
|
||||||
|
SetAuthToken(tokenDetails.Token).
|
||||||
|
SetHeader("Accept", "application/json")
|
||||||
|
|
||||||
|
// pull current secrets
|
||||||
|
secrets, err := GetAllEnvironmentVariables(getAllEnvironmentVariablesRequest, "")
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to retrieve secrets [err=%v]", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
secretsToCreate := []api.RawSecret{}
|
||||||
|
secretsToModify := []api.RawSecret{}
|
||||||
|
secretOperations := []models.SecretSetOperation{}
|
||||||
|
|
||||||
|
sharedSecretMapByName := make(map[string]models.SingleEnvironmentVariable, len(secrets))
|
||||||
|
personalSecretMapByName := make(map[string]models.SingleEnvironmentVariable, len(secrets))
|
||||||
|
|
||||||
|
for _, secret := range secrets {
|
||||||
|
if secret.Type == SECRET_TYPE_PERSONAL {
|
||||||
|
personalSecretMapByName[secret.Key] = secret
|
||||||
|
} else {
|
||||||
|
sharedSecretMapByName[secret.Key] = secret
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, arg := range secretArgs {
|
||||||
|
splitKeyValueFromArg := strings.SplitN(arg, "=", 2)
|
||||||
|
if splitKeyValueFromArg[0] == "" || splitKeyValueFromArg[1] == "" {
|
||||||
|
PrintErrorMessageAndExit("ensure that each secret has a none empty key and value. Modify the input and try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
if unicode.IsNumber(rune(splitKeyValueFromArg[0][0])) {
|
||||||
|
PrintErrorMessageAndExit("keys of secrets cannot start with a number. Modify the key name(s) and try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Key and value from argument
|
||||||
|
key := splitKeyValueFromArg[0]
|
||||||
|
value := splitKeyValueFromArg[1]
|
||||||
|
|
||||||
|
var existingSecret models.SingleEnvironmentVariable
|
||||||
|
var doesSecretExist bool
|
||||||
|
|
||||||
|
if secretType == SECRET_TYPE_SHARED {
|
||||||
|
existingSecret, doesSecretExist = sharedSecretMapByName[key]
|
||||||
|
} else {
|
||||||
|
existingSecret, doesSecretExist = personalSecretMapByName[key]
|
||||||
|
}
|
||||||
|
|
||||||
|
if doesSecretExist {
|
||||||
|
// case: secret exists in project so it needs to be modified
|
||||||
|
encryptedSecretDetails := api.RawSecret{
|
||||||
|
ID: existingSecret.ID,
|
||||||
|
SecretValue: value,
|
||||||
|
SecretKey: key,
|
||||||
|
Type: existingSecret.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only add to modifications if the value is different
|
||||||
|
if existingSecret.Value != value {
|
||||||
|
secretsToModify = append(secretsToModify, encryptedSecretDetails)
|
||||||
|
secretOperations = append(secretOperations, models.SecretSetOperation{
|
||||||
|
SecretKey: key,
|
||||||
|
SecretValue: value,
|
||||||
|
SecretOperation: "SECRET VALUE MODIFIED",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Current value is same as existing so no change
|
||||||
|
secretOperations = append(secretOperations, models.SecretSetOperation{
|
||||||
|
SecretKey: key,
|
||||||
|
SecretValue: value,
|
||||||
|
SecretOperation: "SECRET VALUE UNCHANGED",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// case: secret doesn't exist in project so it needs to be created
|
||||||
|
encryptedSecretDetails := api.RawSecret{
|
||||||
|
SecretKey: key,
|
||||||
|
SecretValue: value,
|
||||||
|
Type: secretType,
|
||||||
|
}
|
||||||
|
secretsToCreate = append(secretsToCreate, encryptedSecretDetails)
|
||||||
|
secretOperations = append(secretOperations, models.SecretSetOperation{
|
||||||
|
SecretKey: key,
|
||||||
|
SecretValue: value,
|
||||||
|
SecretOperation: "SECRET CREATED",
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, secret := range secretsToCreate {
|
||||||
|
createSecretRequest := api.CreateRawSecretV3Request{
|
||||||
|
SecretName: secret.SecretKey,
|
||||||
|
SecretValue: secret.SecretValue,
|
||||||
|
Type: secret.Type,
|
||||||
|
SecretPath: secretsPath,
|
||||||
|
WorkspaceID: projectId,
|
||||||
|
Environment: environmentName,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.CallCreateRawSecretsV3(httpClient, createSecretRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to process new secret creations [err=%v]", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, secret := range secretsToModify {
|
||||||
|
updateSecretRequest := api.UpdateRawSecretByNameV3Request{
|
||||||
|
SecretName: secret.SecretKey,
|
||||||
|
SecretValue: secret.SecretValue,
|
||||||
|
SecretPath: secretsPath,
|
||||||
|
WorkspaceID: projectId,
|
||||||
|
Environment: environmentName,
|
||||||
|
Type: secret.Type,
|
||||||
|
}
|
||||||
|
|
||||||
|
err = api.CallUpdateRawSecretsV3(httpClient, updateSecretRequest)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to process secret update request [err=%v]", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return secretOperations, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user