diff --git a/go.mod b/go.mod index 9f13eea0..6b9be829 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.20230119183450-0702b34d417f // Run "go get github.com/cyberark/conjur-api-go@main" to update + github.com/cyberark/conjur-api-go v0.10.3-0.20230126162601-5da6569f4916 // 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 cc6e5d99..f69a489e 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.20230119183450-0702b34d417f h1:XdJix4x14ZUa1In7cDNGkGA45O6ulCnGh+wUiOBCALE= -github.com/cyberark/conjur-api-go v0.10.3-0.20230119183450-0702b34d417f/go.mod h1:TYds39qj4L37XE/kXoeb4O5t95MdTsXAFD/6ilGppwg= +github.com/cyberark/conjur-api-go v0.10.3-0.20230126162601-5da6569f4916 h1:a3t76oZ1jlbqfxDsYL5LjNlqRgyR4kWk6Z/5E6dhWAY= +github.com/cyberark/conjur-api-go v0.10.3-0.20230126162601-5da6569f4916/go.mod h1:TYds39qj4L37XE/kXoeb4O5t95MdTsXAFD/6ilGppwg= 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 9c16537d..6a248fd8 100644 --- a/pkg/clients/clients.go +++ b/pkg/clients/clients.go @@ -23,7 +23,8 @@ 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) - CheckPermission(resourceID, privilege string) (bool, error) + CheckPermission(resourceID string, privilege string) (bool, error) + CheckPermissionForRole(resourceID string, roleID string, privilege string) (bool, error) ResourceExists(resourceID string) (bool, error) Resource(resourceID string) (resource map[string]interface{}, err error) ResourceIDs(filter *conjurapi.ResourceFilter) ([]string, error) diff --git a/pkg/cmd/check.go b/pkg/cmd/check.go index 21c6a55b..829380c9 100644 --- a/pkg/cmd/check.go +++ b/pkg/cmd/check.go @@ -6,7 +6,8 @@ import ( ) type checkClient interface { - CheckPermission(resourceID, privilege string) (bool, error) + CheckPermission(resourceID string, privilege string) (bool, error) + CheckPermissionForRole(resourceID string, roleID string, privilege string) (bool, error) } type checkClientFactoryFunc func(*cobra.Command) (checkClient, error) @@ -16,15 +17,23 @@ func checkClientFactory(cmd *cobra.Command) (checkClient, error) { } func newCheckCmd(clientFactory checkClientFactoryFunc) *cobra.Command { - return &cobra.Command{ + cmd := &cobra.Command{ Use: "check", - Short: "Check for a privilege on a resource", - Long: `Check whether the currently logged-in user has a given [privilege] on a resource specified by a [resource-id]. + Short: "Check a role's privilege on a resource", + Long: `Check a role's privilege on a resource + +This command requires a [resource-id] and a [privilege]. + +By default, this command checks if the currently authenticated +user has privilege over the resource. When the optional [-r|--role] +flag is provided, the command checks if the specified role has +privilege over the resource. Examples: -- conjur check dev:variable:somevariable read -- conjur check dev:host:somehost write`, +- conjur check dev:host:somehost write +- conjur check -r user:someuser dev:variable:somevariable read +- conjur check -r dev:user:someuser dev:variable:somevariable read`, RunE: func(cmd *cobra.Command, args []string) error { var resourceID string var privilege string @@ -36,12 +45,23 @@ Examples: resourceID, privilege = args[0], args[1] + roleID, err := cmd.Flags().GetString("role") + if err != nil { + return err + } + client, err := clientFactory(cmd) if err != nil { return err } - result, err := client.CheckPermission(resourceID, privilege) + var result bool + + if len(roleID) == 0 { + result, err = client.CheckPermission(resourceID, privilege) + } else { + result, err = client.CheckPermissionForRole(resourceID, roleID, privilege) + } if err != nil { return err } @@ -51,9 +71,14 @@ Examples: return nil }, } + + cmd.Flags().StringP("role", "r", "", "Partially- or fully-qualified role ID to check privilege for") + + return cmd } func init() { checkCmd := newCheckCmd(checkClientFactory) + rootCmd.AddCommand(checkCmd) } diff --git a/pkg/cmd/check_test.go b/pkg/cmd/check_test.go index 7ca3923e..ad8b6436 100644 --- a/pkg/cmd/check_test.go +++ b/pkg/cmd/check_test.go @@ -9,20 +9,26 @@ import ( ) type mockCheckClient struct { - t *testing.T - checkPermission func(t *testing.T, resourceID, privilege string) (bool, error) + t *testing.T + checkPermission func(t *testing.T, resourceID string, privilege string) (bool, error) + checkPermissionForRole func(t *testing.T, resourceID string, roleID string, privilege string) (bool, error) } -func (m mockCheckClient) CheckPermission(resourceID, privilege string) (bool, error) { +func (m mockCheckClient) CheckPermission(resourceID string, privilege string) (bool, error) { return m.checkPermission(m.t, resourceID, privilege) } +func (m mockCheckClient) CheckPermissionForRole(resourceID string, roleID string, privilege string) (bool, error) { + return m.checkPermissionForRole(m.t, resourceID, roleID, privilege) +} + var checkCmdTestCases = []struct { - name string - args []string - checkPermission func(t *testing.T, resourceID, privilege string) (bool, error) - clientFactoryError error - assert func(t *testing.T, stdout string, stderr string, err error) + name string + args []string + checkPermission func(t *testing.T, resourceID string, privilege string) (bool, error) + checkPermissionForRole func(t *testing.T, resourceID string, roleID string, privilege string) (bool, error) + clientFactoryError error + assert func(t *testing.T, stdout string, stderr string, err error) }{ { name: "check help", @@ -53,9 +59,16 @@ var checkCmdTestCases = []struct { }, }, { - name: "check return true", + name: "check with role flag and missing params", + args: []string{"check", "-r", "dev:user:alice"}, + assert: func(t *testing.T, stdout string, stderr string, err error) { + assert.Contains(t, stdout, "HELP LONG") + }, + }, + { + name: "check returns true for default admin role", args: []string{"check", "dev:variable:secret", "read"}, - checkPermission: func(t *testing.T, resourceID, privilege string) (bool, error) { + checkPermission: func(t *testing.T, resourceID string, privilege string) (bool, error) { assert.Equal(t, "dev:variable:secret", resourceID) assert.Equal(t, "read", privilege) @@ -66,10 +79,38 @@ var checkCmdTestCases = []struct { }, }, { - name: "check return false", + name: "check returns false for default admin role", args: []string{"check", "dev:variable:secret", "write"}, - checkPermission: func(t *testing.T, resourceID, privilege string) (bool, error) { + checkPermission: func(t *testing.T, resourceID string, privilege string) (bool, error) { + assert.Equal(t, "dev:variable:secret", resourceID) + assert.Equal(t, "write", privilege) + + return false, nil + }, + assert: func(t *testing.T, stdout, stderr string, err error) { + assert.Contains(t, stdout, "false") + }, + }, + { + name: "check returns true for specific role", + args: []string{"check", "-r", "dev:user:alice", "dev:variable:secret", "read"}, + checkPermissionForRole: func(t *testing.T, resourceID string, roleID string, privilege string) (bool, error) { + assert.Equal(t, "dev:variable:secret", resourceID) + assert.Equal(t, "dev:user:alice", roleID) + assert.Equal(t, "read", privilege) + + return true, nil + }, + assert: func(t *testing.T, stdout, stderr string, err error) { + assert.Contains(t, stdout, "true") + }, + }, + { + name: "check returns false for specific role", + args: []string{"check", "-r", "dev:user:alice", "dev:variable:secret", "write"}, + checkPermissionForRole: func(t *testing.T, resourceID string, roleID string, privilege string) (bool, error) { assert.Equal(t, "dev:variable:secret", resourceID) + assert.Equal(t, "dev:user:alice", roleID) assert.Equal(t, "write", privilege) return false, nil @@ -81,7 +122,7 @@ var checkCmdTestCases = []struct { { name: "check client error", args: []string{"check", "abcdefg", "hijklmn"}, - checkPermission: func(t *testing.T, resourceID, privilege string) (bool, error) { + checkPermission: func(t *testing.T, resourceID string, privilege string) (bool, error) { return false, fmt.Errorf("%s", "an error") }, assert: func(t *testing.T, stdout, stderr string, err error) { @@ -101,7 +142,11 @@ var checkCmdTestCases = []struct { func TestCheckCmd(t *testing.T) { for _, tc := range checkCmdTestCases { t.Run(tc.name, func(t *testing.T) { - mockClient := mockCheckClient{t: t, checkPermission: tc.checkPermission} + mockClient := mockCheckClient{ + t: t, + checkPermission: tc.checkPermission, + checkPermissionForRole: tc.checkPermissionForRole, + } cmd := newCheckCmd( func(cmd *cobra.Command) (checkClient, error) {