Skip to content

Commit

Permalink
generate example .env file command
Browse files Browse the repository at this point in the history
  • Loading branch information
maidul98 committed Feb 8, 2023
1 parent c84add0 commit b0c541f
Show file tree
Hide file tree
Showing 6 changed files with 256 additions and 22 deletions.
39 changes: 24 additions & 15 deletions cli/packages/api/model.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
}

Expand Down
183 changes: 182 additions & 1 deletion cli/packages/cmd/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ package cmd
import (
"encoding/base64"
"fmt"
"regexp"
"sort"
"strings"
"unicode"

Expand All @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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(`(.*)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)

Expand All @@ -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)

Expand Down
11 changes: 10 additions & 1 deletion cli/packages/models/cli.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package models

import "github.com/99designs/keyring"
import (
"github.com/99designs/keyring"
)

type UserCredentials struct {
Email string `json:"email"`
Expand All @@ -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 {
Expand Down
12 changes: 12 additions & 0 deletions cli/packages/util/helper.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package util

import (
"crypto/sha256"
"encoding/base64"
"fmt"
"os"
Expand Down Expand Up @@ -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)
}
2 changes: 1 addition & 1 deletion cli/packages/util/log.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
31 changes: 27 additions & 4 deletions cli/packages/util/secrets.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down

0 comments on commit b0c541f

Please sign in to comment.