Merge pull request #400 from hashicorp/f-glob
Change ACL semantics, use explicit glob and deny has highest precedence
This commit is contained in:
commit
70cd3d1206
118
vault/acl.go
118
vault/acl.go
|
@ -5,23 +5,34 @@ import (
|
|||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
// operationPolicyLevel is used to map each logical operation
|
||||
// into the minimum required permissions to allow the operation.
|
||||
var operationPolicyLevel = map[logical.Operation]int{
|
||||
logical.ReadOperation: pathPolicyLevel[PathPolicyRead],
|
||||
logical.WriteOperation: pathPolicyLevel[PathPolicyWrite],
|
||||
logical.DeleteOperation: pathPolicyLevel[PathPolicyWrite],
|
||||
logical.ListOperation: pathPolicyLevel[PathPolicyRead],
|
||||
logical.RevokeOperation: pathPolicyLevel[PathPolicyWrite],
|
||||
logical.RenewOperation: pathPolicyLevel[PathPolicyRead],
|
||||
logical.HelpOperation: pathPolicyLevel[PathPolicyDeny],
|
||||
}
|
||||
var (
|
||||
// Policy lists are used to restrict what is eligible for an operation
|
||||
anyPolicy = []string{PathPolicyDeny}
|
||||
readWriteSudo = []string{PathPolicyRead, PathPolicyWrite, PathPolicySudo}
|
||||
writeSudo = []string{PathPolicyWrite, PathPolicySudo}
|
||||
|
||||
// permittedPolicyLevel is used to map each logical operation
|
||||
// into the set of policies that allow the operation.
|
||||
permittedPolicyLevels = map[logical.Operation][]string{
|
||||
logical.ReadOperation: readWriteSudo,
|
||||
logical.WriteOperation: writeSudo,
|
||||
logical.DeleteOperation: writeSudo,
|
||||
logical.ListOperation: readWriteSudo,
|
||||
logical.HelpOperation: anyPolicy,
|
||||
logical.RevokeOperation: writeSudo,
|
||||
logical.RenewOperation: writeSudo,
|
||||
logical.RollbackOperation: writeSudo,
|
||||
}
|
||||
)
|
||||
|
||||
// ACL is used to wrap a set of policies to provide
|
||||
// an efficient interface for access control.
|
||||
type ACL struct {
|
||||
// pathRules contains the path policies
|
||||
pathRules *radix.Tree
|
||||
// exactRules contains the path policies that are exact
|
||||
exactRules *radix.Tree
|
||||
|
||||
// globRules contains the path policies that glob
|
||||
globRules *radix.Tree
|
||||
|
||||
// root is enabled if the "root" named policy is present.
|
||||
root bool
|
||||
|
@ -31,8 +42,9 @@ type ACL struct {
|
|||
func NewACL(policies []*Policy) (*ACL, error) {
|
||||
// Initialize
|
||||
a := &ACL{
|
||||
pathRules: radix.New(),
|
||||
root: false,
|
||||
exactRules: radix.New(),
|
||||
globRules: radix.New(),
|
||||
root: false,
|
||||
}
|
||||
|
||||
// Inject each policy
|
||||
|
@ -46,21 +58,23 @@ func NewACL(policies []*Policy) (*ACL, error) {
|
|||
a.root = true
|
||||
}
|
||||
for _, pp := range policy.Paths {
|
||||
// Convert to a policy level
|
||||
policyLevel := pathPolicyLevel[pp.Policy]
|
||||
// Check which tree to use
|
||||
tree := a.exactRules
|
||||
if pp.Glob {
|
||||
tree = a.globRules
|
||||
}
|
||||
|
||||
// Check for an existing policy
|
||||
raw, ok := a.pathRules.Get(pp.Prefix)
|
||||
raw, ok := tree.Get(pp.Prefix)
|
||||
if !ok {
|
||||
a.pathRules.Insert(pp.Prefix, policyLevel)
|
||||
tree.Insert(pp.Prefix, pp)
|
||||
continue
|
||||
}
|
||||
existing := raw.(int)
|
||||
existing := raw.(*PathPolicy)
|
||||
|
||||
// Check if this policy is a higher access level,
|
||||
// we want to store the highest permission permitted.
|
||||
if policyLevel > existing {
|
||||
a.pathRules.Insert(pp.Prefix, policyLevel)
|
||||
// Check if this policy is takes precedence
|
||||
if pp.TakesPrecedence(existing) {
|
||||
tree.Insert(pp.Prefix, pp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -74,18 +88,36 @@ func (a *ACL) AllowOperation(op logical.Operation, path string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// Find a matching rule, default deny if no match
|
||||
policyLevel := 0
|
||||
_, rule, ok := a.pathRules.LongestPrefix(path)
|
||||
if ok {
|
||||
policyLevel = rule.(int)
|
||||
// Check if any policy level allows this operation
|
||||
permitted := permittedPolicyLevels[op]
|
||||
if permitted[0] == PathPolicyDeny {
|
||||
return true
|
||||
}
|
||||
|
||||
// Convert the operation to a minimum required level
|
||||
requiredLevel := operationPolicyLevel[op]
|
||||
// Find an exact matching rule, look for glob if no match
|
||||
var policy *PathPolicy
|
||||
raw, ok := a.exactRules.Get(path)
|
||||
if ok {
|
||||
policy = raw.(*PathPolicy)
|
||||
goto CHECK
|
||||
}
|
||||
|
||||
// Find a glob rule, default deny if no match
|
||||
_, raw, ok = a.globRules.LongestPrefix(path)
|
||||
if !ok {
|
||||
return false
|
||||
} else {
|
||||
policy = raw.(*PathPolicy)
|
||||
}
|
||||
|
||||
CHECK:
|
||||
// Check if the minimum permissions are met
|
||||
return policyLevel >= requiredLevel
|
||||
for _, allowed := range permitted {
|
||||
if allowed == policy.Policy {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// RootPrivilege checks if the user has root level permission
|
||||
|
@ -97,13 +129,23 @@ func (a *ACL) RootPrivilege(path string) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
// Check the rules for a match
|
||||
_, rule, ok := a.pathRules.LongestPrefix(path)
|
||||
if !ok {
|
||||
return false
|
||||
// Find an exact matching rule, look for glob if no match
|
||||
var policy *PathPolicy
|
||||
raw, ok := a.exactRules.Get(path)
|
||||
if ok {
|
||||
policy = raw.(*PathPolicy)
|
||||
goto CHECK
|
||||
}
|
||||
|
||||
// Check the rules for a match, default deny if no match
|
||||
_, raw, ok = a.globRules.LongestPrefix(path)
|
||||
if !ok {
|
||||
return false
|
||||
} else {
|
||||
policy = raw.(*PathPolicy)
|
||||
}
|
||||
|
||||
CHECK:
|
||||
// Check the policy level
|
||||
policyLevel := rule.(int)
|
||||
return policyLevel == pathPolicyLevel[PathPolicySudo]
|
||||
return policy.Policy == PathPolicySudo
|
||||
}
|
||||
|
|
|
@ -105,13 +105,16 @@ func testLayeredACL(t *testing.T, acl *ACL) {
|
|||
|
||||
{logical.DeleteOperation, "stage/foo", true},
|
||||
{logical.WriteOperation, "stage/aws/foo", false},
|
||||
{logical.WriteOperation, "stage/aws/policy/foo", true},
|
||||
{logical.WriteOperation, "stage/aws/policy/foo", false},
|
||||
|
||||
{logical.DeleteOperation, "prod/foo", true},
|
||||
{logical.WriteOperation, "prod/foo", true},
|
||||
{logical.ReadOperation, "prod/foo", true},
|
||||
{logical.ListOperation, "prod/foo", true},
|
||||
{logical.ReadOperation, "prod/aws/foo", false},
|
||||
|
||||
{logical.ReadOperation, "sys/status", false},
|
||||
{logical.WriteOperation, "sys/seal", true},
|
||||
}
|
||||
|
||||
for _, tc := range tcases {
|
||||
|
@ -124,35 +127,41 @@ func testLayeredACL(t *testing.T, acl *ACL) {
|
|||
|
||||
var aclPolicy = `
|
||||
name = "dev"
|
||||
path "dev/" {
|
||||
path "dev/*" {
|
||||
policy = "sudo"
|
||||
}
|
||||
path "stage/" {
|
||||
path "stage/*" {
|
||||
policy = "write"
|
||||
}
|
||||
path "stage/aws/" {
|
||||
path "stage/aws/*" {
|
||||
policy = "read"
|
||||
}
|
||||
path "stage/aws/policy/" {
|
||||
path "stage/aws/policy/*" {
|
||||
policy = "sudo"
|
||||
}
|
||||
path "prod/" {
|
||||
path "prod/*" {
|
||||
policy = "read"
|
||||
}
|
||||
path "prod/aws/" {
|
||||
path "prod/aws/*" {
|
||||
policy = "deny"
|
||||
}
|
||||
path "sys/*" {
|
||||
policy = "deny"
|
||||
}
|
||||
`
|
||||
|
||||
var aclPolicy2 = `
|
||||
name = "ops"
|
||||
path "dev/hide/" {
|
||||
path "dev/hide/*" {
|
||||
policy = "deny"
|
||||
}
|
||||
path "stage/aws/policy/" {
|
||||
path "stage/aws/policy/*" {
|
||||
policy = "deny"
|
||||
}
|
||||
path "prod/" {
|
||||
path "prod/*" {
|
||||
policy = "write"
|
||||
}
|
||||
path "sys/seal" {
|
||||
policy = "write"
|
||||
}
|
||||
`
|
||||
|
|
|
@ -636,7 +636,7 @@ func TestCore_HandleRequest_PermissionAllowed(t *testing.T) {
|
|||
Operation: logical.WriteOperation,
|
||||
Path: "sys/policy/test",
|
||||
Data: map[string]interface{}{
|
||||
"rules": `path "secret/" { policy = "write" }`,
|
||||
"rules": `path "secret/*" { policy = "write" }`,
|
||||
},
|
||||
ClientToken: root,
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package vault
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/hashicorp/hcl"
|
||||
)
|
||||
|
@ -13,15 +14,6 @@ const (
|
|||
PathPolicySudo = "sudo"
|
||||
)
|
||||
|
||||
var (
|
||||
pathPolicyLevel = map[string]int{
|
||||
PathPolicyDeny: 0,
|
||||
PathPolicyRead: 1,
|
||||
PathPolicyWrite: 2,
|
||||
PathPolicySudo: 3,
|
||||
}
|
||||
)
|
||||
|
||||
// Policy is used to represent the policy specified by
|
||||
// an ACL configuration.
|
||||
type Policy struct {
|
||||
|
@ -34,6 +26,47 @@ type Policy struct {
|
|||
type PathPolicy struct {
|
||||
Prefix string `hcl:",key"`
|
||||
Policy string
|
||||
Glob bool
|
||||
}
|
||||
|
||||
// TakesPrecedence is used when multiple policies
|
||||
// collide on a path to determine which policy takes
|
||||
// precendence.
|
||||
func (p *PathPolicy) TakesPrecedence(other *PathPolicy) bool {
|
||||
// Handle the full merge matrix
|
||||
switch p.Policy {
|
||||
case PathPolicyDeny:
|
||||
// Deny always takes precendence
|
||||
return true
|
||||
|
||||
case PathPolicyRead:
|
||||
// Read never takes precedence
|
||||
return false
|
||||
|
||||
case PathPolicyWrite:
|
||||
switch other.Policy {
|
||||
case PathPolicyRead:
|
||||
return true
|
||||
case PathPolicyDeny, PathPolicyWrite, PathPolicySudo:
|
||||
return false
|
||||
default:
|
||||
panic("missing case")
|
||||
}
|
||||
|
||||
case PathPolicySudo:
|
||||
switch other.Policy {
|
||||
case PathPolicyRead, PathPolicyWrite:
|
||||
return true
|
||||
case PathPolicyDeny, PathPolicySudo:
|
||||
return false
|
||||
default:
|
||||
panic("missing case")
|
||||
}
|
||||
|
||||
default:
|
||||
panic("missing case")
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Parse is used to parse the specified ACL rules into an
|
||||
|
@ -48,6 +81,13 @@ func Parse(rules string) (*Policy, error) {
|
|||
|
||||
// Validate the path policy
|
||||
for _, pp := range p.Paths {
|
||||
// Strip the glob character if found
|
||||
if strings.HasSuffix(pp.Prefix, "*") {
|
||||
pp.Prefix = strings.TrimSuffix(pp.Prefix, "*")
|
||||
pp.Glob = true
|
||||
}
|
||||
|
||||
// Check the policy is valid
|
||||
switch pp.Policy {
|
||||
case PathPolicyDeny:
|
||||
case PathPolicyRead:
|
||||
|
|
|
@ -25,6 +25,12 @@ type PolicyStore struct {
|
|||
lru *lru.Cache
|
||||
}
|
||||
|
||||
// PolicyEntry is used to store a policy by name
|
||||
type PolicyEntry struct {
|
||||
Version int
|
||||
Raw string
|
||||
}
|
||||
|
||||
// NewPolicyStore creates a new PolicyStore that is backed
|
||||
// using a given view. It used used to durable store and manage named policy.
|
||||
func NewPolicyStore(view *BarrierView) *PolicyStore {
|
||||
|
@ -64,9 +70,13 @@ func (ps *PolicyStore) SetPolicy(p *Policy) error {
|
|||
return fmt.Errorf("policy name missing")
|
||||
}
|
||||
|
||||
entry := &logical.StorageEntry{
|
||||
Key: p.Name,
|
||||
Value: []byte(p.Raw),
|
||||
// Create the entry
|
||||
entry, err := logical.StorageEntryJSON(p.Name, &PolicyEntry{
|
||||
Version: 2,
|
||||
Raw: p.Raw,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create entry: %v", err)
|
||||
}
|
||||
if err := ps.view.Put(entry); err != nil {
|
||||
return fmt.Errorf("failed to persist policy: %v", err)
|
||||
|
@ -101,16 +111,37 @@ func (ps *PolicyStore) GetPolicy(name string) (*Policy, error) {
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
// Parse into a policy object
|
||||
p, err := Parse(string(out.Value))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse policy: %v", err)
|
||||
// In Vault 0.1.X we stored the raw policy, but in
|
||||
// Vault 0.2 we switch to the PolicyEntry
|
||||
policyEntry := new(PolicyEntry)
|
||||
var policy *Policy
|
||||
if err := out.DecodeJSON(policyEntry); err == nil {
|
||||
// Parse normally
|
||||
p, err := Parse(policyEntry.Raw)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse policy: %v", err)
|
||||
}
|
||||
p.Name = name
|
||||
policy = p
|
||||
|
||||
} else {
|
||||
// On error, attempt to use V1 parsing
|
||||
p, err := Parse(string(out.Value))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse policy: %v", err)
|
||||
}
|
||||
p.Name = name
|
||||
|
||||
// V1 used implicit glob, we need to do a fix-up
|
||||
for _, pp := range p.Paths {
|
||||
pp.Glob = true
|
||||
}
|
||||
policy = p
|
||||
}
|
||||
p.Name = name
|
||||
|
||||
// Update the LRU cache
|
||||
ps.lru.Add(p.Name, p)
|
||||
return p, nil
|
||||
ps.lru.Add(name, policy)
|
||||
return policy, nil
|
||||
}
|
||||
|
||||
// ListPolicies is used to list the available policies
|
||||
|
|
|
@ -3,6 +3,8 @@ package vault
|
|||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/hashicorp/vault/logical"
|
||||
)
|
||||
|
||||
func mockPolicyStore(t *testing.T) *PolicyStore {
|
||||
|
@ -128,3 +130,26 @@ func TestPolicyStore_ACL(t *testing.T) {
|
|||
}
|
||||
testLayeredACL(t, acl)
|
||||
}
|
||||
|
||||
func TestPolicyStore_v1Upgrade(t *testing.T) {
|
||||
ps := mockPolicyStore(t)
|
||||
|
||||
// Put a V1 record
|
||||
raw := `path "foo" { policy = "read" }`
|
||||
ps.view.Put(&logical.StorageEntry{"old", []byte(raw)})
|
||||
|
||||
// Do a read
|
||||
p, err := ps.GetPolicy("old")
|
||||
if err != nil {
|
||||
t.Fatalf("err: %v", err)
|
||||
}
|
||||
|
||||
if p == nil || len(p.Paths) != 1 {
|
||||
t.Fatalf("bad policy: %#v", p)
|
||||
}
|
||||
|
||||
// Check that glob is enabled
|
||||
if !p.Paths[0].Glob {
|
||||
t.Fatalf("should enable glob")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,42 @@ import (
|
|||
"testing"
|
||||
)
|
||||
|
||||
func TestPolicy_TakesPrecedence(t *testing.T) {
|
||||
type tcase struct {
|
||||
a, b string
|
||||
precedence bool
|
||||
}
|
||||
tests := []tcase{
|
||||
tcase{PathPolicyDeny, PathPolicyDeny, true},
|
||||
tcase{PathPolicyDeny, PathPolicyRead, true},
|
||||
tcase{PathPolicyDeny, PathPolicyWrite, true},
|
||||
tcase{PathPolicyDeny, PathPolicySudo, true},
|
||||
|
||||
tcase{PathPolicyRead, PathPolicyDeny, false},
|
||||
tcase{PathPolicyRead, PathPolicyRead, false},
|
||||
tcase{PathPolicyRead, PathPolicyWrite, false},
|
||||
tcase{PathPolicyRead, PathPolicySudo, false},
|
||||
|
||||
tcase{PathPolicyWrite, PathPolicyDeny, false},
|
||||
tcase{PathPolicyWrite, PathPolicyRead, true},
|
||||
tcase{PathPolicyWrite, PathPolicyWrite, false},
|
||||
tcase{PathPolicyWrite, PathPolicySudo, false},
|
||||
|
||||
tcase{PathPolicySudo, PathPolicyDeny, false},
|
||||
tcase{PathPolicySudo, PathPolicyRead, true},
|
||||
tcase{PathPolicySudo, PathPolicyWrite, true},
|
||||
tcase{PathPolicySudo, PathPolicySudo, false},
|
||||
}
|
||||
for idx, test := range tests {
|
||||
a := &PathPolicy{Policy: test.a}
|
||||
b := &PathPolicy{Policy: test.b}
|
||||
if out := a.TakesPrecedence(b); out != test.precedence {
|
||||
t.Fatalf("bad: idx %d expect: %v out: %v",
|
||||
idx, test.precedence, out)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolicy_Parse(t *testing.T) {
|
||||
p, err := Parse(rawPolicy)
|
||||
if err != nil {
|
||||
|
@ -16,9 +52,9 @@ func TestPolicy_Parse(t *testing.T) {
|
|||
}
|
||||
|
||||
expect := []*PathPolicy{
|
||||
&PathPolicy{"", "deny"},
|
||||
&PathPolicy{"stage/", "sudo"},
|
||||
&PathPolicy{"prod/", "read"},
|
||||
&PathPolicy{"", "deny", true},
|
||||
&PathPolicy{"stage/", "sudo", true},
|
||||
&PathPolicy{"prod/version", "read", false},
|
||||
}
|
||||
if !reflect.DeepEqual(p.Paths, expect) {
|
||||
t.Fatalf("bad: %#v", p)
|
||||
|
@ -30,17 +66,17 @@ var rawPolicy = `
|
|||
name = "dev"
|
||||
|
||||
# Deny all paths by default
|
||||
path "" {
|
||||
path "*" {
|
||||
policy = "deny"
|
||||
}
|
||||
|
||||
# Allow full access to staging
|
||||
path "stage/" {
|
||||
path "stage/*" {
|
||||
policy = "sudo"
|
||||
}
|
||||
|
||||
# Limited read privilege to production
|
||||
path "prod/" {
|
||||
path "prod/version" {
|
||||
policy = "read"
|
||||
}
|
||||
`
|
||||
|
|
|
@ -18,41 +18,47 @@ that describe what parts of Vault a user is allowed to access. An example
|
|||
of a policy is shown below:
|
||||
|
||||
```javascript
|
||||
path "sys" {
|
||||
path "sys/*" {
|
||||
policy = "deny"
|
||||
}
|
||||
|
||||
path "secret" {
|
||||
path "secret/*" {
|
||||
policy = "write"
|
||||
}
|
||||
|
||||
path "secret/foo" {
|
||||
policy = "read"
|
||||
}
|
||||
|
||||
path "secret/super-secret" {
|
||||
policy = "deny"
|
||||
}
|
||||
```
|
||||
|
||||
Policies use prefix-based routing to apply rules. They are deny by default,
|
||||
so if a path isn't explicitly given, Vault will reject any access to it.
|
||||
Policies use path based matching to apply rules. A policy may be an exact
|
||||
match, or might be a glob pattern which uses a prefix. The default policy
|
||||
is always deny so if a path isn't explicitly allowed, Vault will reject access to it.
|
||||
This works well due to Vault's architecture of being like a filesystem:
|
||||
everything has a path associated with it, including the core configuration
|
||||
mechanism under "sys".
|
||||
|
||||
~> Policy paths are matched using a longest-prefix match, which is the most
|
||||
specific defined policy. This means if you define a policy for `"secret/foo"`,
|
||||
the policy would also match `"secret/foobar"`.
|
||||
~> Policy paths are matched using the most specific defined policy. This may
|
||||
be an exact match or the longest-prefix match of a glob. This means if you
|
||||
define a policy for `"secret/foo*"`, the policy would also match `"secret/foobar"`.
|
||||
The glob character is only supported at the end of the path specification.
|
||||
|
||||
## Policies
|
||||
|
||||
Allowed policies for a path are:
|
||||
|
||||
* `deny` - No access allowed. Highest precedence.
|
||||
|
||||
* `sudo` - Read, write, and root access to a path.
|
||||
|
||||
* `write` - Read, write access to a path.
|
||||
|
||||
* `read` - Read-only access to a path.
|
||||
|
||||
* `deny` - No access allowed.
|
||||
|
||||
* `sudo` - Read, write, and root access to a path.
|
||||
|
||||
The only non-obvious policy is "sudo". Some routes within Vault and mounted
|
||||
backends are marked as _root_ paths. Clients aren't allowed to access root
|
||||
paths unless they are a root user (have the special policy "root") or
|
||||
|
@ -95,3 +101,33 @@ If an _existing_ policy is modified, the modifications propagate
|
|||
to all associated users instantly. The above paragraph is more specifically
|
||||
stating that you can't add new or remove policies associated with an
|
||||
active identity.
|
||||
|
||||
## Changes from 0.1
|
||||
|
||||
In Vault versions prior to 0.2, the ACL policy language had a slightly
|
||||
different specification and semantics. The current specification requires
|
||||
that glob behavior explicitly be specified by adding the `*` character to
|
||||
the end of a path. Previously, all paths were glob based matches and no
|
||||
exact match could be specified.
|
||||
|
||||
The other change is that deny had the lowest precedence. This meant if there
|
||||
were two policies being merged (e.g. "ops" and "prod") and they had a conflicting
|
||||
policy like:
|
||||
|
||||
```
|
||||
path "sys/seal" {
|
||||
policy = "deny"
|
||||
}
|
||||
|
||||
path "sys/seal" {
|
||||
policy = "read"
|
||||
}
|
||||
```
|
||||
|
||||
The merge would previously give the "read" higher precedence. The current
|
||||
version of Vault prioritizes the explicit deny, so that the "deny" would
|
||||
take precedence.
|
||||
|
||||
To make all Vault 0.1 policies compatible with Vault 0.2, the explicit
|
||||
glob character must be added to all the path prefixes.
|
||||
|
||||
|
|
|
@ -110,8 +110,8 @@ a level of access granted to a path in Vault. When the policies are merged (if m
|
|||
policies are associated with a client), the highest access level permitted is used.
|
||||
For example, if the "engineering" policy permits read/write access to the "eng/" path,
|
||||
and the "ops" policy permits read access to the "ops/" path, then the user gets the
|
||||
union of those. Policy is matched using a longest-prefix match, which is the most
|
||||
specific defined policy.
|
||||
union of those. Policy is matched using the most specific defined policy, which may be
|
||||
an exact match or the longest-prefix match glob pattern.
|
||||
|
||||
Certain operations are only permitted by "root" users, which is a distinguished
|
||||
policy built into Vault. This is similar to the concept of a root user on a Unix system
|
||||
|
|
|
@ -30,11 +30,7 @@ format that is also JSON-compatible, so you can use JSON as well. An example
|
|||
policy is shown below:
|
||||
|
||||
```javascript
|
||||
path "sys" {
|
||||
policy = "deny"
|
||||
}
|
||||
|
||||
path "secret" {
|
||||
path "secret/*" {
|
||||
policy = "write"
|
||||
}
|
||||
|
||||
|
@ -43,14 +39,16 @@ path "secret/foo" {
|
|||
}
|
||||
```
|
||||
|
||||
The policy format uses a longest matching prefix system on the API path
|
||||
to determine access control. Since everything in Vault must be accessed
|
||||
via the API, this gives strict control over every aspect of Vault, including
|
||||
mounting backends, authenticating, as well as secret access.
|
||||
The policy format uses a prefix matching system on the API path
|
||||
to determine access control. The most specific defined policy is used,
|
||||
either an exact match or the longest-prefix glob match. Since everything
|
||||
in Vault must be accessed via the API, this gives strict control over every
|
||||
aspect of Vault, including mounting backends, authenticating, as well as secret access.
|
||||
|
||||
In the policy above, a user could write any secret to `secret/`, except
|
||||
to `secret/foo`, where only read access is allowed. Policies default to
|
||||
deny, so any access to an unspecified path is not allowed.
|
||||
deny, so any access to an unspecified path is not allowed. The policy
|
||||
langauge changed slightly in Vault 0.2, [see this page for details](/docs/concepts/policies.html).
|
||||
|
||||
Save the above policy as `acl.hcl`.
|
||||
|
||||
|
|
Loading…
Reference in New Issue