diff --git a/.envrc b/.envrc new file mode 100644 index 000000000..cffc922b0 --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +use flake . --impure diff --git a/.gitignore b/.gitignore index 5eb4a860c..f4f5d665b 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ dist/ functional-tests/sops vendor/ profile.out +.direnv/ diff --git a/cmd/sops/main.go b/cmd/sops/main.go index 6b19f60b1..b2f1c3a00 100644 --- a/cmd/sops/main.go +++ b/cmd/sops/main.go @@ -29,6 +29,7 @@ import ( "github.com/getsops/sops/v3/cmd/sops/subcommand/exec" filestatuscmd "github.com/getsops/sops/v3/cmd/sops/subcommand/filestatus" "github.com/getsops/sops/v3/cmd/sops/subcommand/groups" + initcmd "github.com/getsops/sops/v3/cmd/sops/subcommand/init" keyservicecmd "github.com/getsops/sops/v3/cmd/sops/subcommand/keyservice" publishcmd "github.com/getsops/sops/v3/cmd/sops/subcommand/publish" "github.com/getsops/sops/v3/cmd/sops/subcommand/updatekeys" @@ -161,6 +162,37 @@ func main() { For more information, see the README at https://github.com/getsops/sops` app.EnableBashCompletion = true app.Commands = []cli.Command{ + { + Name: "init", + Usage: "generate .sops.yaml config", + Flags: append([]cli.Flag{ + cli.StringFlag{ + Name: "dir, d", + Usage: "directory path to save .sops.yaml", + }, + cli.BoolFlag{ + Name: "verbose, v", + Usage: "enable verbose outputs", + }, + }), + Action: func(c *cli.Context) error { + configFilePath, err := os.Getwd() + if err != nil { + return common.NewExitError(fmt.Errorf("error: err"), codes.ErrorGeneric) + } + + if c.String("dir") != "" { + configFilePath = c.String("dir") + } + + initCommandArgs := initcmd.InitCommandArgs{ + ConfigFilePath: configFilePath, + IsVerbose: c.Bool("verbose"), + } + + return initcmd.Init(initCommandArgs) + }, + }, { Name: "completion", Usage: "Generate shell completion scripts", diff --git a/cmd/sops/subcommand/init/init.go b/cmd/sops/subcommand/init/init.go new file mode 100644 index 000000000..9d5916896 --- /dev/null +++ b/cmd/sops/subcommand/init/init.go @@ -0,0 +1,131 @@ +package init + +import ( + "errors" + "fmt" + "os" + "path/filepath" + + "github.com/getsops/sops/v3/cmd/sops/codes" + "github.com/getsops/sops/v3/cmd/sops/common" + "go.yaml.in/yaml/v3" +) + +type CreationRule struct { + PathRegex string `yaml:"path_regex"` + Age []string `yaml:"age"` + PGP []string `yaml:"pgp"` + AWSKMS []string `yaml:"kms"` + GCPKMS []string `yaml:"gcp_kms"` + AzureKeyVault []string `yaml:"azure_keyvault"` + HuaweiCloud []string `yaml:"hckms"` + HashicorpVault []string `yaml:"hc_vault_transit_uri"` +} + +type ConfigFile struct { + CreationRules []CreationRule +} + +type InitCommandArgs struct { + ConfigFilePath string + IsVerbose bool + CreationRule CreationRule +} + +func Init(args InitCommandArgs) error { + + fileInfo, err := os.Stat(args.ConfigFilePath) + if errors.Is(err, os.ErrNotExist) { + return common.NewExitError( + fmt.Errorf("%s does not exist, please ensure this is an existing directory", args.ConfigFilePath), + codes.CouldNotReadInputFile, + ) + } + + if !fileInfo.IsDir() { + return common.NewExitError( + fmt.Errorf("argument is not a directory, please provide a directory path"), + codes.ErrorGeneric, + ) + } + + if err != nil { + return common.NewExitError( + fmt.Errorf("failed to retrieve file stats: %e", err), + codes.CouldNotReadInputFile, + ) + } + + args.ConfigFilePath = filepath.Join(args.ConfigFilePath, ".sops.yaml") + + _, err = os.Stat(args.ConfigFilePath) + if !errors.Is(err, os.ErrNotExist) { + return common.NewExitError( + fmt.Errorf("%s already exists", args.ConfigFilePath), + codes.ErrorGeneric, + ) + } + + if args.IsVerbose { + fmt.Printf("generating .sops.yaml -> %s\n", args.ConfigFilePath) + } + + defaultConfigFile := ConfigFile{ + CreationRules: []CreationRule{ + CreationRule{ + PathRegex: "secrets/[^/]+\\.(yaml|json|env|ini)$", + PGP: []string{ + "2504791468b153b8a3963cc97ba53d1919c5dfd4!", + "CHANGE-ME", + }, + Age: []string{ + "age12zlz6lvcdk6eqaewfylg35w0syh58sm7gh53q5vvn7hd7c6nngyseftjxl", + "CHANGE-ME", + }, + }, + }, + } + + result, err := yaml.Marshal(&defaultConfigFile) + if err != nil { + return common.NewExitError( + fmt.Errorf("error: %e", err), + codes.ErrorReadingConfig, + ) + } + + var mutatedResult = fmt.Sprintf( + "# Example .sops.yaml\n# Please update the values according to your setup\n# https://getsops.io/docs/\n\n%s\n", + string(result), + ) + + file, err := os.Create(args.ConfigFilePath) + if err != nil { + return common.NewExitError( + fmt.Errorf("failed to open file: %e", err), + codes.ErrorGeneric, + ) + } + + _, err = file.Write([]byte(mutatedResult)) + if err != nil { + return common.NewExitError( + fmt.Errorf("failed to write to file: %e", err), + codes.CouldNotWriteOutputFile, + ) + } + + if err = file.Close(); err != nil { + return common.NewExitError( + fmt.Errorf("failed to close file descriptor: %e", err), + codes.ErrorGeneric, + ) + } + + if args.IsVerbose { + fmt.Printf("generated yaml\noutput:\n%s\n", mutatedResult) + } + + return nil + +} diff --git a/flake.lock b/flake.lock new file mode 100644 index 000000000..2151f5da8 --- /dev/null +++ b/flake.lock @@ -0,0 +1,61 @@ +{ + "nodes": { + "flake-utils": { + "inputs": { + "systems": "systems" + }, + "locked": { + "lastModified": 1731533236, + "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", + "owner": "numtide", + "repo": "flake-utils", + "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", + "type": "github" + }, + "original": { + "owner": "numtide", + "repo": "flake-utils", + "type": "github" + } + }, + "nixpkgs": { + "locked": { + "lastModified": 1781141223, + "narHash": "sha256-Eye4UQJjC4TLobclolFCMl6MrjgiF6Bk1cOI5x8SH00=", + "owner": "nixos", + "repo": "nixpkgs", + "rev": "b503dde361500433ca25a32e8f4d218bf58fb659", + "type": "github" + }, + "original": { + "owner": "nixos", + "ref": "nixpkgs-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, + "root": { + "inputs": { + "flake-utils": "flake-utils", + "nixpkgs": "nixpkgs" + } + }, + "systems": { + "locked": { + "lastModified": 1681028828, + "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", + "owner": "nix-systems", + "repo": "default", + "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", + "type": "github" + }, + "original": { + "owner": "nix-systems", + "repo": "default", + "type": "github" + } + } + }, + "root": "root", + "version": 7 +} diff --git a/flake.nix b/flake.nix new file mode 100644 index 000000000..61feb8c24 --- /dev/null +++ b/flake.nix @@ -0,0 +1,73 @@ +{ + description = "sops development"; + + inputs = { + flake-utils = { + url = "github:numtide/flake-utils"; + }; + + nixpkgs = { + url = "github:nixos/nixpkgs/nixpkgs-unstable"; + }; + }; + + outputs = + { + self, + nixpkgs, + flake-utils, + }: + flake-utils.lib.eachDefaultSystem ( + system: + let + pkgs = import nixpkgs { + inherit system; + + config = { + allowUnfree = true; + }; + }; + in + { + devShells = { + default = pkgs.mkShell { + # https://nixos.wiki/wiki/Go#Using_cgo_on_NixOS + hardeningDisable = [ "fortify" ]; + + packages = with pkgs; [ + delve + go + ]; + + shellHook = '' + CGO_ENABLED = 0; + ''; + }; + }; + + packages = { + default = pkgs.buildGoModule { + pname = "sops"; + version = "3.13.1"; + + src = ./.; + + subPackages = [ "cmd/sops" ]; + + vendorHash = "sha256-b94pcUopemj+kXj2AacZTQ0BYaTMqXAgHUEVz6x3+Lg="; + + meta = { + mainProgram = "sops"; + }; + }; + }; + + apps = { + default = { + type = "app"; + program = "${self.packages.${system}.default}/bin/sops"; + }; + }; + } + ); +} \ No newline at end of file