Resultant acl (#4386)

This commit is contained in:
Jeff Mitchell 2018-04-20 14:19:04 -04:00 committed by GitHub
parent 4881a53eb0
commit 640b30ff7b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 704 additions and 0 deletions

View File

@ -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]),
},
},
}
@ -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 += "/"
@ -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.",
"",
},
}

View File

@ -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"
@ -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)
}
}

View File

@ -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" {

9
vendor/github.com/go-test/deep/CHANGES.md generated vendored Normal file
View File

@ -0,0 +1,9 @@
# go-test/deep Changelog
## v1.0.1 released 2018-01-28
* Fixed #12: Arrays are not properly compared (samlitowitz)
## v1.0.0 releaesd 2017-10-27
* First release

21
vendor/github.com/go-test/deep/LICENSE generated vendored Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright 2015-2017 Daniel Nichter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

51
vendor/github.com/go-test/deep/README.md generated vendored Normal file
View File

@ -0,0 +1,51 @@
# Deep Variable Equality for Humans
[![Go Report Card](https://goreportcard.com/badge/github.com/go-test/deep)](https://goreportcard.com/report/github.com/go-test/deep) [![Build Status](https://travis-ci.org/go-test/deep.svg?branch=master)](https://travis-ci.org/go-test/deep) [![Coverage Status](https://coveralls.io/repos/github/go-test/deep/badge.svg?branch=master)](https://coveralls.io/github/go-test/deep?branch=master) [![GoDoc](https://godoc.org/github.com/go-test/deep?status.svg)](https://godoc.org/github.com/go-test/deep)
This package provides a single function: `deep.Equal`. It's like [reflect.DeepEqual](http://golang.org/pkg/reflect/#DeepEqual) but much friendlier to humans (or any sentient being) for two reason:
* `deep.Equal` returns a list of differences
* `deep.Equal` does not compare unexported fields (by default)
`reflect.DeepEqual` is good (like all things Golang!), but it's a game of [Hunt the Wumpus](https://en.wikipedia.org/wiki/Hunt_the_Wumpus). For large maps, slices, and structs, finding the difference is difficult.
`deep.Equal` doesn't play games with you, it lists the differences:
```go
package main_test
import (
"testing"
"github.com/go-test/deep"
)
type T struct {
Name string
Numbers []float64
}
func TestDeepEqual(t *testing.T) {
// Can you spot the difference?
t1 := T{
Name: "Isabella",
Numbers: []float64{1.13459, 2.29343, 3.010100010},
}
t2 := T{
Name: "Isabella",
Numbers: []float64{1.13459, 2.29843, 3.010100010},
}
if diff := deep.Equal(t1, t2); diff != nil {
t.Error(diff)
}
}
```
```
$ go test
--- FAIL: TestDeepEqual (0.00s)
main_test.go:25: [Numbers.slice[1]: 2.29343 != 2.29843]
```
The difference is in `Numbers.slice[1]`: the two values aren't equal using Go `==`.

352
vendor/github.com/go-test/deep/deep.go generated vendored Normal file
View File

@ -0,0 +1,352 @@
// Package deep provides function deep.Equal which is like reflect.DeepEqual but
// returns a list of differences. This is helpful when comparing complex types
// like structures and maps.
package deep
import (
"errors"
"fmt"
"log"
"reflect"
"strings"
)
var (
// FloatPrecision is the number of decimal places to round float values
// to when comparing.
FloatPrecision = 10
// MaxDiff specifies the maximum number of differences to return.
MaxDiff = 10
// MaxDepth specifies the maximum levels of a struct to recurse into.
MaxDepth = 10
// LogErrors causes errors to be logged to STDERR when true.
LogErrors = false
// CompareUnexportedFields causes unexported struct fields, like s in
// T{s int}, to be comparsed when true.
CompareUnexportedFields = false
)
var (
// ErrMaxRecursion is logged when MaxDepth is reached.
ErrMaxRecursion = errors.New("recursed to MaxDepth")
// ErrTypeMismatch is logged when Equal passed two different types of values.
ErrTypeMismatch = errors.New("variables are different reflect.Type")
// ErrNotHandled is logged when a primitive Go kind is not handled.
ErrNotHandled = errors.New("cannot compare the reflect.Kind")
)
type cmp struct {
diff []string
buff []string
floatFormat string
}
var errorType = reflect.TypeOf((*error)(nil)).Elem()
// Equal compares variables a and b, recursing into their structure up to
// MaxDepth levels deep, and returns a list of differences, or nil if there are
// none. Some differences may not be found if an error is also returned.
//
// If a type has an Equal method, like time.Equal, it is called to check for
// equality.
func Equal(a, b interface{}) []string {
aVal := reflect.ValueOf(a)
bVal := reflect.ValueOf(b)
c := &cmp{
diff: []string{},
buff: []string{},
floatFormat: fmt.Sprintf("%%.%df", FloatPrecision),
}
if a == nil && b == nil {
return nil
} else if a == nil && b != nil {
c.saveDiff(b, "<nil pointer>")
} else if a != nil && b == nil {
c.saveDiff(a, "<nil pointer>")
}
if len(c.diff) > 0 {
return c.diff
}
c.equals(aVal, bVal, 0)
if len(c.diff) > 0 {
return c.diff // diffs
}
return nil // no diffs
}
func (c *cmp) equals(a, b reflect.Value, level int) {
if level > MaxDepth {
logError(ErrMaxRecursion)
return
}
// Check if one value is nil, e.g. T{x: *X} and T.x is nil
if !a.IsValid() || !b.IsValid() {
if a.IsValid() && !b.IsValid() {
c.saveDiff(a.Type(), "<nil pointer>")
} else if !a.IsValid() && b.IsValid() {
c.saveDiff("<nil pointer>", b.Type())
}
return
}
// If differenet types, they can't be equal
aType := a.Type()
bType := b.Type()
if aType != bType {
c.saveDiff(aType, bType)
logError(ErrTypeMismatch)
return
}
// Primitive https://golang.org/pkg/reflect/#Kind
aKind := a.Kind()
bKind := b.Kind()
// If both types implement the error interface, compare the error strings.
// This must be done before dereferencing because the interface is on a
// pointer receiver.
if aType.Implements(errorType) && bType.Implements(errorType) {
if a.Elem().IsValid() && b.Elem().IsValid() { // both err != nil
aString := a.MethodByName("Error").Call(nil)[0].String()
bString := b.MethodByName("Error").Call(nil)[0].String()
if aString != bString {
c.saveDiff(aString, bString)
}
return
}
}
// Dereference pointers and interface{}
if aElem, bElem := (aKind == reflect.Ptr || aKind == reflect.Interface),
(bKind == reflect.Ptr || bKind == reflect.Interface); aElem || bElem {
if aElem {
a = a.Elem()
}
if bElem {
b = b.Elem()
}
c.equals(a, b, level+1)
return
}
// Types with an Equal(), like time.Time.
eqFunc := a.MethodByName("Equal")
if eqFunc.IsValid() {
retVals := eqFunc.Call([]reflect.Value{b})
if !retVals[0].Bool() {
c.saveDiff(a, b)
}
return
}
switch aKind {
/////////////////////////////////////////////////////////////////////
// Iterable kinds
/////////////////////////////////////////////////////////////////////
case reflect.Struct:
/*
The variables are structs like:
type T struct {
FirstName string
LastName string
}
Type = <pkg>.T, Kind = reflect.Struct
Iterate through the fields (FirstName, LastName), recurse into their values.
*/
for i := 0; i < a.NumField(); i++ {
if aType.Field(i).PkgPath != "" && !CompareUnexportedFields {
continue // skip unexported field, e.g. s in type T struct {s string}
}
c.push(aType.Field(i).Name) // push field name to buff
// Get the Value for each field, e.g. FirstName has Type = string,
// Kind = reflect.String.
af := a.Field(i)
bf := b.Field(i)
// Recurse to compare the field values
c.equals(af, bf, level+1)
c.pop() // pop field name from buff
if len(c.diff) >= MaxDiff {
break
}
}
case reflect.Map:
/*
The variables are maps like:
map[string]int{
"foo": 1,
"bar": 2,
}
Type = map[string]int, Kind = reflect.Map
Or:
type T map[string]int{}
Type = <pkg>.T, Kind = reflect.Map
Iterate through the map keys (foo, bar), recurse into their values.
*/
if a.IsNil() || b.IsNil() {
if a.IsNil() && !b.IsNil() {
c.saveDiff("<nil map>", b)
} else if !a.IsNil() && b.IsNil() {
c.saveDiff(a, "<nil map>")
}
return
}
if a.Pointer() == b.Pointer() {
return
}
for _, key := range a.MapKeys() {
c.push(fmt.Sprintf("map[%s]", key))
aVal := a.MapIndex(key)
bVal := b.MapIndex(key)
if bVal.IsValid() {
c.equals(aVal, bVal, level+1)
} else {
c.saveDiff(aVal, "<does not have key>")
}
c.pop()
if len(c.diff) >= MaxDiff {
return
}
}
for _, key := range b.MapKeys() {
if aVal := a.MapIndex(key); aVal.IsValid() {
continue
}
c.push(fmt.Sprintf("map[%s]", key))
c.saveDiff("<does not have key>", b.MapIndex(key))
c.pop()
if len(c.diff) >= MaxDiff {
return
}
}
case reflect.Array:
n := a.Len()
for i := 0; i < n; i++ {
c.push(fmt.Sprintf("array[%d]", i))
c.equals(a.Index(i), b.Index(i), level+1)
c.pop()
if len(c.diff) >= MaxDiff {
break
}
}
case reflect.Slice:
if a.IsNil() || b.IsNil() {
if a.IsNil() && !b.IsNil() {
c.saveDiff("<nil slice>", b)
} else if !a.IsNil() && b.IsNil() {
c.saveDiff(a, "<nil slice>")
}
return
}
if a.Pointer() == b.Pointer() {
return
}
aLen := a.Len()
bLen := b.Len()
n := aLen
if bLen > aLen {
n = bLen
}
for i := 0; i < n; i++ {
c.push(fmt.Sprintf("slice[%d]", i))
if i < aLen && i < bLen {
c.equals(a.Index(i), b.Index(i), level+1)
} else if i < aLen {
c.saveDiff(a.Index(i), "<no value>")
} else {
c.saveDiff("<no value>", b.Index(i))
}
c.pop()
if len(c.diff) >= MaxDiff {
break
}
}
/////////////////////////////////////////////////////////////////////
// Primitive kinds
/////////////////////////////////////////////////////////////////////
case reflect.Float32, reflect.Float64:
// Avoid 0.04147685731961082 != 0.041476857319611
// 6 decimal places is close enough
aval := fmt.Sprintf(c.floatFormat, a.Float())
bval := fmt.Sprintf(c.floatFormat, b.Float())
if aval != bval {
c.saveDiff(a.Float(), b.Float())
}
case reflect.Bool:
if a.Bool() != b.Bool() {
c.saveDiff(a.Bool(), b.Bool())
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if a.Int() != b.Int() {
c.saveDiff(a.Int(), b.Int())
}
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if a.Uint() != b.Uint() {
c.saveDiff(a.Uint(), b.Uint())
}
case reflect.String:
if a.String() != b.String() {
c.saveDiff(a.String(), b.String())
}
default:
logError(ErrNotHandled)
}
}
func (c *cmp) push(name string) {
c.buff = append(c.buff, name)
}
func (c *cmp) pop() {
if len(c.buff) > 0 {
c.buff = c.buff[0 : len(c.buff)-1]
}
}
func (c *cmp) saveDiff(aval, bval interface{}) {
if len(c.buff) > 0 {
varName := strings.Join(c.buff, ".")
c.diff = append(c.diff, fmt.Sprintf("%s: %v != %v", varName, aval, bval))
} else {
c.diff = append(c.diff, fmt.Sprintf("%v != %v", aval, bval))
}
}
func logError(err error) {
if LogErrors {
log.Println(err)
}
}

6
vendor/vendor.json vendored
View File

@ -924,6 +924,12 @@
"revision": "bc14601d1bd56421dd60f561e6052c9ed77f9daf",
"revisionTime": "2018-01-25T05:47:45Z"
},
{
"checksumSHA1": "dni8Puy96l7QbQlJwLtrZvM5rxM=",
"path": "github.com/go-test/deep",
"revision": "6592d9cc0a499ad2d5f574fde80a2b5c5cc3b4f5",
"revisionTime": "2018-01-28T22:55:04Z"
},
{
"checksumSHA1": "989p8YX+h2GxOFWhUMvh1O/K3rc=",
"path": "github.com/gocql/gocql",