From 90d060efeda4d9b93ce0170e7a5f3ea99edca2b7 Mon Sep 17 00:00:00 2001 From: John ODonnell Date: Wed, 25 Jan 2023 12:11:20 -0500 Subject: [PATCH] Implement CheckPermissionForRole --- conjurapi/client.go | 58 ++++++++++++++----------- conjurapi/resource.go | 20 ++++++++- conjurapi/resource_test.go | 87 +++++++++++++++++++++++++++++--------- conjurapi/variable_test.go | 3 +- 4 files changed, 122 insertions(+), 46 deletions(-) diff --git a/conjurapi/client.go b/conjurapi/client.go index adbfdc0..3e62419 100644 --- a/conjurapi/client.go +++ b/conjurapi/client.go @@ -324,26 +324,41 @@ func (c *Client) ChangeUserPasswordRequest(username string, password string, new return req, nil } -// CheckPermissionRequest crafts an HTTP request to Conjur's /resource endpoint -// to check if a certain user has the given privilege on the given resourceID. -// If the roleID parameter is provided, the check is made on behalf of the -// specified role - otherwise, the check is made of behalf of the authenticated user. -func (c *Client) CheckPermissionRequest(resourceID string, roleID string, privilege string) (*http.Request, error) { +// CheckPermissionForRoleRequest crafts an HTTP request to Conjur's /resource endpoint +// to check if the authenticated user has the given privilege on the given resourceID. +func (c *Client) CheckPermissionRequest(resourceID, privilege string) (*http.Request, error) { account, kind, id, err := c.parseID(resourceID) if err != nil { return nil, err } query := fmt.Sprintf("check=true&privilege=%s", url.QueryEscape(privilege)) - if len(roleID) != 0 { - roleAccount, roleKind, roleIdentifier, err := c.parseID(roleID) - if err != nil { - return nil, err - } - fullyQualifiedRoleID := strings.Join([]string{roleAccount, roleKind, roleIdentifier}, ":") - query = fmt.Sprintf("%s&role=%s", query, url.QueryEscape(fullyQualifiedRoleID)) + checkURL := makeRouterURL(c.resourcesURL(account), kind, url.QueryEscape(id)).withQuery(query).String() + + return http.NewRequest( + "GET", + checkURL, + nil, + ) +} + +// CheckPermissionForRoleRequest crafts an HTTP request to Conjur's /resource endpoint +// to check if a given role has the given privilege on the given resourceID. +func (c *Client) CheckPermissionForRoleRequest(resourceID, roleID, privilege string) (*http.Request, error) { + account, kind, id, err := c.parseID(resourceID) + if err != nil { + return nil, err } + + roleAccount, roleKind, roleIdentifier, err := c.parseID(roleID) + if err != nil { + return nil, err + } + fullyQualifiedRoleID := strings.Join([]string{roleAccount, roleKind, roleIdentifier}, ":") + + query := fmt.Sprintf("check=true&privilege=%s&role=%s", url.QueryEscape(privilege), url.QueryEscape(fullyQualifiedRoleID)) + checkURL := makeRouterURL(c.resourcesURL(account), kind, url.QueryEscape(id)).withQuery(query).String() return http.NewRequest( @@ -679,24 +694,19 @@ func makeFullId(account, kind, id string) string { return strings.Join(tokens, ":") } -// parseID accepts as argument a resource ID, and returns its components - account, +// parseID accepts as argument a resource ID and returns its components - account, // resource kind, and identifier. The provided ID can either be fully- or -// partially-qualified. If the ID is fully-qualified, the account must match the -// configured account. If the ID is partially-qualified, the configured account -// will be used. -// -// Returns the resource's account, kind, identifier, and fully-qualified ID. +// partially-qualified. If the ID is only partially-qualified, the configured +// account will be returned. // // Examples: -// parseID("dev:user:alice") => "dev", "user", "alice", "dev:user:alice", nil -// parseID("user:alice") => "dev", "user", "alice", "dev:user:alice", nil -// parseID("prod:user:alice") => "", "", "", "", error +// c.parseID("dev:user:alice") => "dev", "user", "alice", nil +// c.parseID("user:alice") => "dev", "user", "alice", nil +// c.parseID("prod:user:alice") => "prod", "user", "alice", nil +// c.parseID("malformed") => "", "", "". error func (c *Client) parseID(id string) (account, kind, identifier string, err error) { tokens := strings.SplitN(id, ":", 3) if len(tokens) == 3 { - if tokens[0] != c.config.Account { - return "", "", "", fmt.Errorf("Account of '%s' must match the configured account '%s'", id, c.config.Account) - } return tokens[0], tokens[1], tokens[2], nil } else if len(tokens) == 2 { return c.config.Account, tokens[0], tokens[1], nil diff --git a/conjurapi/resource.go b/conjurapi/resource.go index d1a5e47..8de7d0e 100644 --- a/conjurapi/resource.go +++ b/conjurapi/resource.go @@ -3,6 +3,7 @@ package conjurapi import ( "encoding/json" "fmt" + "net/http" "github.com/cyberark/conjur-api-go/conjurapi/response" ) @@ -16,12 +17,27 @@ type ResourceFilter struct { // CheckPermission determines whether the authenticated user has a specified privilege // on a resource. -func (c *Client) CheckPermission(resourceID string, roleID string, privilege string) (bool, error) { - req, err := c.CheckPermissionRequest(resourceID, roleID, privilege) +func (c *Client) CheckPermission(resourceID string, privilege string) (bool, error) { + req, err := c.CheckPermissionRequest(resourceID, privilege) if err != nil { return false, err } + return c.processPermissionCheck(req) +} + +// CheckPermissionForRole determines whether the provided role has a specific +// privilege on a resource. +func (c *Client) CheckPermissionForRole(resourceID string, roleID string, privilege string) (bool, error) { + req, err := c.CheckPermissionForRoleRequest(resourceID, roleID, privilege) + if err != nil { + return false, err + } + + return c.processPermissionCheck(req) +} + +func (c *Client) processPermissionCheck(req *http.Request) (bool, error) { resp, err := c.SubmitRequest(req) if err != nil { return false, err diff --git a/conjurapi/resource_test.go b/conjurapi/resource_test.go index 3aabc08..8209cb9 100644 --- a/conjurapi/resource_test.go +++ b/conjurapi/resource_test.go @@ -6,35 +6,84 @@ import ( "github.com/stretchr/testify/assert" ) -func TestClient_CheckPermission(t *testing.T) { - checkSucceeds := func(conjur *Client, id string, role string) func(t *testing.T) { - return func(t *testing.T) { - allowed, err := conjur.CheckPermission(id, role, "execute") +type checkAssertion func(t *testing.T, result bool, err error) - assert.NoError(t, err) - assert.True(t, allowed) - } - } +func assertSuccess(t *testing.T, result bool, err error) { + assert.True(t, result) + assert.NoError(t, err) +} - checkFails := func(conjur *Client, id string, role string) func(t *testing.T) { - return func(t *testing.T) { - allowed, err := conjur.CheckPermission(id, role, "execute") +func assertFailure(t *testing.T, result bool, err error) { + assert.False(t, result) + assert.NoError(t, err) +} - assert.NoError(t, err) - assert.False(t, allowed) +func assertError(t *testing.T, result bool, err error) { + assert.False(t, result) + assert.Error(t, err) +} + +func checkAndAssert( + conjur *Client, + assertion checkAssertion, + args ...string, +) func(t *testing.T) { + return func(t *testing.T) { + var result bool + var err error + + if len(args) == 1 { + result, err = conjur.CheckPermission(args[0], "execute") + } else if len(args) == 2 { + result, err = conjur.CheckPermissionForRole(args[0], args[1], "execute") } + + assertion(t, result, err) } +} +func TestClient_CheckPermission(t *testing.T) { conjur, err := conjurSetup(&Config{}, defaultTestPolicy) assert.NoError(t, err) - t.Run("Check an allowed permission for default admin role", checkSucceeds(conjur, "cucumber:variable:db-password", "")) - t.Run("Check an allowed permission for a role", checkSucceeds(conjur, "cucumber:variable:db-password", "cucumber:user:alice")) - t.Run("Check a permission for an account-less role", checkSucceeds(conjur, "cucumber:variable:db-password", "user:alice")) - t.Run("Check a permission on account-less resource", checkSucceeds(conjur, "variable:db-password", "")) + t.Run( + "Check an allowed permission for default role", + checkAndAssert(conjur, assertSuccess, "cucumber:variable:db-password"), + ) + t.Run( + "Check a permission on account-less resource", + checkAndAssert(conjur, assertSuccess, "variable:db-password"), + ) + t.Run( + "Check a permission on a non-existent resource", + checkAndAssert(conjur, assertFailure, "cucumber:variable:foobar"), + ) +} + +func TestClient_CheckPermissionForRole(t *testing.T) { + conjur, err := conjurSetup(&Config{}, defaultTestPolicy) + assert.NoError(t, err) - t.Run("Check a permission on a non-existent resource", checkFails(conjur, "cucumber:variable:foobar", "cucumber:user:alice")) - t.Run("Check no permission for a role", checkFails(conjur, "cucumber:variable:db-password", "cucumber:host:bob")) + t.Run( + "Check an allowed permission for a role", + checkAndAssert(conjur, assertSuccess, "cucumber:variable:db-password", "cucumber:user:alice"), + ) + t.Run( + "Check a permission for an account-less role", + checkAndAssert(conjur, assertSuccess, "cucumber:variable:db-password", "user:alice"), + ) + t.Run( + "Check a permission on a non-existent resource", + checkAndAssert(conjur, assertFailure, "cucumber:variable:foobar", "cucumber:user:alice"), + ) + t.Run( + "Check no permission for a role", + checkAndAssert(conjur, assertFailure, "cucumber:variable:db-password", "cucumber:host:bob"), + ) + t.Run( + "Check a permission with empty role", + checkAndAssert(conjur, assertError, "cucumber:variable:db-password", ""), + ) } func TestClient_ResourceExists(t *testing.T) { diff --git a/conjurapi/variable_test.go b/conjurapi/variable_test.go index 9ace95b..0c28a1b 100644 --- a/conjurapi/variable_test.go +++ b/conjurapi/variable_test.go @@ -94,7 +94,8 @@ func TestClient_RetrieveSecret(t *testing.T) { t.Run("Rejects an id from the wrong account", func(t *testing.T) { _, err := conjur.RetrieveSecret("foobar:variable:" + variableIdentifier) - assert.Equal(t, err.Error(), fmt.Sprintf("Account of 'foobar:variable:%s' must match the configured account 'cucumber'", variableIdentifier)) + conjurError := err.(*response.ConjurError) + assert.Equal(t, 404, conjurError.Code) }) t.Run("Rejects an id with the wrong kind", func(t *testing.T) {