Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add the ability to use multiple paths for capability checking #3663

Merged
merged 8 commits into from
Mar 1, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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