Skip to content

Commit

Permalink
Add the ability to use multiple paths for capability checking (#3663)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
jefferai authored and vishalnayak committed Mar 1, 2018
1 parent d3b7e62 commit e7524b8
Show file tree
Hide file tree
Showing 5 changed files with 271 additions and 58 deletions.
95 changes: 63 additions & 32 deletions vault/logical_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
},
},

Expand Down Expand Up @@ -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.",
},
},

Expand All @@ -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.",
},
},

Expand Down Expand Up @@ -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) {
Expand All @@ -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
Expand Down
174 changes: 166 additions & 8 deletions vault/logical_system_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -337,17 +337,171 @@ 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")
}

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)
Expand All @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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 {
Expand Down
21 changes: 15 additions & 6 deletions website/source/api/system/capabilities-accessor.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
}
```

Expand All @@ -55,6 +55,15 @@ $ curl \

```json
{
"capabilities": ["read", "list"]
"secret/bar": [
"sudo",
"update"
],
"secret/foo": [
"delete",
"list",
"read",
"update"
]
}
```
Loading

0 comments on commit e7524b8

Please sign in to comment.