224 lines
5.7 KiB
Go
224 lines
5.7 KiB
Go
package vault
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/hashicorp/go-multierror"
|
|
"github.com/hashicorp/hcl"
|
|
"github.com/hashicorp/hcl/hcl/ast"
|
|
)
|
|
|
|
const (
|
|
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"
|
|
)
|
|
|
|
const (
|
|
DenyCapabilityInt uint32 = 1 << iota
|
|
CreateCapabilityInt
|
|
ReadCapabilityInt
|
|
UpdateCapabilityInt
|
|
DeleteCapabilityInt
|
|
ListCapabilityInt
|
|
SudoCapabilityInt
|
|
)
|
|
|
|
var (
|
|
cap2Int = map[string]uint32{
|
|
DenyCapability: DenyCapabilityInt,
|
|
CreateCapability: CreateCapabilityInt,
|
|
ReadCapability: ReadCapabilityInt,
|
|
UpdateCapability: UpdateCapabilityInt,
|
|
DeleteCapability: DeleteCapabilityInt,
|
|
ListCapability: ListCapabilityInt,
|
|
SudoCapability: SudoCapabilityInt,
|
|
}
|
|
)
|
|
|
|
// Policy is used to represent the policy specified by
|
|
// an ACL configuration.
|
|
type Policy struct {
|
|
Name string `hcl:"name"`
|
|
Paths []*PathCapabilities `hcl:"-"`
|
|
Raw string
|
|
}
|
|
|
|
/*
|
|
*/
|
|
// PathCapabilities represents a policy for a path in the namespace.
|
|
type PathCapabilities struct {
|
|
Prefix string
|
|
Policy string
|
|
Capabilities []string
|
|
Permissions *Permissions
|
|
Glob bool
|
|
}
|
|
|
|
type Permissions struct {
|
|
CapabilitiesBitmap uint32
|
|
AllowedParameters map[string][]interface{}
|
|
DeniedParameters map[string][]interface{}
|
|
}
|
|
|
|
// Parse is used to parse the specified ACL rules into an
|
|
// intermediary set of policies, before being compiled into
|
|
// the ACL
|
|
func Parse(rules string) (*Policy, error) {
|
|
// 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
|
|
var p Policy
|
|
p.Raw = rules
|
|
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 {
|
|
if err := parsePaths(&p, o); err != nil {
|
|
return nil, fmt.Errorf("Failed to parse policy: %s", err)
|
|
}
|
|
}
|
|
|
|
return &p, nil
|
|
}
|
|
|
|
func parsePaths(result *Policy, list *ast.ObjectList) error {
|
|
// specifically how can we access the key value pairs for
|
|
// permissions
|
|
paths := make([]*PathCapabilities, 0, len(list.Items))
|
|
for _, item := range list.Items {
|
|
key := "path"
|
|
if len(item.Keys) > 0 {
|
|
key = item.Keys[0].Token.Value().(string) // "secret/foo"
|
|
}
|
|
valid := []string{
|
|
"policy",
|
|
"capabilities",
|
|
"permissions", // added here to validate
|
|
}
|
|
if err := checkHCLKeys(item.Val, valid); err != nil {
|
|
return multierror.Prefix(err, fmt.Sprintf("path %q:", key))
|
|
}
|
|
|
|
var pc PathCapabilities
|
|
|
|
// allocate memory so that DecodeObject can initialize the Permissions struct
|
|
pc.Permissions = new(Permissions)
|
|
|
|
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
|
|
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)
|
|
}
|
|
}
|
|
|
|
// 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:
|
|
return fmt.Errorf("path %q: invalid capability '%s'", key, cap)
|
|
}
|
|
}
|
|
|
|
PathFinished:
|
|
|
|
paths = append(paths, &pc)
|
|
}
|
|
|
|
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
|
|
}
|