mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
383 lines
12 KiB
Go
383 lines
12 KiB
Go
/*
|
|
Copyright © 2022 NAME HERE <EMAIL ADDRESS>
|
|
*/
|
|
package cmd
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"fmt"
|
|
"strings"
|
|
"unicode"
|
|
|
|
"crypto/sha256"
|
|
|
|
"github.com/Infisical/infisical-merge/packages/http"
|
|
"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"
|
|
log "github.com/sirupsen/logrus"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var secretsCmd = &cobra.Command{
|
|
Example: `infisical secrets"`,
|
|
Short: "Used to create, read update and delete secrets",
|
|
Use: "secrets",
|
|
DisableFlagsInUseLine: true,
|
|
PreRun: toggleDebug,
|
|
Args: cobra.NoArgs,
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
environmentName, err := cmd.Flags().GetString("env")
|
|
if err != nil {
|
|
log.Errorln("Unable to parse the environment name flag")
|
|
log.Debugln(err)
|
|
return
|
|
}
|
|
|
|
secrets, err := util.GetAllEnvironmentVariables("", environmentName)
|
|
secrets = util.SubstituteSecrets(secrets)
|
|
if err != nil {
|
|
log.Debugln(err)
|
|
return
|
|
}
|
|
|
|
visualize.PrintAllSecretDetails(secrets)
|
|
},
|
|
}
|
|
|
|
var secretsGetCmd = &cobra.Command{
|
|
Example: `secrets get <secret name A> <secret name B>..."`,
|
|
Short: "Used to retrieve secrets by name",
|
|
Use: "get [secrets]",
|
|
DisableFlagsInUseLine: true,
|
|
Args: cobra.MinimumNArgs(1),
|
|
PreRun: toggleDebug,
|
|
Run: getSecretsByNames,
|
|
}
|
|
|
|
var secretsSetCmd = &cobra.Command{
|
|
Example: `secrets set <secret name A> <secret value A> <secret name B> <secret value B>..."`,
|
|
Short: "Used update retrieve secrets by name",
|
|
Use: "set [secrets]",
|
|
DisableFlagsInUseLine: true,
|
|
PreRun: toggleDebug,
|
|
Args: cobra.MinimumNArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
secretType, err := cmd.Flags().GetString("type")
|
|
if err != nil {
|
|
log.Errorln("Unable to parse the secret type flag")
|
|
log.Debugln(err)
|
|
return
|
|
}
|
|
|
|
if !util.IsSecretTypeValid(secretType) {
|
|
log.Errorf("secret type can only be `personal` or `shared`. You have entered [%v]", secretType)
|
|
return
|
|
}
|
|
|
|
environmentName, err := cmd.Flags().GetString("env")
|
|
if err != nil {
|
|
log.Errorln("Unable to parse the environment name flag")
|
|
log.Debugln(err)
|
|
return
|
|
}
|
|
|
|
if !util.IsSecretEnvironmentValid(environmentName) {
|
|
log.Errorln("You have entered a invalid environment name. Environment names can only be prod, dev, test or staging")
|
|
return
|
|
}
|
|
|
|
workspaceFileExists := util.WorkspaceConfigFileExistsInCurrentPath()
|
|
if !workspaceFileExists {
|
|
log.Error("You have not yet connected to an Infisical Project. Please run [infisical init]")
|
|
}
|
|
|
|
workspaceFile, err := util.GetWorkSpaceFromFile()
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
if !loggedInUserDetails.IsUserLoggedIn {
|
|
log.Error("You are not logged in yet. Please run [infisical login] then try again")
|
|
return
|
|
}
|
|
|
|
if loggedInUserDetails.IsUserLoggedIn && loggedInUserDetails.LoginExpired {
|
|
log.Error("Your login has expired. Please run [infisical login] then try again")
|
|
return
|
|
}
|
|
|
|
httpClient := resty.New().
|
|
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
|
|
SetHeader("Accept", "application/json")
|
|
|
|
request := models.GetEncryptedWorkspaceKeyRequest{
|
|
WorkspaceId: workspaceFile.WorkspaceId,
|
|
}
|
|
|
|
workspaceKeyResponse, err := http.CallGetEncryptedWorkspaceKey(httpClient, request)
|
|
if err != nil {
|
|
log.Errorf("unable to get your encrypted workspace key. [err=%v]", err)
|
|
return
|
|
}
|
|
|
|
encryptedWorkspaceKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.LatestKey.EncryptedKey)
|
|
encryptedWorkspaceKeySenderPublicKey, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.LatestKey.Sender.PublicKey)
|
|
encryptedWorkspaceKeyNonce, _ := base64.StdEncoding.DecodeString(workspaceKeyResponse.LatestKey.Nonce)
|
|
currentUsersPrivateKey, _ := base64.StdEncoding.DecodeString(loggedInUserDetails.UserCredentials.PrivateKey)
|
|
|
|
// decrypt workspace key
|
|
plainTextEncryptionKey := util.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey)
|
|
|
|
// pull current secrets
|
|
secrets, err := util.GetAllEnvironmentVariables("", environmentName)
|
|
if err != nil {
|
|
log.Error("unable to retrieve secrets. Run with -d to see full logs")
|
|
log.Debug(err)
|
|
}
|
|
|
|
secretsToCreate := []models.Secret{}
|
|
secretsToModify := []models.Secret{}
|
|
|
|
secretByKey := getSecretsByKeys(secrets)
|
|
|
|
for _, arg := range args {
|
|
splitKeyValueFromArg := strings.SplitN(arg, "=", 2)
|
|
if splitKeyValueFromArg[0] == "" || splitKeyValueFromArg[1] == "" {
|
|
log.Error("ensure that each secret has a none empty key and value. Modify the input and try again")
|
|
return
|
|
}
|
|
|
|
if unicode.IsNumber(rune(splitKeyValueFromArg[0][0])) {
|
|
log.Error("keys of secrets cannot start with a number. Modify the key name(s) and try again")
|
|
return
|
|
}
|
|
|
|
key := splitKeyValueFromArg[0]
|
|
value := splitKeyValueFromArg[1]
|
|
|
|
hashedKey := fmt.Sprintf("%x", sha256.Sum256([]byte(key)))
|
|
encryptedKey, err := util.EncryptSymmetric([]byte(key), []byte(plainTextEncryptionKey))
|
|
if err != nil {
|
|
log.Errorf("unable to encrypt your secrets [err=%v]", err)
|
|
}
|
|
|
|
hashedValue := fmt.Sprintf("%x", sha256.Sum256([]byte(value)))
|
|
encryptedValue, err := util.EncryptSymmetric([]byte(value), []byte(plainTextEncryptionKey))
|
|
if err != nil {
|
|
log.Errorf("unable to encrypt your secrets [err=%v]", err)
|
|
}
|
|
|
|
if existingSecret, ok := secretByKey[key]; ok {
|
|
// case: secret exists in project so it needs to be modified
|
|
encryptedSecretDetails := models.Secret{
|
|
ID: existingSecret.ID,
|
|
SecretValueCiphertext: base64.StdEncoding.EncodeToString(encryptedValue.CipherText),
|
|
SecretValueIV: base64.StdEncoding.EncodeToString(encryptedValue.Nonce),
|
|
SecretValueTag: base64.StdEncoding.EncodeToString(encryptedValue.AuthTag),
|
|
SecretValueHash: hashedValue,
|
|
}
|
|
|
|
// Only add to modifications if the value is different
|
|
if existingSecret.Value != value {
|
|
secretsToModify = append(secretsToModify, encryptedSecretDetails)
|
|
}
|
|
|
|
} else {
|
|
// case: secret doesn't exist in project so it needs to be created
|
|
encryptedSecretDetails := models.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,
|
|
}
|
|
secretsToCreate = append(secretsToCreate, encryptedSecretDetails)
|
|
}
|
|
}
|
|
|
|
if len(secretsToCreate) > 0 {
|
|
batchCreateRequest := models.BatchCreateSecretsByWorkspaceAndEnvRequest{
|
|
WorkspaceId: workspaceFile.WorkspaceId,
|
|
EnvironmentName: environmentName,
|
|
Secrets: secretsToCreate,
|
|
}
|
|
|
|
err = http.CallBatchCreateSecretsByWorkspaceAndEnv(httpClient, batchCreateRequest)
|
|
if err != nil {
|
|
log.Errorf("Unable to process new secret creations because %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
if len(secretsToModify) > 0 {
|
|
batchModifyRequest := models.BatchModifySecretsByWorkspaceAndEnvRequest{
|
|
WorkspaceId: workspaceFile.WorkspaceId,
|
|
EnvironmentName: environmentName,
|
|
Secrets: secretsToModify,
|
|
}
|
|
|
|
err = http.CallBatchModifySecretsByWorkspaceAndEnv(httpClient, batchModifyRequest)
|
|
if err != nil {
|
|
log.Errorf("Unable to process the modifications to your secrets because %v", err)
|
|
return
|
|
}
|
|
}
|
|
|
|
log.Infoln("secrets have been successfully set")
|
|
},
|
|
}
|
|
|
|
var secretsDeleteCmd = &cobra.Command{
|
|
Example: `secrets delete <secret name A> <secret name B>..."`,
|
|
Short: "Used to delete secrets by name",
|
|
Use: "delete [secrets]",
|
|
DisableFlagsInUseLine: true,
|
|
PreRun: toggleDebug,
|
|
Args: cobra.MinimumNArgs(1),
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
environmentName, err := cmd.Flags().GetString("env")
|
|
if err != nil {
|
|
log.Errorln("Unable to parse the environment name flag")
|
|
log.Debugln(err)
|
|
return
|
|
}
|
|
|
|
loggedInUserDetails, err := util.GetCurrentLoggedInUserDetails()
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
if !loggedInUserDetails.IsUserLoggedIn {
|
|
log.Error("You are not logged in yet. Please run [infisical login] then try again")
|
|
return
|
|
}
|
|
|
|
if loggedInUserDetails.IsUserLoggedIn && loggedInUserDetails.LoginExpired {
|
|
log.Error("Your login has expired. Please run [infisical login] then try again")
|
|
return
|
|
}
|
|
|
|
workspaceFileExists := util.WorkspaceConfigFileExistsInCurrentPath()
|
|
if !workspaceFileExists {
|
|
log.Error("You have not yet connected to an Infisical Project. Please run [infisical init]")
|
|
}
|
|
|
|
workspaceFile, err := util.GetWorkSpaceFromFile()
|
|
if err != nil {
|
|
log.Error(err)
|
|
return
|
|
}
|
|
|
|
secrets, err := util.GetAllEnvironmentVariables("", environmentName)
|
|
if err != nil {
|
|
log.Error("Unable to retrieve secrets. Run with -d to see full logs")
|
|
log.Debug(err)
|
|
}
|
|
|
|
secretByKey := getSecretsByKeys(secrets)
|
|
validSecretIdsToDelete := []string{}
|
|
invalidSecretNamesThatDoNotExist := []string{}
|
|
|
|
for _, secretKeyFromArg := range args {
|
|
if value, ok := secretByKey[secretKeyFromArg]; ok {
|
|
validSecretIdsToDelete = append(validSecretIdsToDelete, value.ID)
|
|
} else {
|
|
invalidSecretNamesThatDoNotExist = append(invalidSecretNamesThatDoNotExist, secretKeyFromArg)
|
|
}
|
|
}
|
|
|
|
if len(invalidSecretNamesThatDoNotExist) != 0 {
|
|
log.Errorf("secret name(s) [%v] does not exist in your project. To see which secrets exist run [infisical secrets]", strings.Join(invalidSecretNamesThatDoNotExist, ", "))
|
|
return
|
|
}
|
|
|
|
request := models.BatchDeleteSecretsBySecretIdsRequest{
|
|
WorkspaceId: workspaceFile.WorkspaceId,
|
|
EnvironmentName: environmentName,
|
|
SecretIds: validSecretIdsToDelete,
|
|
}
|
|
|
|
httpClient := resty.New().
|
|
SetAuthToken(loggedInUserDetails.UserCredentials.JTWToken).
|
|
SetHeader("Accept", "application/json")
|
|
|
|
err = http.CallBatchDeleteSecretsByWorkspaceAndEnv(httpClient, request)
|
|
if err != nil {
|
|
log.Errorf("Unable to complete your request because %v", err)
|
|
return
|
|
}
|
|
|
|
log.Infof("secret name(s) [%v] have been deleted from your project", strings.Join(args, ", "))
|
|
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
secretsCmd.AddCommand(secretsGetCmd)
|
|
secretsSetCmd.Flags().String("type", "shared", "Used to set the type for secrets")
|
|
secretsCmd.AddCommand(secretsSetCmd)
|
|
secretsCmd.AddCommand(secretsDeleteCmd)
|
|
secretsCmd.PersistentFlags().String("env", "dev", "Used to define the environment name on which actions should be taken on")
|
|
rootCmd.AddCommand(secretsCmd)
|
|
}
|
|
|
|
func getSecretsByNames(cmd *cobra.Command, args []string) {
|
|
environmentName, err := cmd.Flags().GetString("env")
|
|
if err != nil {
|
|
log.Errorln("Unable to parse the environment name flag")
|
|
log.Debugln(err)
|
|
return
|
|
}
|
|
|
|
secrets, err := util.GetAllEnvironmentVariables("", environmentName)
|
|
if err != nil {
|
|
log.Error("Unable to retrieve secrets. Run with -d to see full logs")
|
|
log.Debug(err)
|
|
}
|
|
|
|
requestedSecrets := []models.SingleEnvironmentVariable{}
|
|
|
|
secretsMap := make(map[string]models.SingleEnvironmentVariable)
|
|
for _, secret := range secrets {
|
|
secretsMap[secret.Key] = secret
|
|
}
|
|
|
|
for _, secretKeyFromArg := range args {
|
|
if value, ok := secretsMap[secretKeyFromArg]; ok {
|
|
requestedSecrets = append(requestedSecrets, value)
|
|
} else {
|
|
requestedSecrets = append(requestedSecrets, models.SingleEnvironmentVariable{
|
|
Key: secretKeyFromArg,
|
|
Type: "*not found*",
|
|
Value: "*not found*",
|
|
})
|
|
}
|
|
}
|
|
|
|
visualize.PrintAllSecretDetails(requestedSecrets)
|
|
}
|
|
|
|
func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]models.SingleEnvironmentVariable {
|
|
secretMapByName := make(map[string]models.SingleEnvironmentVariable)
|
|
|
|
for _, secret := range secrets {
|
|
secretMapByName[secret.Key] = secret
|
|
}
|
|
|
|
return secretMapByName
|
|
}
|