mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-06 21:27:10 +00:00
Merge pull request #3257 from Infisical/feat/addFileImportToSecretSetCLI
Add file option to secret set CLI command to retrieve secrets from .env or .yaml files
This commit is contained in:
@@ -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")
|
||||||
|
|||||||
@@ -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) {
|
||||||
@@ -564,7 +565,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")
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
Reference in New Issue
Block a user