Merge pull request #317 from Infisical/gen-example-env-command

generate example .env file command
This commit is contained in:
Maidul Islam
2023-02-08 18:51:23 -08:00
committed by GitHub
7 changed files with 261 additions and 22 deletions

View File

@@ -201,21 +201,30 @@ type GetEncryptedSecretsV2Request struct {
type GetEncryptedSecretsV2Response struct {
Secrets []struct {
ID string `json:"_id"`
Version int `json:"version"`
Workspace string `json:"workspace"`
Type string `json:"type"`
Environment string `json:"environment"`
SecretKeyCiphertext string `json:"secretKeyCiphertext"`
SecretKeyIV string `json:"secretKeyIV"`
SecretKeyTag string `json:"secretKeyTag"`
SecretValueCiphertext string `json:"secretValueCiphertext"`
SecretValueIV string `json:"secretValueIV"`
SecretValueTag string `json:"secretValueTag"`
V int `json:"__v"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
User string `json:"user,omitempty"`
ID string `json:"_id"`
Version int `json:"version"`
Workspace string `json:"workspace"`
Type string `json:"type"`
Environment string `json:"environment"`
SecretKeyCiphertext string `json:"secretKeyCiphertext"`
SecretKeyIV string `json:"secretKeyIV"`
SecretKeyTag string `json:"secretKeyTag"`
SecretValueCiphertext string `json:"secretValueCiphertext"`
SecretValueIV string `json:"secretValueIV"`
SecretValueTag string `json:"secretValueTag"`
SecretCommentCiphertext string `json:"secretCommentCiphertext"`
SecretCommentIV string `json:"secretCommentIV"`
SecretCommentTag string `json:"secretCommentTag"`
V int `json:"__v"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
User string `json:"user,omitempty"`
Tags []struct {
ID string `json:"_id"`
Name string `json:"name"`
Slug string `json:"slug"`
Workspace string `json:"workspace"`
} `json:"tags"`
} `json:"secrets"`
}

View File

@@ -6,6 +6,8 @@ package cmd
import (
"encoding/base64"
"fmt"
"regexp"
"sort"
"strings"
"unicode"
@@ -22,7 +24,7 @@ import (
)
var secretsCmd = &cobra.Command{
Example: `infisical secrets"`,
Example: `infisical secrets`,
Short: "Used to create, read update and delete secrets",
Use: "secrets",
DisableFlagsInUseLine: true,
@@ -67,6 +69,16 @@ var secretsGetCmd = &cobra.Command{
Run: getSecretsByNames,
}
var secretsGenerateExampleEnvCmd = &cobra.Command{
Example: `secrets generate-example-env > .example-env`,
Short: "Used to generate a example .env file",
Use: "generate-example-env",
DisableFlagsInUseLine: true,
Args: cobra.NoArgs,
PreRun: toggleDebug,
Run: generateExampleEnv,
}
var secretsSetCmd = &cobra.Command{
Example: `secrets set <secretName=secretValue> <secretName=secretValue>..."`,
Short: "Used set secrets",
@@ -357,6 +369,171 @@ func getSecretsByNames(cmd *cobra.Command, args []string) {
visualize.PrintAllSecretDetails(requestedSecrets)
}
func generateExampleEnv(cmd *cobra.Command, args []string) {
environmentName, err := cmd.Flags().GetString("env")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
workspaceFileExists := util.WorkspaceConfigFileExistsInCurrentPath()
if !workspaceFileExists {
util.HandleError(err, "Unable to parse flag")
}
infisicalToken, err := cmd.Flags().GetString("token")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
secrets, err := util.GetAllEnvironmentVariables(models.GetAllSecretsParameters{Environment: environmentName, InfisicalToken: infisicalToken})
if err != nil {
util.HandleError(err, "To fetch all secrets")
}
tagsHashToSecretKey := make(map[string]int)
type TagsAndSecrets struct {
Secrets []models.SingleEnvironmentVariable
Tags []struct {
ID string `json:"_id"`
Name string `json:"name"`
Slug string `json:"slug"`
Workspace string `json:"workspace"`
}
}
// sort secrets by associated tags (most number of tags to least tags)
sort.Slice(secrets, func(i, j int) bool {
return len(secrets[i].Tags) > len(secrets[j].Tags)
})
for _, secret := range secrets {
listOfTagSlugs := []string{}
for _, tag := range secret.Tags {
listOfTagSlugs = append(listOfTagSlugs, tag.Slug)
}
sort.Strings(listOfTagSlugs)
tagsHash := util.GetHashFromStringList(listOfTagSlugs)
tagsHashToSecretKey[tagsHash] += 1
}
finalTagHashToSecretKey := make(map[string]TagsAndSecrets)
for _, secret := range secrets {
listOfTagSlugs := []string{}
for _, tag := range secret.Tags {
listOfTagSlugs = append(listOfTagSlugs, tag.Slug)
}
// sort the slug so we get the same hash each time
sort.Strings(listOfTagSlugs)
tagsHash := util.GetHashFromStringList(listOfTagSlugs)
occurrence, exists := tagsHashToSecretKey[tagsHash]
if exists && occurrence > 0 {
value, exists2 := finalTagHashToSecretKey[tagsHash]
allSecretsForTags := append(value.Secrets, secret)
// sort the the secrets by keys so that they can later be sorted by the first item in the secrets array
sort.Slice(allSecretsForTags, func(i, j int) bool {
return allSecretsForTags[i].Key < allSecretsForTags[j].Key
})
if exists2 {
finalTagHashToSecretKey[tagsHash] = TagsAndSecrets{
Tags: secret.Tags,
Secrets: allSecretsForTags,
}
} else {
finalTagHashToSecretKey[tagsHash] = TagsAndSecrets{
Tags: secret.Tags,
Secrets: []models.SingleEnvironmentVariable{secret},
}
}
tagsHashToSecretKey[tagsHash] -= 1
}
}
// sort the fianl result by secret key fo consistent print order
listOfsecretDetails := make([]TagsAndSecrets, 0, len(finalTagHashToSecretKey))
for _, secretDetails := range finalTagHashToSecretKey {
listOfsecretDetails = append(listOfsecretDetails, secretDetails)
}
// sort the order of the headings by the order of the secrets
sort.Slice(listOfsecretDetails, func(i, j int) bool {
return len(listOfsecretDetails[i].Tags) < len(listOfsecretDetails[j].Tags)
})
for _, secretDetails := range listOfsecretDetails {
listOfKeyValue := []string{}
for _, secret := range secretDetails.Secrets {
re := regexp.MustCompile(`(?s)(.*)DEFAULT:(.*)`)
match := re.FindStringSubmatch(secret.Comment)
defaultValue := ""
comment := secret.Comment
// Case: Only has default value
if len(match) == 2 {
defaultValue = strings.TrimSpace(match[1])
}
// Case: has a comment and a default value
if len(match) == 3 {
comment = match[1]
defaultValue = match[2]
}
row := ""
if comment != "" {
comment = addHash(comment)
row = fmt.Sprintf("%s \n%s=%s", strings.TrimSpace(comment), strings.TrimSpace(secret.Key), strings.TrimSpace(defaultValue))
} else {
row = fmt.Sprintf("%s=%s", strings.TrimSpace(secret.Key), strings.TrimSpace(defaultValue))
}
// each secret row to be added to the file
listOfKeyValue = append(listOfKeyValue, row)
}
listOfTagNames := []string{}
for _, tag := range secretDetails.Tags {
listOfTagNames = append(listOfTagNames, tag.Name)
}
heading := CenterString(strings.Join(listOfTagNames, " & "), 80)
if len(listOfTagNames) == 0 {
fmt.Printf("\n%s \n", strings.Join(listOfKeyValue, "\n \n"))
} else {
fmt.Printf("\n\n\n%s\n \n%s \n", heading, strings.Join(listOfKeyValue, "\n \n"))
}
}
}
func CenterString(s string, numStars int) string {
stars := strings.Repeat("*", numStars)
padding := (numStars - len(s)) / 2
cenetredTextWithStar := stars[:padding] + " " + strings.ToUpper(s) + " " + stars[padding:]
hashes := strings.Repeat("#", len(cenetredTextWithStar)+2)
return fmt.Sprintf("%s \n# %s \n%s", hashes, cenetredTextWithStar, hashes)
}
func addHash(input string) string {
lines := strings.Split(input, "\n")
for i, line := range lines {
lines[i] = "# " + line
}
return strings.Join(lines, "\n")
}
func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]models.SingleEnvironmentVariable {
secretMapByName := make(map[string]models.SingleEnvironmentVariable)
@@ -368,6 +545,10 @@ func getSecretsByKeys(secrets []models.SingleEnvironmentVariable) map[string]mod
}
func init() {
secretsGenerateExampleEnvCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
secretsCmd.AddCommand(secretsGenerateExampleEnvCmd)
secretsGetCmd.Flags().String("token", "", "Fetch secrets using the Infisical Token")
secretsCmd.AddCommand(secretsGetCmd)

View File

@@ -12,6 +12,11 @@ import (
// will decrypt cipher text to plain text using iv and tag
func DecryptSymmetric(key []byte, cipherText []byte, tag []byte, iv []byte) ([]byte, error) {
// Case: empty string
if len(cipherText) == 0 && len(tag) == 0 && len(iv) == 0 {
return []byte{}, nil
}
block, err := aes.NewCipher(key)
if err != nil {
return nil, err

View File

@@ -1,6 +1,8 @@
package models
import "github.com/99designs/keyring"
import (
"github.com/99designs/keyring"
)
type UserCredentials struct {
Email string `json:"email"`
@@ -19,6 +21,13 @@ type SingleEnvironmentVariable struct {
Value string `json:"value"`
Type string `json:"type"`
ID string `json:"_id"`
Tags []struct {
ID string `json:"_id"`
Name string `json:"name"`
Slug string `json:"slug"`
Workspace string `json:"workspace"`
} `json:"tags"`
Comment string `json:"comment"`
}
type Workspace struct {

View File

@@ -1,6 +1,7 @@
package util
import (
"crypto/sha256"
"encoding/base64"
"fmt"
"os"
@@ -98,3 +99,14 @@ func RequireLocalWorkspaceFile() {
PrintMessageAndExit("Your project id is missing in your local config file. Please add it or run again [infisical init]")
}
}
func GetHashFromStringList(list []string) string {
hash := sha256.New()
for _, item := range list {
hash.Write([]byte(item))
}
sum := sha256.Sum256(hash.Sum(nil))
return fmt.Sprintf("%x", sum)
}

View File

@@ -24,7 +24,7 @@ func PrintErrorAndExit(exitCode int, err error, messages ...string) {
}
func PrintWarning(message string) {
color.Yellow("Warning: %v", message)
color.New(color.FgYellow).Fprintf(os.Stderr, "Warning: %v \n", message)
}
func PrintMessageAndExit(messages ...string) {

View File

@@ -315,11 +315,34 @@ func GetPlainTextSecrets(key []byte, encryptedSecrets api.GetEncryptedSecretsV2R
return nil, fmt.Errorf("unable to symmetrically decrypt secret value")
}
// Decrypt comment
comment_iv, err := base64.StdEncoding.DecodeString(secret.SecretCommentIV)
if err != nil {
return nil, fmt.Errorf("unable to decode secret IV for secret value")
}
comment_tag, err := base64.StdEncoding.DecodeString(secret.SecretCommentTag)
if err != nil {
return nil, fmt.Errorf("unable to decode secret authentication tag for secret value")
}
comment_ciphertext, _ := base64.StdEncoding.DecodeString(secret.SecretCommentCiphertext)
if err != nil {
return nil, fmt.Errorf("unable to decode secret cipher text for secret key")
}
plainTextComment, err := crypto.DecryptSymmetric(key, comment_ciphertext, comment_tag, comment_iv)
if err != nil {
return nil, fmt.Errorf("unable to symmetrically decrypt secret comment")
}
plainTextSecret := models.SingleEnvironmentVariable{
Key: string(plainTextKey),
Value: string(plainTextValue),
Type: string(secret.Type),
ID: secret.ID,
Key: string(plainTextKey),
Value: string(plainTextValue),
Type: string(secret.Type),
ID: secret.ID,
Tags: secret.Tags,
Comment: string(plainTextComment),
}
plainTextSecrets = append(plainTextSecrets, plainTextSecret)