Compare commits

...

3 Commits

3 changed files with 126 additions and 4 deletions

View File

@@ -143,7 +143,15 @@ var secretsSetCmd = &cobra.Command{
Short: "Used set secrets", Short: "Used set secrets",
Use: "set [secrets]", Use: "set [secrets]",
DisableFlagsInUseLine: true, DisableFlagsInUseLine: true,
Args: cobra.MinimumNArgs(1), Args: func(cmd *cobra.Command, args []string) error {
if cmd.Flags().Changed("file") {
if len(args) > 0 {
return fmt.Errorf("secrets cannot be provided as command-line arguments when the --file option is used. Please choose either file-based or argument-based secret input")
}
return nil
}
return cobra.MinimumNArgs(1)(cmd, args)
},
Run: func(cmd *cobra.Command, args []string) { Run: func(cmd *cobra.Command, args []string) {
token, err := util.GetInfisicalToken(cmd) token, err := util.GetInfisicalToken(cmd)
if err != nil { if err != nil {
@@ -177,13 +185,18 @@ var secretsSetCmd = &cobra.Command{
util.HandleError(err, "Unable to parse secret type") util.HandleError(err, "Unable to parse secret type")
} }
file, err := cmd.Flags().GetString("file")
if err != nil {
util.HandleError(err, "Unable to parse flag")
}
var secretOperations []models.SecretSetOperation var secretOperations []models.SecretSetOperation
if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) { if token != nil && (token.Type == util.SERVICE_TOKEN_IDENTIFIER || token.Type == util.UNIVERSAL_AUTH_TOKEN_IDENTIFIER) {
if projectId == "" { if projectId == "" {
util.PrintErrorMessageAndExit("When using service tokens or machine identities, you must set the --projectId flag") util.PrintErrorMessageAndExit("When using service tokens or machine identities, you must set the --projectId flag")
} }
secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, token) secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, token, file)
} else { } else {
if projectId == "" { if projectId == "" {
workspaceFile, err := util.GetWorkSpaceFromFile() workspaceFile, err := util.GetWorkSpaceFromFile()
@@ -206,7 +219,7 @@ var secretsSetCmd = &cobra.Command{
secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, &models.TokenDetails{ secretOperations, err = util.SetRawSecrets(args, secretType, environmentName, secretsPath, projectId, &models.TokenDetails{
Type: "", Type: "",
Token: loggedInUserDetails.UserCredentials.JTWToken, Token: loggedInUserDetails.UserCredentials.JTWToken,
}) }, file)
} }
if err != nil { if err != nil {
@@ -691,6 +704,7 @@ func init() {
secretsSetCmd.Flags().String("projectId", "", "manually set the project ID to for setting secrets when using machine identity based auth") 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")
secretsSetCmd.Flags().String("file", "", "Load secrets from the specified file. File format: .env or YAML (comments: # or //). This option is mutually exclusive with command-line secrets arguments.")
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("token", "", "Fetch secrets using service token or machine identity access token")

View File

@@ -17,6 +17,7 @@ import (
"github.com/go-resty/resty/v2" "github.com/go-resty/resty/v2"
"github.com/rs/zerolog/log" "github.com/rs/zerolog/log"
"github.com/zalando/go-keyring" "github.com/zalando/go-keyring"
"gopkg.in/yaml.v3"
) )
func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool, tagSlugs string, expandSecretReferences bool) ([]models.SingleEnvironmentVariable, error) { func GetPlainTextSecretsViaServiceToken(fullServiceToken string, environment string, secretPath string, includeImports bool, recursive bool, tagSlugs string, expandSecretReferences bool) ([]models.SingleEnvironmentVariable, error) {
@@ -563,7 +564,99 @@ func GetPlainTextWorkspaceKey(authenticationToken string, receiverPrivateKey str
return crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey), nil return crypto.DecryptAsymmetric(encryptedWorkspaceKey, encryptedWorkspaceKeyNonce, encryptedWorkspaceKeySenderPublicKey, currentUsersPrivateKey), nil
} }
func SetRawSecrets(secretArgs []string, secretType string, environmentName string, secretsPath string, projectId string, tokenDetails *models.TokenDetails) ([]models.SecretSetOperation, error) { func parseSecrets(fileName string, content string) (map[string]string, error) {
secrets := make(map[string]string)
if strings.HasSuffix(fileName, ".yaml") || strings.HasSuffix(fileName, ".yml") {
// Handle YAML secrets
var yamlData map[string]interface{}
if err := yaml.Unmarshal([]byte(content), &yamlData); err != nil {
return nil, fmt.Errorf("failed to parse YAML file: %v", err)
}
for key, value := range yamlData {
if strValue, ok := value.(string); ok {
secrets[key] = strValue
} else {
return nil, fmt.Errorf("YAML secret '%s' must be a string", key)
}
}
} else {
// Handle .env files
lines := strings.Split(content, "\n")
for _, line := range lines {
line = strings.TrimSpace(line)
// Ignore empty lines and comments
if line == "" || strings.HasPrefix(line, "#") || strings.HasPrefix(line, "//") {
continue
}
// Ensure it's a valid key=value pair
splitKeyValue := strings.SplitN(line, "=", 2)
if len(splitKeyValue) != 2 {
return nil, fmt.Errorf("invalid format, expected key=value in line: %s", line)
}
key, value := strings.TrimSpace(splitKeyValue[0]), strings.TrimSpace(splitKeyValue[1])
// Handle quoted values
if (strings.HasPrefix(value, `"`) && strings.HasSuffix(value, `"`)) ||
(strings.HasPrefix(value, `'`) && strings.HasSuffix(value, `'`)) {
value = value[1 : len(value)-1] // Remove surrounding quotes
}
secrets[key] = value
}
}
return secrets, nil
}
func validateSecretKey(key string) error {
if key == "" {
return errors.New("secret keys cannot be empty")
}
if unicode.IsNumber(rune(key[0])) {
return fmt.Errorf("secret key '%s' cannot start with a number", key)
}
if strings.Contains(key, " ") {
return fmt.Errorf("secret key '%s' cannot contain spaces", key)
}
return nil
}
func SetRawSecrets(secretArgs []string, secretType string, environmentName string, secretsPath string, projectId string, tokenDetails *models.TokenDetails, file string) ([]models.SecretSetOperation, error) {
if file != "" {
content, err := os.ReadFile(file)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
PrintErrorMessageAndExit("File does not exist")
}
return nil, fmt.Errorf("unable to process file [err=%v]", err)
}
parsedSecrets, err := parseSecrets(file, string(content))
if err != nil {
PrintErrorMessageAndExit(fmt.Sprintf("error parsing secrets: %v", err))
}
// Step 2: Validate secrets
for key, value := range parsedSecrets {
if err := validateSecretKey(key); err != nil {
PrintErrorMessageAndExit(err.Error())
}
if strings.TrimSpace(value) == "" {
PrintErrorMessageAndExit(fmt.Sprintf("Secret key '%s' has an empty value", key))
}
secretArgs = append(secretArgs, fmt.Sprintf("%s=%s", key, value))
}
if len(secretArgs) == 0 {
PrintErrorMessageAndExit("no valid secrets found in the file")
}
}
if tokenDetails == nil { if tokenDetails == nil {
return nil, fmt.Errorf("unable to process set secret operations, token details are missing") return nil, fmt.Errorf("unable to process set secret operations, token details are missing")

View File

@@ -219,6 +219,21 @@ $ infisical secrets set STRIPE_API_KEY=sjdgwkeudyjwe DOMAIN=example.com HASH=jeb
``` ```
</Accordion> </Accordion>
<Accordion title="--file">
Used to set secrets from a file, supporting both `.env` and `YAML` formats. The file path can be either absolute or relative to the current working directory.
The file should contain secrets in the following formats:
- `key=value` for `.env` files
- `key: value` for YAML files
Comments can be written using `# comment` or `// comment`. Empty lines will be ignored during processing.
```bash
# Example
infisical secrets set --file="./.env"
```
</Accordion>
</Accordion> </Accordion>
<Accordion title="infisical secrets delete"> <Accordion title="infisical secrets delete">