mirror of
https://github.com/getsops/sops.git
synced 2026-02-05 12:45:21 +01:00
Merge pull request #545 from endorama/add-filestatus-command
add filestatus command
This commit is contained in:
@@ -26,6 +26,7 @@ import (
|
||||
"github.com/getsops/sops/v3/cmd/sops/codes"
|
||||
"github.com/getsops/sops/v3/cmd/sops/common"
|
||||
"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"
|
||||
keyservicecmd "github.com/getsops/sops/v3/cmd/sops/subcommand/keyservice"
|
||||
publishcmd "github.com/getsops/sops/v3/cmd/sops/subcommand/publish"
|
||||
@@ -428,6 +429,38 @@ func main() {
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "filestatus",
|
||||
Usage: "check the status of the file, returning encryption status",
|
||||
ArgsUsage: `file`,
|
||||
Flags: []cli.Flag{},
|
||||
Action: func(c *cli.Context) error {
|
||||
if c.NArg() < 1 {
|
||||
return common.NewExitError("Error: no file specified", codes.NoFileSpecified)
|
||||
}
|
||||
|
||||
fileName := c.Args()[0]
|
||||
inputStore := inputStore(c, fileName)
|
||||
opts := filestatuscmd.Opts{
|
||||
InputStore: inputStore,
|
||||
InputPath: fileName,
|
||||
}
|
||||
|
||||
status, err := filestatuscmd.FileStatus(opts)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
json, err := encodingjson.Marshal(status)
|
||||
if err != nil {
|
||||
return common.NewExitError(err, codes.ErrorGeneric)
|
||||
}
|
||||
|
||||
fmt.Println(string(json))
|
||||
|
||||
return nil
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "groups",
|
||||
Usage: "modify the groups on a SOPS file",
|
||||
|
||||
60
cmd/sops/subcommand/filestatus/filestatus.go
Normal file
60
cmd/sops/subcommand/filestatus/filestatus.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package filestatus
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/getsops/sops/v3"
|
||||
"github.com/getsops/sops/v3/cmd/sops/common"
|
||||
)
|
||||
|
||||
// Opts represent the input options for FileStatus
|
||||
type Opts struct {
|
||||
InputStore sops.Store
|
||||
InputPath string
|
||||
}
|
||||
|
||||
// Status represents the status of a file
|
||||
type Status struct {
|
||||
// Encrypted represents whether the file provided is encrypted by SOPS
|
||||
Encrypted bool `json:"encrypted"`
|
||||
}
|
||||
|
||||
// FileStatus checks encryption status of a file
|
||||
func FileStatus(opts Opts) (Status, error) {
|
||||
encrypted, err := cfs(opts.InputStore, opts.InputPath)
|
||||
if err != nil {
|
||||
return Status{}, fmt.Errorf("cannot check file status: %w", err)
|
||||
}
|
||||
return Status{Encrypted: encrypted}, nil
|
||||
}
|
||||
|
||||
// cfs checks and reports on file encryption status.
|
||||
//
|
||||
// It tries to decrypt the input file with the provided store.
|
||||
// It returns true if the file contains sops metadata, false
|
||||
// if it doesn't or Version or MessageAuthenticationCode are
|
||||
// not found.
|
||||
// It reports any error encountered different from
|
||||
// sops.MetadataNotFound, as that is used to detect a sops
|
||||
// encrypted file.
|
||||
func cfs(s sops.Store, inputpath string) (bool, error) {
|
||||
tree, err := common.LoadEncryptedFile(s, inputpath)
|
||||
if err != nil && err == sops.MetadataNotFound {
|
||||
return false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("cannot load encrypted file: %w", err)
|
||||
}
|
||||
|
||||
// NOTE: even if it's a file that sops recognize as containing
|
||||
// valid metadata, we want to ensure some metadata are present
|
||||
// to report the file as encrypted.
|
||||
if tree.Metadata.Version == "" {
|
||||
return false, nil
|
||||
}
|
||||
if tree.Metadata.MessageAuthenticationCode == "" {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
52
cmd/sops/subcommand/filestatus/filestatus_internal_test.go
Normal file
52
cmd/sops/subcommand/filestatus/filestatus_internal_test.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package filestatus
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/getsops/sops/v3/cmd/sops/common"
|
||||
"github.com/getsops/sops/v3/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const repoRoot = "../../../../"
|
||||
|
||||
func fromRepoRoot(p string) string {
|
||||
return path.Join(repoRoot, p)
|
||||
}
|
||||
|
||||
func TestFileStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
file string
|
||||
expectedEncrypted bool
|
||||
}{
|
||||
{
|
||||
name: "encrypted file should be reported as such",
|
||||
file: "example.yaml",
|
||||
expectedEncrypted: true,
|
||||
},
|
||||
{
|
||||
name: "plain text file should be reported as cleartext",
|
||||
file: "functional-tests/res/plainfile.yaml",
|
||||
},
|
||||
{
|
||||
name: "file without mac should be reported as cleartext",
|
||||
file: "functional-tests/res/plainfile.yaml",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f := fromRepoRoot(tt.file)
|
||||
s := common.DefaultStoreForPath(config.NewStoresConfig(), f)
|
||||
encrypted, err := cfs(s, f)
|
||||
require.Nil(t, err, "should not error")
|
||||
if tt.expectedEncrypted {
|
||||
require.True(t, encrypted, "file should have been reported as encrypted")
|
||||
} else {
|
||||
require.False(t, encrypted, "file should have been reported as cleartext")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
1
functional-tests/res/plainfile.yaml
Normal file
1
functional-tests/res/plainfile.yaml
Normal file
@@ -0,0 +1 @@
|
||||
hello: world
|
||||
Reference in New Issue
Block a user