open-vault/vault/policy.go

329 lines
8.7 KiB
Go
Raw Normal View History

2015-03-17 22:53:29 +00:00
package vault
import (
"errors"
2015-03-17 22:53:29 +00:00
"fmt"
"strings"
"time"
2015-03-17 22:53:29 +00:00
"github.com/hashicorp/errwrap"
2016-03-10 18:36:54 +00:00
"github.com/hashicorp/go-multierror"
2015-03-17 22:53:29 +00:00
"github.com/hashicorp/hcl"
2016-03-10 18:36:54 +00:00
"github.com/hashicorp/hcl/hcl/ast"
"github.com/hashicorp/vault/helper/parseutil"
"github.com/mitchellh/copystructure"
2015-03-17 22:53:29 +00:00
)
const (
2016-01-12 22:08:10 +00:00
DenyCapability = "deny"
CreateCapability = "create"
ReadCapability = "read"
UpdateCapability = "update"
DeleteCapability = "delete"
ListCapability = "list"
SudoCapability = "sudo"
RootCapability = "root"
// Backwards compatibility
OldDenyPathPolicy = "deny"
OldReadPathPolicy = "read"
OldWritePathPolicy = "write"
OldSudoPathPolicy = "sudo"
2015-03-17 22:53:29 +00:00
)
2016-01-12 22:08:10 +00:00
const (
DenyCapabilityInt uint32 = 1 << iota
CreateCapabilityInt
ReadCapabilityInt
UpdateCapabilityInt
DeleteCapabilityInt
ListCapabilityInt
SudoCapabilityInt
)
2017-10-23 18:59:37 +00:00
type PolicyType uint32
const (
PolicyTypeACL PolicyType = iota
PolicyTypeRGP
PolicyTypeEGP
// Triggers a lookup in the map to figure out if ACL or RGP
PolicyTypeToken
)
func (p PolicyType) String() string {
switch p {
case PolicyTypeACL:
return "acl"
case PolicyTypeRGP:
return "rgp"
case PolicyTypeEGP:
return "egp"
}
return ""
}
2016-01-12 22:08:10 +00:00
var (
cap2Int = map[string]uint32{
DenyCapability: DenyCapabilityInt,
CreateCapability: CreateCapabilityInt,
ReadCapability: ReadCapabilityInt,
UpdateCapability: UpdateCapabilityInt,
DeleteCapability: DeleteCapabilityInt,
ListCapability: ListCapabilityInt,
SudoCapability: SudoCapabilityInt,
}
)
2015-03-17 22:53:29 +00:00
// Policy is used to represent the policy specified by
// an ACL configuration.
type Policy struct {
2017-10-23 20:49:46 +00:00
Name string `hcl:"name"`
Paths []*PathRules `hcl:"-"`
Raw string
Type PolicyType
2015-03-17 22:53:29 +00:00
}
2017-10-23 18:59:37 +00:00
// PathRules represents a policy for a path in the namespace.
type PathRules struct {
Prefix string
Policy string
2017-10-23 18:59:37 +00:00
Permissions *ACLPermissions
Glob bool
2017-02-16 02:12:26 +00:00
Capabilities []string
// These keys are used at the top level to make the HCL nicer; we store in
2017-10-23 18:59:37 +00:00
// the ACLPermissions object though
MinWrappingTTLHCL interface{} `hcl:"min_wrapping_ttl"`
MaxWrappingTTLHCL interface{} `hcl:"max_wrapping_ttl"`
2017-02-16 02:12:26 +00:00
AllowedParametersHCL map[string][]interface{} `hcl:"allowed_parameters"`
DeniedParametersHCL map[string][]interface{} `hcl:"denied_parameters"`
}
2017-10-23 18:59:37 +00:00
type ACLPermissions struct {
CapabilitiesBitmap uint32
MinWrappingTTL time.Duration
MaxWrappingTTL time.Duration
2017-02-16 02:12:26 +00:00
AllowedParameters map[string][]interface{}
DeniedParameters map[string][]interface{}
}
2017-10-23 18:59:37 +00:00
func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
ret := &ACLPermissions{
CapabilitiesBitmap: p.CapabilitiesBitmap,
MinWrappingTTL: p.MinWrappingTTL,
MaxWrappingTTL: p.MaxWrappingTTL,
}
switch {
case p.AllowedParameters == nil:
case len(p.AllowedParameters) == 0:
ret.AllowedParameters = make(map[string][]interface{})
default:
clonedAllowed, err := copystructure.Copy(p.AllowedParameters)
if err != nil {
return nil, err
}
ret.AllowedParameters = clonedAllowed.(map[string][]interface{})
}
switch {
case p.DeniedParameters == nil:
case len(p.DeniedParameters) == 0:
ret.DeniedParameters = make(map[string][]interface{})
default:
clonedDenied, err := copystructure.Copy(p.DeniedParameters)
if err != nil {
return nil, err
}
ret.DeniedParameters = clonedDenied.(map[string][]interface{})
}
return ret, nil
}
2015-03-17 22:53:29 +00:00
// Parse is used to parse the specified ACL rules into an
// intermediary set of policies, before being compiled into
// the ACL
2017-10-23 18:59:37 +00:00
func ParseACLPolicy(rules string) (*Policy, error) {
2016-03-10 18:36:54 +00:00
// Parse the rules
root, err := hcl.Parse(rules)
if err != nil {
return nil, fmt.Errorf("Failed to parse policy: %s", err)
}
// Top-level item should be the object list
list, ok := root.Node.(*ast.ObjectList)
if !ok {
return nil, fmt.Errorf("Failed to parse policy: does not contain a root object")
}
// Check for invalid top-level keys
valid := []string{
"name",
"path",
}
if err := checkHCLKeys(list, valid); err != nil {
return nil, fmt.Errorf("Failed to parse policy: %s", err)
}
// Create the initial policy and store the raw text of the rules
2016-03-10 20:55:47 +00:00
var p Policy
p.Raw = rules
2017-10-23 18:59:37 +00:00
p.Type = PolicyTypeACL
2016-03-10 18:36:54 +00:00
if err := hcl.DecodeObject(&p, list); err != nil {
return nil, fmt.Errorf("Failed to parse policy: %s", err)
}
if o := list.Filter("path"); len(o.Items) > 0 {
2016-03-10 20:55:47 +00:00
if err := parsePaths(&p, o); err != nil {
2016-03-10 18:36:54 +00:00
return nil, fmt.Errorf("Failed to parse policy: %s", err)
}
2015-03-17 22:53:29 +00:00
}
2016-03-10 20:55:47 +00:00
return &p, nil
2016-03-10 18:36:54 +00:00
}
func parsePaths(result *Policy, list *ast.ObjectList) error {
2017-10-23 18:59:37 +00:00
paths := make([]*PathRules, 0, len(list.Items))
2016-03-10 18:36:54 +00:00
for _, item := range list.Items {
key := "path"
if len(item.Keys) > 0 {
2017-01-20 19:32:58 +00:00
key = item.Keys[0].Token.Value().(string)
2016-03-10 18:36:54 +00:00
}
valid := []string{
"policy",
"capabilities",
2017-02-16 02:12:26 +00:00
"allowed_parameters",
"denied_parameters",
"min_wrapping_ttl",
"max_wrapping_ttl",
2016-03-10 18:36:54 +00:00
}
if err := checkHCLKeys(item.Val, valid); err != nil {
return multierror.Prefix(err, fmt.Sprintf("path %q:", key))
}
2017-10-23 18:59:37 +00:00
var pc PathRules
2017-10-23 18:59:37 +00:00
// allocate memory so that DecodeObject can initialize the ACLPermissions struct
pc.Permissions = new(ACLPermissions)
2016-03-10 18:36:54 +00:00
pc.Prefix = key
if err := hcl.DecodeObject(&pc, item.Val); err != nil {
return multierror.Prefix(err, fmt.Sprintf("path %q:", key))
}
// Strip a leading '/' as paths in Vault start after the / in the API path
if len(pc.Prefix) > 0 && pc.Prefix[0] == '/' {
pc.Prefix = pc.Prefix[1:]
}
// Strip the glob character if found
if strings.HasSuffix(pc.Prefix, "*") {
pc.Prefix = strings.TrimSuffix(pc.Prefix, "*")
pc.Glob = true
}
// Map old-style policies into capabilities
2016-03-10 18:36:54 +00:00
if len(pc.Policy) > 0 {
switch pc.Policy {
case OldDenyPathPolicy:
pc.Capabilities = []string{DenyCapability}
case OldReadPathPolicy:
pc.Capabilities = append(pc.Capabilities, []string{ReadCapability, ListCapability}...)
case OldWritePathPolicy:
pc.Capabilities = append(pc.Capabilities, []string{CreateCapability, ReadCapability, UpdateCapability, DeleteCapability, ListCapability}...)
case OldSudoPathPolicy:
pc.Capabilities = append(pc.Capabilities, []string{CreateCapability, ReadCapability, UpdateCapability, DeleteCapability, ListCapability, SudoCapability}...)
default:
return fmt.Errorf("path %q: invalid policy '%s'", key, pc.Policy)
}
2015-03-17 22:53:29 +00:00
}
// Initialize the map
pc.Permissions.CapabilitiesBitmap = 0
for _, cap := range pc.Capabilities {
switch cap {
// If it's deny, don't include any other capability
case DenyCapability:
pc.Capabilities = []string{DenyCapability}
pc.Permissions.CapabilitiesBitmap = DenyCapabilityInt
goto PathFinished
case CreateCapability, ReadCapability, UpdateCapability, DeleteCapability, ListCapability, SudoCapability:
pc.Permissions.CapabilitiesBitmap |= cap2Int[cap]
default:
2016-03-10 18:36:54 +00:00
return fmt.Errorf("path %q: invalid capability '%s'", key, cap)
}
}
2017-02-17 01:50:10 +00:00
if pc.AllowedParametersHCL != nil {
pc.Permissions.AllowedParameters = make(map[string][]interface{}, len(pc.AllowedParametersHCL))
for key, val := range pc.AllowedParametersHCL {
pc.Permissions.AllowedParameters[strings.ToLower(key)] = val
}
}
if pc.DeniedParametersHCL != nil {
pc.Permissions.DeniedParameters = make(map[string][]interface{}, len(pc.DeniedParametersHCL))
for key, val := range pc.DeniedParametersHCL {
pc.Permissions.DeniedParameters[strings.ToLower(key)] = val
}
}
if pc.MinWrappingTTLHCL != nil {
dur, err := parseutil.ParseDurationSecond(pc.MinWrappingTTLHCL)
if err != nil {
return errwrap.Wrapf("error parsing min_wrapping_ttl: {{err}}", err)
}
pc.Permissions.MinWrappingTTL = dur
}
if pc.MaxWrappingTTLHCL != nil {
dur, err := parseutil.ParseDurationSecond(pc.MaxWrappingTTLHCL)
if err != nil {
return errwrap.Wrapf("error parsing max_wrapping_ttl: {{err}}", err)
}
pc.Permissions.MaxWrappingTTL = dur
}
if pc.Permissions.MinWrappingTTL != 0 &&
pc.Permissions.MaxWrappingTTL != 0 &&
pc.Permissions.MaxWrappingTTL < pc.Permissions.MinWrappingTTL {
return errors.New("max_wrapping_ttl cannot be less than min_wrapping_ttl")
}
2016-03-10 18:36:54 +00:00
2017-02-16 02:12:26 +00:00
PathFinished:
2016-03-10 18:36:54 +00:00
paths = append(paths, &pc)
2015-03-17 22:53:29 +00:00
}
2016-03-10 18:36:54 +00:00
result.Paths = paths
return nil
}
func checkHCLKeys(node ast.Node, valid []string) error {
var list *ast.ObjectList
switch n := node.(type) {
case *ast.ObjectList:
list = n
case *ast.ObjectType:
list = n.List
default:
return fmt.Errorf("cannot check HCL keys of type %T", n)
}
validMap := make(map[string]struct{}, len(valid))
for _, v := range valid {
validMap[v] = struct{}{}
}
var result error
for _, item := range list.Items {
key := item.Keys[0].Token.Value().(string)
if _, ok := validMap[key]; !ok {
result = multierror.Append(result, fmt.Errorf(
"invalid key '%s' on line %d", key, item.Assign.Line))
}
}
return result
2015-03-17 22:53:29 +00:00
}