Skip to content

Commit

Permalink
Resultant acl (#4386)
Browse files Browse the repository at this point in the history
  • Loading branch information
jefferai authored Apr 20, 2018
1 parent 01abce8 commit 3580853
Show file tree
Hide file tree
Showing 8 changed files with 704 additions and 0 deletions.
122 changes: 122 additions & 0 deletions vault/logical_system.go
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,14 @@ func NewSystemBackend(core *Core, logger log.Logger) *SystemBackend {
HelpSynopsis: strings.TrimSpace(sysHelp["internal-ui-mounts"][0]),
HelpDescription: strings.TrimSpace(sysHelp["internal-ui-mounts"][1]),
},
&framework.Path{
Pattern: "internal/ui/resultant-acl",
Callbacks: map[logical.Operation]framework.OperationFunc{
logical.ReadOperation: b.pathInternalUIResultantACL,
},
HelpSynopsis: strings.TrimSpace(sysHelp["internal-ui-resultant-acl"][0]),
HelpDescription: strings.TrimSpace(sysHelp["internal-ui-resultant-acl"][1]),
},
},
}

Expand Down Expand Up @@ -3430,6 +3438,110 @@ func (b *SystemBackend) pathInternalUIMountsRead(ctx context.Context, req *logic
return resp, nil
}

func (b *SystemBackend) pathInternalUIResultantACL(ctx context.Context, req *logical.Request, d *framework.FieldData) (*logical.Response, error) {
if req.ClientToken == "" {
// 204 -- no ACL
return nil, nil
}

acl, _, entity, err := b.Core.fetchACLTokenEntryAndEntity(req.ClientToken)
if err != nil {
return nil, err
}

if entity != nil && entity.Disabled {
return logical.ErrorResponse(logical.ErrEntityDisabled.Error()), nil
}

resp := &logical.Response{
Data: map[string]interface{}{
"root": false,
},
}

if acl.root {
resp.Data["root"] = true
return resp, nil
}

exact := map[string]interface{}{}
glob := map[string]interface{}{}

walkFn := func(pt map[string]interface{}, s string, v interface{}) {
if v == nil {
return
}

perms := v.(*ACLPermissions)
capabilities := []string{}

if perms.CapabilitiesBitmap&CreateCapabilityInt > 0 {
capabilities = append(capabilities, CreateCapability)
}
if perms.CapabilitiesBitmap&DeleteCapabilityInt > 0 {
capabilities = append(capabilities, DeleteCapability)
}
if perms.CapabilitiesBitmap&ListCapabilityInt > 0 {
capabilities = append(capabilities, ListCapability)
}
if perms.CapabilitiesBitmap&ReadCapabilityInt > 0 {
capabilities = append(capabilities, ReadCapability)
}
if perms.CapabilitiesBitmap&SudoCapabilityInt > 0 {
capabilities = append(capabilities, SudoCapability)
}
if perms.CapabilitiesBitmap&UpdateCapabilityInt > 0 {
capabilities = append(capabilities, UpdateCapability)
}

// If "deny" is explicitly set or if the path has no capabilities at all,
// set the path capabilities to "deny"
if perms.CapabilitiesBitmap&DenyCapabilityInt > 0 || len(capabilities) == 0 {
capabilities = []string{DenyCapability}
}

res := map[string]interface{}{}
if len(capabilities) > 0 {
res["capabilities"] = capabilities
}
if perms.MinWrappingTTL != 0 {
res["min_wrapping_ttl"] = int64(perms.MinWrappingTTL.Seconds())
}
if perms.MaxWrappingTTL != 0 {
res["max_wrapping_ttl"] = int64(perms.MaxWrappingTTL.Seconds())
}
if len(perms.AllowedParameters) > 0 {
res["allowed_parameters"] = perms.AllowedParameters
}
if len(perms.DeniedParameters) > 0 {
res["denied_parameters"] = perms.DeniedParameters
}
if len(perms.RequiredParameters) > 0 {
res["required_parameters"] = perms.RequiredParameters
}

pt[s] = res
}

exactWalkFn := func(s string, v interface{}) bool {
walkFn(exact, s, v)
return false
}

globWalkFn := func(s string, v interface{}) bool {
walkFn(glob, s, v)
return false
}

acl.exactRules.Walk(exactWalkFn)
acl.globRules.Walk(globWalkFn)

resp.Data["exact_paths"] = exact
resp.Data["glob_paths"] = glob

return resp, nil
}

func sanitizeMountPath(path string) string {
if !strings.HasSuffix(path, "/") {
path += "/"
Expand Down Expand Up @@ -4040,12 +4152,22 @@ This path responds to the following HTTP methods.
},
"listing_visibility": {
"Determines the visibility of the mount in the UI-specific listing endpoint.",
"",
},
"passthrough_request_headers": {
"A list of headers to whitelist and pass from the request to the backend.",
"",
},
"raw": {
"Write, Read, and Delete data directly in the Storage backend.",
"",
},
"internal-ui-mounts": {
"Information about mounts returned according to their tuned visibility. Internal API; its location, inputs, and outputs may change.",
"",
},
"internal-ui-resultant-acl": {
"Information about a token's resultant ACL. Internal API; its location, inputs, and outputs may change.",
"",
},
}
136 changes: 136 additions & 0 deletions vault/logical_system_integ_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import (
"testing"
"time"

"github.com/go-test/deep"
"github.com/hashicorp/vault/api"
"github.com/hashicorp/vault/builtin/plugin"
"github.com/hashicorp/vault/helper/pluginutil"
vaulthttp "github.com/hashicorp/vault/http"
Expand Down Expand Up @@ -604,3 +606,137 @@ func TestBackend_PluginMainCredentials(t *testing.T) {
t.Fatal(err)
}
}

func TestSystemBackend_InternalUIResultantACL(t *testing.T) {
cluster := vault.NewTestCluster(t, nil, &vault.TestClusterOptions{
HandlerFunc: vaulthttp.Handler,
})
cluster.Start()
defer cluster.Cleanup()
client := cluster.Cores[0].Client

resp, err := client.Auth().Token().Create(&api.TokenCreateRequest{
Policies: []string{"default"},
})
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
if resp.Auth == nil {
t.Fatal("nil auth")
}
if resp.Auth.ClientToken == "" {
t.Fatal("empty client token")
}

client.SetToken(resp.Auth.ClientToken)

resp, err = client.Logical().Read("sys/internal/ui/resultant-acl")
if err != nil {
t.Fatal(err)
}
if resp == nil {
t.Fatal("nil response")
}
if resp.Data == nil {
t.Fatal("nil data")
}

exp := map[string]interface{}{
"exact_paths": map[string]interface{}{
"auth/token/lookup-self": map[string]interface{}{
"capabilities": []interface{}{
"read",
},
},
"auth/token/renew-self": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
"auth/token/revoke-self": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
"sys/capabilities-self": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
"sys/internal/ui/resultant-acl": map[string]interface{}{
"capabilities": []interface{}{
"read",
},
},
"sys/leases/lookup": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
"sys/leases/renew": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
"sys/renew": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
"sys/tools/hash": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
"sys/tools/random": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
"sys/wrapping/lookup": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
"sys/wrapping/unwrap": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
"sys/wrapping/wrap": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
},
"glob_paths": map[string]interface{}{
"cubbyhole/": map[string]interface{}{
"capabilities": []interface{}{
"create",
"delete",
"list",
"read",
"update",
},
},
"sys/tools/hash/": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
"sys/tools/random/": map[string]interface{}{
"capabilities": []interface{}{
"update",
},
},
},
"root": false,
}

if diff := deep.Equal(resp.Data, exp); diff != nil {
t.Fatal(diff)
}
}
7 changes: 7 additions & 0 deletions vault/policy_store.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,13 @@ path "sys/capabilities-self" {
capabilities = ["update"]
}
# Allow a token to look up its resultant ACL from all policies. This is useful
# for UIs. It is an internal path because the format may change at any time
# based on how the internal ACL features and capabilities change.
path "sys/internal/ui/resultant-acl" {
capabilities = ["read"]
}
# Allow a token to renew a lease via lease_id in the request body; old path for
# old clients, new path for newer
path "sys/renew" {
Expand Down
9 changes: 9 additions & 0 deletions vendor/github.com/go-test/deep/CHANGES.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions vendor/github.com/go-test/deep/LICENSE

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

51 changes: 51 additions & 0 deletions vendor/github.com/go-test/deep/README.md

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 3580853

Please sign in to comment.