diff --git a/CHANGELOG.md b/CHANGELOG.md index 40e85477..5f8c7dda 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,11 +10,13 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Nothing should go in this section, please add to the latest unreleased version (and update the corresponding date), or add a new version. -## [8.0.4] - 2023-02-23 +## [8.0.4] - 2023-02-28 ### Fixed - Allow hostfactory cidrs to specify a subnet [cyberark/conjur-cli-go#113](https://github.com/cyberark/conjur-cli-go/pull/113) +- Update variable get to retrieve multiple variables + [cyberark/conjur-cli-go#114](https://github.com/cyberark/conjur-cli-go/pull/114) ## [8.0.3] - 2023-02-21 diff --git a/cmd/integration/integration_test.go b/cmd/integration/integration_test.go index b283cbbf..08073955 100644 --- a/cmd/integration/integration_test.go +++ b/cmd/integration/integration_test.go @@ -70,7 +70,7 @@ func TestIntegration(t *testing.T) { t.Run("policy load", func(t *testing.T) { stdOut, stdErr, err = conjurCLI.RunWithStdin( - bytes.NewReader([]byte("- !variable meow\n- !user alice\n- !host bob")), + bytes.NewReader([]byte("- !variable meow\n- !variable woof\n- !user alice\n- !host bob")), "policy", "load", "-b", "root", "-f", "-", ) assertPolicyLoadCmd(t, err, stdOut, stdErr) @@ -81,11 +81,21 @@ func TestIntegration(t *testing.T) { assertSetVariableCmd(t, err, stdOut, stdErr) }) + t.Run("set another variable after policy load", func(t *testing.T) { + stdOut, stdErr, err = conjurCLI.Run("variable", "set", "-i", "woof", "-v", "quack") + assertSetVariableCmd(t, err, stdOut, stdErr) + }) + t.Run("get variable after policy load", func(t *testing.T) { stdOut, stdErr, err = conjurCLI.Run("variable", "get", "-i", "meow") assertGetVariableCmd(t, err, stdOut, stdErr) }) + t.Run("get two variables after policy load", func(t *testing.T) { + stdOut, stdErr, err = conjurCLI.Run("variable", "get", "-i", "meow,woof") + assertGetTwoVariablesCmd(t, err, stdOut, stdErr) + }) + t.Run("exists returns false", func(t *testing.T) { stdOut, stdErr, err = conjurCLI.Run("role", "exists", "dev:user:meow") assertExistsCmd(t, err, stdOut, stdErr) diff --git a/cmd/integration/shared.go b/cmd/integration/shared.go index 5e5bcb14..357e94d5 100644 --- a/cmd/integration/shared.go +++ b/cmd/integration/shared.go @@ -146,6 +146,13 @@ func assertGetVariableCmd(t *testing.T, err error, stdOut string, stdErr string) assert.Equal(t, "", stdErr) } +func assertGetTwoVariablesCmd(t *testing.T, err error, stdOut string, stdErr string) { + assert.NoError(t, err) + assert.Contains(t, stdOut, "moo\n") + assert.Contains(t, stdOut, "quack\n") + assert.Equal(t, "", stdErr) +} + func assertExistsCmd(t *testing.T, err error, stdOut string, stdErr string) { assert.NoError(t, err) assert.Equal(t, "false\n", stdOut) diff --git a/dev/policy.yml b/dev/policy.yml index bb320f15..282afd77 100644 --- a/dev/policy.yml +++ b/dev/policy.yml @@ -8,6 +8,7 @@ - !layer test-layer - !variable secret +- !variable top-secret - !permit # Give permissions to the human user to update the secret and fetch the secret. diff --git a/go.mod b/go.mod index 2e03fa68..8e25cfb3 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ go 1.19 require ( github.com/chzyer/readline v1.5.1 - github.com/cyberark/conjur-api-go v0.10.3-0.20230217143503-a059d3d0c5da // Run "go get github.com/cyberark/conjur-api-go@main" to update + github.com/cyberark/conjur-api-go v0.10.3-0.20230217154521-e01f713716d2 // Run "go get github.com/cyberark/conjur-api-go@main" to update github.com/manifoldco/promptui v0.9.0 github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 github.com/spf13/cobra v1.5.0 diff --git a/go.sum b/go.sum index cc2569a2..5e1cb740 100644 --- a/go.sum +++ b/go.sum @@ -13,8 +13,8 @@ github.com/chzyer/test v1.0.0 h1:p3BQDXSxOhOG0P9z6/hGnII4LGiEPOYBhs8asl/fC04= github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38GC8= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyberark/conjur-api-go v0.10.3-0.20230217143503-a059d3d0c5da h1:nwoF3JbH7dL+RqxpHkwkKu+or+QUkdKrYzoGUAGnw2Q= -github.com/cyberark/conjur-api-go v0.10.3-0.20230217143503-a059d3d0c5da/go.mod h1:AbU7bDVW6ygUdgTDCKkh4wyfIVrOtdEeE/r01OE1EYo= +github.com/cyberark/conjur-api-go v0.10.3-0.20230217154521-e01f713716d2 h1:bvwd6+PiMS71vTjbk+I7CoZ5y0lk8StzoalRaTdCUP4= +github.com/cyberark/conjur-api-go v0.10.3-0.20230217154521-e01f713716d2/go.mod h1:AbU7bDVW6ygUdgTDCKkh4wyfIVrOtdEeE/r01OE1EYo= github.com/danieljoos/wincred v1.1.2 h1:QLdCxFs1/Yl4zduvBdcHB8goaYk9RARS2SgLLRuAyr0= github.com/danieljoos/wincred v1.1.2/go.mod h1:GijpziifJoIBfYh+S7BbkdUTU4LfM+QnGqR5Vl2tAx0= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/pkg/clients/clients.go b/pkg/clients/clients.go index ae536939..7084e4b7 100644 --- a/pkg/clients/clients.go +++ b/pkg/clients/clients.go @@ -25,6 +25,7 @@ type ConjurClient interface { LoadPolicy(mode conjurapi.PolicyMode, policyID string, policy io.Reader) (*conjurapi.PolicyResponse, error) AddSecret(variableID string, secretValue string) error RetrieveSecret(variableID string) ([]byte, error) + RetrieveBatchSecretsSafe(variableIDs []string) (map[string][]byte, error) RetrieveSecretWithVersion(variableID string, version int) ([]byte, error) CheckPermission(resourceID string, privilege string) (bool, error) CheckPermissionForRole(resourceID string, roleID string, privilege string) (bool, error) diff --git a/pkg/cmd/variable.go b/pkg/cmd/variable.go index 84ca0269..6d98084d 100644 --- a/pkg/cmd/variable.go +++ b/pkg/cmd/variable.go @@ -1,7 +1,9 @@ package cmd import ( + "fmt" "strconv" + "strings" "github.com/cyberark/conjur-cli-go/pkg/clients" "github.com/spf13/cobra" @@ -26,7 +28,8 @@ func newVariableCmd( // Cobra supports Persistent Flags which will work for this command // and all subcommands, e.g.: - variableGetCmd.Flags().StringP("id", "i", "", "Provide variable identifier") + ids := make([]string, 0) + variableGetCmd.Flags().StringSliceP("id", "i", ids, "Provide variable identifiers") variableGetCmd.MarkFlagRequired("id") // Variable versions start from 1 and then increase so we don't know @@ -44,6 +47,7 @@ func newVariableCmd( type variableGetClient interface { RetrieveSecret(string) ([]byte, error) + RetrieveBatchSecretsSafe(variableIDs []string) (map[string][]byte, error) RetrieveSecretWithVersion(string, int) ([]byte, error) } type variableSetClient interface { @@ -61,17 +65,44 @@ func variableSetClientFactory(cmd *cobra.Command) (variableSetClient, error) { return clients.AuthenticatedConjurClientForCommand(cmd) } +func printMultilineResults(cmd *cobra.Command, secrets map[string][]byte) error { + if len(secrets) > 1 { + cmd.Println("{") + for fullID, value := range secrets { + id := strings.Split(string(fullID), ":") + cmd.Printf(" \"%s\": \"%s\"\n", id[len(id)-1], value) + } + cmd.Println("}") + } else { + for _, v := range secrets { + cmd.Println(string(v)) + } + } + return nil +} + func newVariableGetCmd(clientFactory variableGetClientFactoryFunc) *cobra.Command { return &cobra.Command{ - Use: "get", - Short: "Use the get subcommand to get the value of one or more Conjur variables", + Use: "get", + Short: "Use the get subcommand to get the value of one or more Conjur variables", + Long: `Use the get subcommand to get the value of one or more Conjur variables + +Examples: +- conjur variable set -i secret -v my_secret_value +- conjur variable get -i secret +- conjur variable get -i secret,secret2 +- conjur variable get -i secret -v 1 + `, SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { - id, err := cmd.Flags().GetString("id") + id, err := cmd.Flags().GetStringSlice("id") if err != nil { return err } - + singleID := "" + if len(id) == 1 { + singleID = id[0] + } client, err := clientFactory(cmd) if err != nil { return err @@ -82,26 +113,24 @@ func newVariableGetCmd(clientFactory variableGetClientFactoryFunc) *cobra.Comman return err } - var data []byte + data := map[string][]byte{} if versionStr == "" { - data, err = client.RetrieveSecret(id) + data, err = client.RetrieveBatchSecretsSafe(id) } else { + if len(id) > 1 { + return fmt.Errorf("version can not be used with multiple variables") + } version, err := strconv.Atoi(versionStr) if err != nil { return err } - - data, err = client.RetrieveSecretWithVersion(id, version) + data[singleID], err = client.RetrieveSecretWithVersion(singleID, version) } - if err != nil { return err } - - cmd.Println(string(data)) - - return nil + return printMultilineResults(cmd, data) }, } } diff --git a/pkg/cmd/variable_test.go b/pkg/cmd/variable_test.go index dae1a5fd..91b4b595 100644 --- a/pkg/cmd/variable_test.go +++ b/pkg/cmd/variable_test.go @@ -12,6 +12,7 @@ type mockVariableClient struct { t *testing.T get func(*testing.T, string) ([]byte, error) getWithVersion func(*testing.T, string, int) ([]byte, error) + getBatch func(*testing.T, []string) (map[string][]byte, error) set func(*testing.T, string, string) error } @@ -21,6 +22,9 @@ func (m mockVariableClient) RetrieveSecret(path string) ([]byte, error) { func (m mockVariableClient) RetrieveSecretWithVersion(path string, version int) ([]byte, error) { return m.getWithVersion(m.t, path, version) } +func (m mockVariableClient) RetrieveBatchSecretsSafe(paths []string) (map[string][]byte, error) { + return m.getBatch(m.t, paths) +} func (m mockVariableClient) AddSecret(path string, value string) error { return m.set(m.t, path, value) } @@ -30,6 +34,7 @@ var variableCmdTestCases = []struct { args []string get func(t *testing.T, path string) ([]byte, error) getWithVersion func(t *testing.T, path string, version int) ([]byte, error) + getBatch func(t *testing.T, paths []string) (map[string][]byte, error) set func(t *testing.T, path string, value string) error clientFactoryError error assert func(t *testing.T, stdout string, stderr string, err error) @@ -58,11 +63,11 @@ var variableCmdTestCases = []struct { { name: "get subcommand", args: []string{"variable", "get", "-i", "meow"}, - get: func(t *testing.T, path string) ([]byte, error) { + getBatch: func(t *testing.T, path []string) (map[string][]byte, error) { // Assert on arguments - assert.Equal(t, "meow", path) + assert.Equal(t, ("meow"), path[0]) - return []byte("moo"), nil + return map[string][]byte{"meow": []byte("moo")}, nil }, assert: func(t *testing.T, stdout, stderr string, err error) { assert.Contains(t, stdout, "moo") @@ -71,7 +76,7 @@ var variableCmdTestCases = []struct { { name: "get subcommand error", args: []string{"variable", "get", "-i", "meow"}, - get: func(t *testing.T, path string) ([]byte, error) { + getBatch: func(t *testing.T, path []string) (map[string][]byte, error) { return nil, fmt.Errorf("%s", "get error") }, assert: func(t *testing.T, stdout, stderr string, err error) { @@ -106,6 +111,27 @@ var variableCmdTestCases = []struct { assert.Contains(t, stderr, "Error: client factory error\n") }, }, + { + name: "get two variables", + args: []string{"variable", "get", "-i", "meow,woof"}, + getBatch: func(t *testing.T, path []string) (map[string][]byte, error) { + // Assert on arguments + assert.Equal(t, "meow", path[0]) + assert.Equal(t, "woof", path[1]) + return map[string][]byte{"meow": []byte("moo"), "woof": []byte("quack")}, nil + }, + assert: func(t *testing.T, stdout, stderr string, err error) { + assert.Contains(t, stdout, "moo") + assert.Contains(t, stdout, "quack") + }, + }, + { + name: "get two variables with version", + args: []string{"variable", "get", "-i", "meow,woof", "-v", "1"}, + assert: func(t *testing.T, stdout, stderr string, err error) { + assert.Contains(t, stderr, "version can not be used with multiple variables") + }, + }, { name: "set subcommand", args: []string{"variable", "set", "-i", "meow", "-v", "moo"}, @@ -153,7 +179,7 @@ func TestVariableCmd(t *testing.T) { for _, tc := range variableCmdTestCases { t.Run(tc.name, func(t *testing.T) { mockClient := mockVariableClient{ - t: t, set: tc.set, get: tc.get, getWithVersion: tc.getWithVersion, + t: t, set: tc.set, get: tc.get, getWithVersion: tc.getWithVersion, getBatch: tc.getBatch, } cmd := newVariableCmd(