Add the ability to use multiple paths for capability checking (#3663)
* Add the ability to use multiple paths for capability checking. WIP (tests, docs). Fixes #3336 * Added tests * added 'paths' field * Update docs * return error if paths is not supplied
This commit is contained in:
parent
ff99c8420e
commit
5034ae2dcb
|
@ -101,8 +101,12 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
|||
Description: "Accessor of the token for which capabilities are being queried.",
|
||||
},
|
||||
"path": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Path on which capabilities are being queried.",
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "(DEPRECATED) Path on which capabilities are being queried. Use 'paths' instead.",
|
||||
},
|
||||
"paths": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Paths on which capabilities are being queried.",
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -151,8 +155,12 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
|||
Description: "Token for which capabilities are being queried.",
|
||||
},
|
||||
"path": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Path on which capabilities are being queried.",
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "(DEPRECATED) Path on which capabilities are being queried. Use 'paths' instead.",
|
||||
},
|
||||
"paths": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Paths on which capabilities are being queried.",
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -173,8 +181,12 @@ func NewSystemBackend(core *Core) *SystemBackend {
|
|||
Description: "Token for which capabilities are being queried.",
|
||||
},
|
||||
"path": &framework.FieldSchema{
|
||||
Type: framework.TypeString,
|
||||
Description: "Path on which capabilities are being queried.",
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "(DEPRECATED) Path on which capabilities are being queried. Use 'paths' instead.",
|
||||
},
|
||||
"paths": &framework.FieldSchema{
|
||||
Type: framework.TypeCommaStringSlice,
|
||||
Description: "Paths on which capabilities are being queried.",
|
||||
},
|
||||
},
|
||||
|
||||
|
@ -1264,24 +1276,6 @@ func (b *SystemBackend) handleAuditedHeadersRead(ctx context.Context, req *logic
|
|||
}, nil
|
||||
}
|
||||
|
||||
// handleCapabilities returns the ACL capabilities of the token for a given path
|
||||
func (b *SystemBackend) handleCapabilities(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
token := d.Get("token").(string)
|
||||
if token == "" {
|
||||
token = req.ClientToken
|
||||
}
|
||||
capabilities, err := b.Core.Capabilities(ctx, token, d.Get("path").(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"capabilities": capabilities,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// handleCapabilitiesAccessor returns the ACL capabilities of the
|
||||
// token associted with the given accessor for a given path.
|
||||
func (b *SystemBackend) handleCapabilitiesAccessor(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
|
@ -1295,16 +1289,53 @@ func (b *SystemBackend) handleCapabilitiesAccessor(ctx context.Context, req *log
|
|||
return nil, err
|
||||
}
|
||||
|
||||
capabilities, err := b.Core.Capabilities(ctx, aEntry.TokenID, d.Get("path").(string))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
d.Raw["token"] = aEntry.TokenID
|
||||
return b.handleCapabilities(ctx, req, d)
|
||||
}
|
||||
|
||||
// handleCapabilities returns the ACL capabilities of the token for a given path
|
||||
func (b *SystemBackend) handleCapabilities(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
|
||||
var token string
|
||||
if strings.HasSuffix(req.Path, "capabilities-self") {
|
||||
token = req.ClientToken
|
||||
} else {
|
||||
tokenRaw, ok := d.Raw["token"]
|
||||
if ok {
|
||||
token, _ = tokenRaw.(string)
|
||||
}
|
||||
}
|
||||
if token == "" {
|
||||
return nil, fmt.Errorf("no token found")
|
||||
}
|
||||
|
||||
return &logical.Response{
|
||||
Data: map[string]interface{}{
|
||||
"capabilities": capabilities,
|
||||
},
|
||||
}, nil
|
||||
ret := &logical.Response{
|
||||
Data: map[string]interface{}{},
|
||||
}
|
||||
|
||||
paths := d.Get("paths").([]string)
|
||||
if len(paths) == 0 {
|
||||
// Read from the deprecated field
|
||||
paths = d.Get("path").([]string)
|
||||
}
|
||||
|
||||
if len(paths) == 0 {
|
||||
return logical.ErrorResponse("paths must be supplied"), nil
|
||||
}
|
||||
|
||||
for _, path := range paths {
|
||||
pathCap, err := b.Core.Capabilities(ctx, token, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret.Data[path] = pathCap
|
||||
}
|
||||
|
||||
// This is only here for backwards compatibility
|
||||
if len(paths) == 1 {
|
||||
ret.Data["capabilities"] = ret.Data[paths[0]]
|
||||
}
|
||||
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
// handleRekeyRetrieve returns backed-up, PGP-encrypted unseal keys from a
|
||||
|
|
|
@ -337,9 +337,159 @@ path "foo/bar*" {
|
|||
path "sys/capabilities*" {
|
||||
capabilities = ["update"]
|
||||
}
|
||||
path "bar/baz" {
|
||||
capabilities = ["read", "update"]
|
||||
}
|
||||
path "bar/baz" {
|
||||
capabilities = ["delete"]
|
||||
}
|
||||
`
|
||||
|
||||
func TestSystemBackend_Capabilities(t *testing.T) {
|
||||
func TestSystemBackend_PathCapabilities(t *testing.T) {
|
||||
var resp *logical.Response
|
||||
var err error
|
||||
|
||||
core, b, rootToken := testCoreSystemBackend(t)
|
||||
|
||||
policy, _ := ParseACLPolicy(capabilitiesPolicy)
|
||||
err = core.policyStore.SetPolicy(context.Background(), policy)
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
path1 := "foo/bar"
|
||||
path2 := "foo/bar/sample"
|
||||
path3 := "sys/capabilities"
|
||||
path4 := "bar/baz"
|
||||
|
||||
rootCheckFunc := func(t *testing.T, resp *logical.Response) {
|
||||
// All the paths should have "root" as the capability
|
||||
expectedRoot := []string{"root"}
|
||||
if !reflect.DeepEqual(resp.Data[path1], expectedRoot) ||
|
||||
!reflect.DeepEqual(resp.Data[path2], expectedRoot) ||
|
||||
!reflect.DeepEqual(resp.Data[path3], expectedRoot) ||
|
||||
!reflect.DeepEqual(resp.Data[path4], expectedRoot) {
|
||||
t.Fatalf("bad: capabilities; expected: %#v, actual: %#v", expectedRoot, resp.Data)
|
||||
}
|
||||
}
|
||||
|
||||
// Check the capabilities using the root token
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
Path: "capabilities",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"paths": []string{path1, path2, path3, path4},
|
||||
"token": rootToken,
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
||||
}
|
||||
rootCheckFunc(t, resp)
|
||||
|
||||
// Check the capabilities using capabilities-self
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
ClientToken: rootToken,
|
||||
Path: "capabilities-self",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"paths": []string{path1, path2, path3, path4},
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
||||
}
|
||||
rootCheckFunc(t, resp)
|
||||
|
||||
// Lookup the accessor of the root token
|
||||
te, err := core.tokenStore.Lookup(context.Background(), rootToken)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check the capabilities using capabilities-accessor endpoint
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
Path: "capabilities-accessor",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"paths": []string{path1, path2, path3, path4},
|
||||
"accessor": te.Accessor,
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
||||
}
|
||||
rootCheckFunc(t, resp)
|
||||
|
||||
// Create a non-root token
|
||||
testMakeToken(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"})
|
||||
|
||||
nonRootCheckFunc := func(t *testing.T, resp *logical.Response) {
|
||||
expected1 := []string{"create", "sudo", "update"}
|
||||
expected2 := expected1
|
||||
expected3 := []string{"update"}
|
||||
expected4 := []string{"delete", "read", "update"}
|
||||
|
||||
if !reflect.DeepEqual(resp.Data[path1], expected1) ||
|
||||
!reflect.DeepEqual(resp.Data[path2], expected2) ||
|
||||
!reflect.DeepEqual(resp.Data[path3], expected3) ||
|
||||
!reflect.DeepEqual(resp.Data[path4], expected4) {
|
||||
t.Fatalf("bad: capabilities; actual: %#v", resp.Data)
|
||||
}
|
||||
}
|
||||
|
||||
// Check the capabilities using a non-root token
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
Path: "capabilities",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"paths": []string{path1, path2, path3, path4},
|
||||
"token": "tokenid",
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
||||
}
|
||||
nonRootCheckFunc(t, resp)
|
||||
|
||||
// Check the capabilities of a non-root token using capabilities-self
|
||||
// endpoint
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
ClientToken: "tokenid",
|
||||
Path: "capabilities-self",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"paths": []string{path1, path2, path3, path4},
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
||||
}
|
||||
nonRootCheckFunc(t, resp)
|
||||
|
||||
// Lookup the accessor of the non-root token
|
||||
te, err = core.tokenStore.Lookup(context.Background(), "tokenid")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check the capabilities using a non-root token using
|
||||
// capabilities-accessor endpoint
|
||||
resp, err = b.HandleRequest(context.Background(), &logical.Request{
|
||||
Path: "capabilities-accessor",
|
||||
Operation: logical.UpdateOperation,
|
||||
Data: map[string]interface{}{
|
||||
"paths": []string{path1, path2, path3, path4},
|
||||
"accessor": te.Accessor,
|
||||
},
|
||||
})
|
||||
if err != nil || (resp != nil && resp.IsError()) {
|
||||
t.Fatalf("bad: resp: %#v\nerr: %v", resp, err)
|
||||
}
|
||||
nonRootCheckFunc(t, resp)
|
||||
}
|
||||
|
||||
func TestSystemBackend_Capabilities_BC(t *testing.T) {
|
||||
testCapabilities(t, "capabilities")
|
||||
testCapabilities(t, "capabilities-self")
|
||||
}
|
||||
|
@ -347,7 +497,11 @@ func TestSystemBackend_Capabilities(t *testing.T) {
|
|||
func testCapabilities(t *testing.T, endpoint string) {
|
||||
core, b, rootToken := testCoreSystemBackend(t)
|
||||
req := logical.TestRequest(t, logical.UpdateOperation, endpoint)
|
||||
req.Data["token"] = rootToken
|
||||
if endpoint == "capabilities-self" {
|
||||
req.ClientToken = rootToken
|
||||
} else {
|
||||
req.Data["token"] = rootToken
|
||||
}
|
||||
req.Data["path"] = "any_path"
|
||||
|
||||
resp, err := b.HandleRequest(context.Background(), req)
|
||||
|
@ -372,7 +526,11 @@ func testCapabilities(t *testing.T, endpoint string) {
|
|||
|
||||
testMakeToken(t, core.tokenStore, rootToken, "tokenid", "", []string{"test"})
|
||||
req = logical.TestRequest(t, logical.UpdateOperation, endpoint)
|
||||
req.Data["token"] = "tokenid"
|
||||
if endpoint == "capabilities-self" {
|
||||
req.ClientToken = "tokenid"
|
||||
} else {
|
||||
req.Data["token"] = "tokenid"
|
||||
}
|
||||
req.Data["path"] = "foo/bar"
|
||||
|
||||
resp, err = b.HandleRequest(context.Background(), req)
|
||||
|
@ -390,7 +548,7 @@ func testCapabilities(t *testing.T, endpoint string) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestSystemBackend_CapabilitiesAccessor(t *testing.T) {
|
||||
func TestSystemBackend_CapabilitiesAccessor_BC(t *testing.T) {
|
||||
core, b, rootToken := testCoreSystemBackend(t)
|
||||
te, err := core.tokenStore.Lookup(context.Background(), rootToken)
|
||||
if err != nil {
|
||||
|
@ -1747,22 +1905,22 @@ func TestSystemBackend_rotate(t *testing.T) {
|
|||
|
||||
func testSystemBackend(t *testing.T) logical.Backend {
|
||||
c, _, _ := TestCoreUnsealed(t)
|
||||
return testSystemBackendInternal(t, c)
|
||||
return c.systemBackend
|
||||
}
|
||||
|
||||
func testSystemBackendRaw(t *testing.T) logical.Backend {
|
||||
c, _, _ := TestCoreUnsealedRaw(t)
|
||||
return testSystemBackendInternal(t, c)
|
||||
return c.systemBackend
|
||||
}
|
||||
|
||||
func testCoreSystemBackend(t *testing.T) (*Core, logical.Backend, string) {
|
||||
c, _, root := TestCoreUnsealed(t)
|
||||
return c, testSystemBackendInternal(t, c), root
|
||||
return c, c.systemBackend, root
|
||||
}
|
||||
|
||||
func testCoreSystemBackendRaw(t *testing.T) (*Core, logical.Backend, string) {
|
||||
c, _, root := TestCoreUnsealedRaw(t)
|
||||
return c, testSystemBackendInternal(t, c), root
|
||||
return c, c.systemBackend, root
|
||||
}
|
||||
|
||||
func testSystemBackendInternal(t *testing.T, c *Core) logical.Backend {
|
||||
|
|
|
@ -26,18 +26,18 @@ for the given path.
|
|||
|
||||
### Parameters
|
||||
|
||||
- `accessor` `(string: <required>)` – Specifies the accessor of the token to
|
||||
check.
|
||||
- `accessor` `(string: <required>)` – Accessor of the token for which
|
||||
capabilities are being queried.
|
||||
|
||||
- `path` `(string: <required>)` – Specifies the path on which the token's
|
||||
capabilities will be checked.
|
||||
- `paths` `(list: <required>)` – Paths on which capabilities are being
|
||||
queried.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"accessor": "abcd1234",
|
||||
"path": "secret/foo"
|
||||
"paths": ["secret/foo", "secret/bar"]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -55,6 +55,15 @@ $ curl \
|
|||
|
||||
```json
|
||||
{
|
||||
"capabilities": ["read", "list"]
|
||||
"secret/bar": [
|
||||
"sudo",
|
||||
"update"
|
||||
],
|
||||
"secret/foo": [
|
||||
"delete",
|
||||
"list",
|
||||
"read",
|
||||
"update"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
|
@ -26,14 +26,13 @@ client token is the Vault token with which this API call is made.
|
|||
|
||||
### Parameters
|
||||
|
||||
- `path` `(string: <required>)` – Specifies the path on which the client token's
|
||||
capabilities will be checked.
|
||||
- `paths` `(list: <required>)` – Paths on which capabilities are being queried.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "secret/foo"
|
||||
"paths": ["secret/foo", "secret/bar"]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -51,6 +50,14 @@ $ curl \
|
|||
|
||||
```json
|
||||
{
|
||||
"capabilities": ["read", "list"]
|
||||
"secret/bar": [
|
||||
"sudo",
|
||||
"update"
|
||||
],
|
||||
"secret/foo": [
|
||||
"delete",
|
||||
"list",
|
||||
"read",
|
||||
"update"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
|
@ -24,18 +24,17 @@ This endpoint returns the list of capabilities for a provided token.
|
|||
|
||||
### Parameters
|
||||
|
||||
- `path` `(string: <required>)` – Specifies the path against which to check the
|
||||
token's capabilities.
|
||||
- `paths` `(list: <required>)` – Paths on which capabilities are being queried.
|
||||
|
||||
- `token` `(string: <required>)` – Specifies the token for which to check
|
||||
capabilities.
|
||||
- `token` `(string: <required>)` – Token for which capabilities are being
|
||||
queried.
|
||||
|
||||
### Sample Payload
|
||||
|
||||
```json
|
||||
{
|
||||
"path": "secret/foo",
|
||||
"token": "abcd1234"
|
||||
"token": "abcd1234",
|
||||
"paths": ["secret/foo", "secret/bar"]
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -53,6 +52,15 @@ $ curl \
|
|||
|
||||
```json
|
||||
{
|
||||
"capabilities": ["read", "list"]
|
||||
"secret/bar": [
|
||||
"sudo",
|
||||
"update"
|
||||
],
|
||||
"secret/foo": [
|
||||
"delete",
|
||||
"list",
|
||||
"read",
|
||||
"update"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
|
Loading…
Reference in New Issue