-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #55 from aris-bunnyshell/feat/secrets
feat: implement Bunnyshell secrets
- Loading branch information
Showing
14 changed files
with
624 additions
and
14 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package secret | ||
|
||
import ( | ||
"errors" | ||
"io" | ||
"os" | ||
|
||
"bunnyshell.com/cli/pkg/api/secret" | ||
"bunnyshell.com/cli/pkg/build" | ||
"bunnyshell.com/cli/pkg/config" | ||
"bunnyshell.com/cli/pkg/lib" | ||
"github.com/MakeNowJust/heredoc" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var ( | ||
errMissingEncryptedExpression = errors.New("the encrypted expression must be provided") | ||
errMultipleEncryptedExpressionInputs = errors.New("the encrypted expression must be provided either by argument or by stdin, not both") | ||
) | ||
|
||
var decryptCommandExample = heredoc.Docf(` | ||
%[1]s%[2]s secret decrypt --organization dMVwZO5jGN "ENCRYPTED[F67baQQZ6XXHNxcmRz]" | ||
%[1]scat encrypted.txt | %[2]s secret decrypt --organization dMVwZO5jGN | ||
`, "\t", build.Name) | ||
|
||
func init() { | ||
settings := config.GetSettings() | ||
|
||
decryptOptions := secret.NewDecryptOptions() | ||
|
||
command := &cobra.Command{ | ||
Use: "decrypt <expression>", | ||
|
||
Short: "Decrypts a secret expression of the given organization", | ||
Example: decryptCommandExample, | ||
|
||
Args: cobra.MaximumNArgs(1), | ||
ArgAliases: []string{"expression"}, | ||
ValidArgsFunction: cobra.NoFileCompletions, | ||
|
||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
hasStdin, err := isStdinPresent() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if len(args) == 0 && !hasStdin { | ||
return errMissingEncryptedExpression | ||
} | ||
|
||
if len(args) == 1 && hasStdin { | ||
return errMultipleEncryptedExpressionInputs | ||
} | ||
|
||
return nil | ||
}, | ||
|
||
RunE: func(cmd *cobra.Command, args []string) error { | ||
if decryptOptions.Organization == "" { | ||
decryptOptions.Organization = settings.Profile.Context.Organization | ||
} | ||
|
||
if len(args) == 1 { | ||
decryptOptions.Expression = args[0] | ||
} else { | ||
buf, err := io.ReadAll(os.Stdin) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
decryptOptions.Expression = string(buf) | ||
} | ||
|
||
decryptedSecret, err := secret.Decrypt(decryptOptions) | ||
if err != nil { | ||
return lib.FormatCommandError(cmd, err) | ||
} | ||
|
||
return lib.FormatCommandData(cmd, decryptedSecret) | ||
}, | ||
} | ||
|
||
decryptOptions.UpdateSelfFlags(command.Flags()) | ||
|
||
mainCmd.AddCommand(command) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package secret | ||
|
||
import ( | ||
"bunnyshell.com/cli/pkg/api/secret" | ||
"bunnyshell.com/cli/pkg/build" | ||
"bunnyshell.com/cli/pkg/lib" | ||
"github.com/MakeNowJust/heredoc" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var decryptDefinitionCommandExample = heredoc.Docf(` | ||
%[1]s%[2]s secret decrypt-definition --organization dMVwZO5jGN --file plain.txt | ||
%[1]scat plain.txt | %[2]s secret decrypt-definition --organization dMVwZO5jGN | ||
`, "\t", build.Name) | ||
|
||
var resolvedExpressions bool | ||
|
||
func init() { | ||
transcriptConfigurationOptions := secret.NewTranscriptConfigurationOptions() | ||
|
||
command := &cobra.Command{ | ||
Use: "decrypt-definition", | ||
|
||
Short: "Decrypts an environment definition for the given organization", | ||
Example: decryptDefinitionCommandExample, | ||
|
||
ValidArgsFunction: cobra.NoFileCompletions, | ||
|
||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
return validateDefinitionCommand(transcriptConfigurationOptions) | ||
}, | ||
|
||
RunE: func(cmd *cobra.Command, args []string) error { | ||
mode := secret.TranscriptModeExposed | ||
if resolvedExpressions { | ||
mode = secret.TranscriptModeResolved | ||
} | ||
|
||
decryptedDefinition, err := executeTranscriptConfiguration(transcriptConfigurationOptions, mode) | ||
if err != nil { | ||
return lib.FormatCommandError(cmd, err) | ||
} | ||
|
||
cmd.Println(decryptedDefinition) | ||
|
||
return nil | ||
}, | ||
} | ||
|
||
flags := command.Flags() | ||
|
||
transcriptConfigurationOptions.UpdateSelfFlags(flags) | ||
flags.BoolVar(&resolvedExpressions, "resolved", false, "Resolve the expressions and include directly the value") | ||
|
||
mainCmd.AddCommand(command) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,108 @@ | ||
package secret | ||
|
||
import ( | ||
"errors" | ||
"io" | ||
"os" | ||
|
||
"bunnyshell.com/cli/pkg/api/secret" | ||
"bunnyshell.com/cli/pkg/config" | ||
"bunnyshell.com/cli/pkg/util" | ||
) | ||
|
||
var ( | ||
errYamlBlankValue = errors.New("the environment definition provided is blank") | ||
errYamlMissingValue = errors.New("the environment definition must be provided") | ||
errYamlFileNotFound = errors.New("the provided filepath does not exist") | ||
errMultipleYamlValueInputs = errors.New("the environment definition must be provided either by argument or by stdin, not both") | ||
) | ||
|
||
func executeTranscriptConfiguration(options *secret.TranscriptConfigurationOptions, mode secret.TranscriptMode) (string, error) { | ||
settings := config.GetSettings() | ||
|
||
options.Mode = string(mode) | ||
if options.Organization == "" { | ||
options.Organization = settings.Profile.Context.Organization | ||
} | ||
|
||
err := loadDefinition(options) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if options.Yaml == "" { | ||
return "", errYamlBlankValue | ||
} | ||
|
||
resultedDefinition, err := secret.TranscriptConfiguration(options) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return string(resultedDefinition.Bytes), nil | ||
} | ||
|
||
func validateDefinitionCommand(options *secret.TranscriptConfigurationOptions) error { | ||
hasStdin, err := isStdinPresent() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if options.DefinitionFilePath == "" && !hasStdin { | ||
return errYamlMissingValue | ||
} | ||
|
||
if options.DefinitionFilePath != "" { | ||
if hasStdin { | ||
return errMultipleYamlValueInputs | ||
} | ||
|
||
exists, err := util.FileExists(options.DefinitionFilePath) | ||
if err != nil { | ||
return err | ||
} | ||
if !exists { | ||
return errYamlFileNotFound | ||
} | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func loadDefinition(options *secret.TranscriptConfigurationOptions) error { | ||
if options.DefinitionFilePath != "" { | ||
contents, err := loadDefinitionFromFile(options.DefinitionFilePath) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
options.Yaml = contents | ||
} else { | ||
buf, err := io.ReadAll(os.Stdin) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
options.Yaml = string(buf) | ||
} | ||
|
||
return nil | ||
} | ||
|
||
func loadDefinitionFromFile(filepath string) (string, error) { | ||
exists, err := util.FileExists(filepath) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
if !exists { | ||
return "", errors.New("the provided file does not exist") | ||
} | ||
|
||
fileContents, err := os.ReadFile(filepath) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
return string(fileContents), nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package secret | ||
|
||
import ( | ||
"errors" | ||
"io" | ||
"os" | ||
|
||
"bunnyshell.com/cli/pkg/api/secret" | ||
"bunnyshell.com/cli/pkg/build" | ||
"bunnyshell.com/cli/pkg/config" | ||
"bunnyshell.com/cli/pkg/lib" | ||
"github.com/MakeNowJust/heredoc" | ||
"github.com/spf13/cobra" | ||
) | ||
|
||
var ( | ||
errMissingValue = errors.New("the plain value must be provided") | ||
errMultipleValueInputs = errors.New("the value must be provided either by argument or by stdin, not both") | ||
) | ||
|
||
var encryptCommandExample = heredoc.Docf(` | ||
%[1]s%[2]s secret encrypt --organization dMVwZO5jGN "my plain secret value" | ||
%[1]scat plain.txt | %[2]s secret encrypt --organization dMVwZO5jGN | ||
`, "\t", build.Name) | ||
|
||
func init() { | ||
settings := config.GetSettings() | ||
|
||
encryptOptions := secret.NewEncryptOptions() | ||
|
||
command := &cobra.Command{ | ||
Use: "encrypt <value>", | ||
|
||
Short: "Encrypts a secret for the given organization", | ||
Example: encryptCommandExample, | ||
|
||
Args: cobra.MaximumNArgs(1), | ||
ArgAliases: []string{"value"}, | ||
ValidArgsFunction: cobra.NoFileCompletions, | ||
|
||
PreRunE: func(cmd *cobra.Command, args []string) error { | ||
hasStdin, err := isStdinPresent() | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if len(args) == 0 && !hasStdin { | ||
return errMissingValue | ||
} | ||
|
||
if len(args) == 1 && hasStdin { | ||
return errMultipleValueInputs | ||
} | ||
|
||
return nil | ||
}, | ||
|
||
RunE: func(cmd *cobra.Command, args []string) error { | ||
if encryptOptions.Organization == "" { | ||
encryptOptions.Organization = settings.Profile.Context.Organization | ||
} | ||
|
||
if len(args) == 1 { | ||
encryptOptions.PlainText = args[0] | ||
} else { | ||
buf, err := io.ReadAll(os.Stdin) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
encryptOptions.PlainText = string(buf) | ||
} | ||
|
||
encryptedSecret, err := secret.Encrypt(encryptOptions) | ||
if err != nil { | ||
return lib.FormatCommandError(cmd, err) | ||
} | ||
|
||
return lib.FormatCommandData(cmd, encryptedSecret) | ||
}, | ||
} | ||
|
||
encryptOptions.UpdateSelfFlags(command.Flags()) | ||
|
||
mainCmd.AddCommand(command) | ||
} |
Oops, something went wrong.