Skip to content

Commit 11cf377

Browse files
committed
Add encrypt and decrypt subcommands.
Signed-off-by: Felix Fontein <[email protected]>
1 parent 721217a commit 11cf377

File tree

1 file changed

+305
-0
lines changed

1 file changed

+305
-0
lines changed

cmd/sops/main.go

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,311 @@ func main() {
568568
return nil
569569
},
570570
},
571+
{
572+
Name: "decrypt",
573+
Usage: "decrypt a file, and output the results to stdout",
574+
ArgsUsage: `file`,
575+
Flags: append([]cli.Flag{
576+
cli.BoolFlag{
577+
Name: "in-place, i",
578+
Usage: "write output back to the same file instead of stdout",
579+
},
580+
cli.StringFlag{
581+
Name: "extract",
582+
Usage: "extract a specific key or branch from the input document. Example: --extract '[\"somekey\"][0]'",
583+
},
584+
cli.StringFlag{
585+
Name: "output",
586+
Usage: "Save the output after decryption to the file specified",
587+
},
588+
cli.StringFlag{
589+
Name: "input-type",
590+
Usage: "currently json, yaml, dotenv and binary are supported. If not set, sops will use the file's extension to determine the type",
591+
},
592+
cli.StringFlag{
593+
Name: "output-type",
594+
Usage: "currently json, yaml, dotenv and binary are supported. If not set, sops will use the input file's extension to determine the output format",
595+
},
596+
cli.BoolFlag{
597+
Name: "ignore-mac",
598+
Usage: "ignore Message Authentication Code during decryption",
599+
},
600+
}, keyserviceFlags...),
601+
Action: func(c *cli.Context) error {
602+
if c.Bool("verbose") {
603+
logging.SetLevel(logrus.DebugLevel)
604+
}
605+
if c.NArg() < 1 {
606+
return common.NewExitError("Error: no file specified", codes.NoFileSpecified)
607+
}
608+
warnMoreThanOnePositionalArgument(c)
609+
if c.Bool("in-place") && c.String("output") != "" {
610+
return common.NewExitError("Error: cannot operate on both --output and --in-place", codes.ErrorConflictingParameters)
611+
}
612+
fileName, err := filepath.Abs(c.Args()[0])
613+
if err != nil {
614+
return toExitError(err)
615+
}
616+
if _, err := os.Stat(fileName); os.IsNotExist(err) {
617+
return common.NewExitError("Error: cannot operate on non-existent file", codes.NoFileSpecified)
618+
}
619+
620+
inputStore := inputStore(c, fileName)
621+
outputStore := outputStore(c, fileName)
622+
svcs := keyservices(c)
623+
624+
var extract []interface{}
625+
extract, err = parseTreePath(c.String("extract"))
626+
if err != nil {
627+
return common.NewExitError(fmt.Errorf("error parsing --extract path: %s", err), codes.InvalidTreePathFormat)
628+
}
629+
630+
output, err := decrypt(decryptOpts{
631+
OutputStore: outputStore,
632+
InputStore: inputStore,
633+
InputPath: fileName,
634+
Cipher: aes.NewCipher(),
635+
Extract: extract,
636+
KeyServices: svcs,
637+
IgnoreMAC: c.Bool("ignore-mac"),
638+
})
639+
if err != nil {
640+
return toExitError(err)
641+
}
642+
643+
// We open the file *after* the operations on the tree have been
644+
// executed to avoid truncating it when there's errors
645+
if c.Bool("in-place") {
646+
file, err := os.Create(fileName)
647+
if err != nil {
648+
return common.NewExitError(fmt.Sprintf("Could not open in-place file for writing: %s", err), codes.CouldNotWriteOutputFile)
649+
}
650+
defer file.Close()
651+
_, err = file.Write(output)
652+
if err != nil {
653+
return toExitError(err)
654+
}
655+
log.Info("File written successfully")
656+
return nil
657+
}
658+
659+
outputFile := os.Stdout
660+
if c.String("output") != "" {
661+
file, err := os.Create(c.String("output"))
662+
if err != nil {
663+
return common.NewExitError(fmt.Sprintf("Could not open output file for writing: %s", err), codes.CouldNotWriteOutputFile)
664+
}
665+
defer file.Close()
666+
outputFile = file
667+
}
668+
_, err = outputFile.Write(output)
669+
return toExitError(err)
670+
},
671+
},
672+
{
673+
Name: "encrypt",
674+
Usage: "encrypt a file, and output the results to stdout",
675+
ArgsUsage: `file`,
676+
Flags: append([]cli.Flag{
677+
cli.BoolFlag{
678+
Name: "in-place, i",
679+
Usage: "write output back to the same file instead of stdout",
680+
},
681+
cli.StringFlag{
682+
Name: "output",
683+
Usage: "Save the output after decryption to the file specified",
684+
},
685+
cli.StringFlag{
686+
Name: "kms, k",
687+
Usage: "comma separated list of KMS ARNs",
688+
EnvVar: "SOPS_KMS_ARN",
689+
},
690+
cli.StringFlag{
691+
Name: "aws-profile",
692+
Usage: "The AWS profile to use for requests to AWS",
693+
},
694+
cli.StringFlag{
695+
Name: "gcp-kms",
696+
Usage: "comma separated list of GCP KMS resource IDs",
697+
EnvVar: "SOPS_GCP_KMS_IDS",
698+
},
699+
cli.StringFlag{
700+
Name: "azure-kv",
701+
Usage: "comma separated list of Azure Key Vault URLs",
702+
EnvVar: "SOPS_AZURE_KEYVAULT_URLS",
703+
},
704+
cli.StringFlag{
705+
Name: "hc-vault-transit",
706+
Usage: "comma separated list of vault's key URI (e.g. 'https://vault.example.org:8200/v1/transit/keys/dev')",
707+
EnvVar: "SOPS_VAULT_URIS",
708+
},
709+
cli.StringFlag{
710+
Name: "pgp, p",
711+
Usage: "comma separated list of PGP fingerprints",
712+
EnvVar: "SOPS_PGP_FP",
713+
},
714+
cli.StringFlag{
715+
Name: "age, a",
716+
Usage: "comma separated list of age recipients",
717+
EnvVar: "SOPS_AGE_RECIPIENTS",
718+
},
719+
cli.StringFlag{
720+
Name: "input-type",
721+
Usage: "currently json, yaml, dotenv and binary are supported. If not set, sops will use the file's extension to determine the type",
722+
},
723+
cli.StringFlag{
724+
Name: "output-type",
725+
Usage: "currently json, yaml, dotenv and binary are supported. If not set, sops will use the input file's extension to determine the output format",
726+
},
727+
cli.StringFlag{
728+
Name: "unencrypted-suffix",
729+
Usage: "override the unencrypted key suffix.",
730+
},
731+
cli.StringFlag{
732+
Name: "encrypted-suffix",
733+
Usage: "override the encrypted key suffix. When empty, all keys will be encrypted, unless otherwise marked with unencrypted-suffix.",
734+
},
735+
cli.StringFlag{
736+
Name: "unencrypted-regex",
737+
Usage: "set the unencrypted key regex. When specified, only keys matching the regex will be left unencrypted.",
738+
},
739+
cli.StringFlag{
740+
Name: "encrypted-regex",
741+
Usage: "set the encrypted key regex. When specified, only keys matching the regex will be encrypted.",
742+
},
743+
cli.StringFlag{
744+
Name: "encryption-context",
745+
Usage: "comma separated list of KMS encryption context key:value pairs",
746+
},
747+
cli.IntFlag{
748+
Name: "shamir-secret-sharing-threshold",
749+
Usage: "the number of master keys required to retrieve the data key with shamir",
750+
},
751+
}, keyserviceFlags...),
752+
Action: func(c *cli.Context) error {
753+
if c.Bool("verbose") {
754+
logging.SetLevel(logrus.DebugLevel)
755+
}
756+
if c.NArg() < 1 {
757+
return common.NewExitError("Error: no file specified", codes.NoFileSpecified)
758+
}
759+
warnMoreThanOnePositionalArgument(c)
760+
if c.Bool("in-place") && c.String("output") != "" {
761+
return common.NewExitError("Error: cannot operate on both --output and --in-place", codes.ErrorConflictingParameters)
762+
}
763+
fileName, err := filepath.Abs(c.Args()[0])
764+
if err != nil {
765+
return toExitError(err)
766+
}
767+
if _, err := os.Stat(fileName); os.IsNotExist(err) {
768+
return common.NewExitError("Error: cannot operate on non-existent file", codes.NoFileSpecified)
769+
}
770+
771+
unencryptedSuffix := c.String("unencrypted-suffix")
772+
encryptedSuffix := c.String("encrypted-suffix")
773+
encryptedRegex := c.String("encrypted-regex")
774+
unencryptedRegex := c.String("unencrypted-regex")
775+
conf, err := loadConfig(c, fileName, nil)
776+
if err != nil {
777+
return toExitError(err)
778+
}
779+
if conf != nil {
780+
// command line options have precedence
781+
if unencryptedSuffix == "" {
782+
unencryptedSuffix = conf.UnencryptedSuffix
783+
}
784+
if encryptedSuffix == "" {
785+
encryptedSuffix = conf.EncryptedSuffix
786+
}
787+
if encryptedRegex == "" {
788+
encryptedRegex = conf.EncryptedRegex
789+
}
790+
if unencryptedRegex == "" {
791+
unencryptedRegex = conf.UnencryptedRegex
792+
}
793+
}
794+
795+
cryptRuleCount := 0
796+
if unencryptedSuffix != "" {
797+
cryptRuleCount++
798+
}
799+
if encryptedSuffix != "" {
800+
cryptRuleCount++
801+
}
802+
if encryptedRegex != "" {
803+
cryptRuleCount++
804+
}
805+
if unencryptedRegex != "" {
806+
cryptRuleCount++
807+
}
808+
809+
if cryptRuleCount > 1 {
810+
return common.NewExitError("Error: cannot use more than one of encrypted_suffix, unencrypted_suffix, encrypted_regex or unencrypted_regex in the same file", codes.ErrorConflictingParameters)
811+
}
812+
813+
// only supply the default UnencryptedSuffix when EncryptedSuffix and EncryptedRegex are not provided
814+
if cryptRuleCount == 0 {
815+
unencryptedSuffix = sops.DefaultUnencryptedSuffix
816+
}
817+
818+
inputStore := inputStore(c, fileName)
819+
outputStore := outputStore(c, fileName)
820+
svcs := keyservices(c)
821+
822+
groups, err := keyGroups(c, fileName)
823+
if err != nil {
824+
return toExitError(err)
825+
}
826+
threshold, err := shamirThreshold(c, fileName)
827+
if err != nil {
828+
return toExitError(err)
829+
}
830+
output, err := encrypt(encryptOpts{
831+
OutputStore: outputStore,
832+
InputStore: inputStore,
833+
InputPath: fileName,
834+
Cipher: aes.NewCipher(),
835+
UnencryptedSuffix: unencryptedSuffix,
836+
EncryptedSuffix: encryptedSuffix,
837+
UnencryptedRegex: unencryptedRegex,
838+
EncryptedRegex: encryptedRegex,
839+
KeyServices: svcs,
840+
KeyGroups: groups,
841+
GroupThreshold: threshold,
842+
})
843+
if err != nil {
844+
return toExitError(err)
845+
}
846+
847+
// We open the file *after* the operations on the tree have been
848+
// executed to avoid truncating it when there's errors
849+
if c.Bool("in-place") {
850+
file, err := os.Create(fileName)
851+
if err != nil {
852+
return common.NewExitError(fmt.Sprintf("Could not open in-place file for writing: %s", err), codes.CouldNotWriteOutputFile)
853+
}
854+
defer file.Close()
855+
_, err = file.Write(output)
856+
if err != nil {
857+
return toExitError(err)
858+
}
859+
log.Info("File written successfully")
860+
return nil
861+
}
862+
863+
outputFile := os.Stdout
864+
if c.String("output") != "" {
865+
file, err := os.Create(c.String("output"))
866+
if err != nil {
867+
return common.NewExitError(fmt.Sprintf("Could not open output file for writing: %s", err), codes.CouldNotWriteOutputFile)
868+
}
869+
defer file.Close()
870+
outputFile = file
871+
}
872+
_, err = outputFile.Write(output)
873+
return toExitError(err)
874+
},
875+
},
571876
}
572877
app.Flags = append([]cli.Flag{
573878
cli.BoolFlag{

0 commit comments

Comments
 (0)