1package vault
2
3import (
4	"errors"
5	"fmt"
6	"strings"
7	"time"
8
9	"github.com/hashicorp/errwrap"
10	multierror "github.com/hashicorp/go-multierror"
11	"github.com/hashicorp/hcl"
12	"github.com/hashicorp/hcl/hcl/ast"
13	"github.com/hashicorp/vault/helper/identity"
14	"github.com/hashicorp/vault/helper/namespace"
15	"github.com/hashicorp/vault/sdk/helper/hclutil"
16	"github.com/hashicorp/vault/sdk/helper/identitytpl"
17	"github.com/hashicorp/vault/sdk/helper/parseutil"
18	"github.com/mitchellh/copystructure"
19)
20
21const (
22	DenyCapability   = "deny"
23	CreateCapability = "create"
24	ReadCapability   = "read"
25	UpdateCapability = "update"
26	DeleteCapability = "delete"
27	ListCapability   = "list"
28	SudoCapability   = "sudo"
29	RootCapability   = "root"
30
31	// Backwards compatibility
32	OldDenyPathPolicy  = "deny"
33	OldReadPathPolicy  = "read"
34	OldWritePathPolicy = "write"
35	OldSudoPathPolicy  = "sudo"
36)
37
38const (
39	DenyCapabilityInt uint32 = 1 << iota
40	CreateCapabilityInt
41	ReadCapabilityInt
42	UpdateCapabilityInt
43	DeleteCapabilityInt
44	ListCapabilityInt
45	SudoCapabilityInt
46)
47
48type PolicyType uint32
49
50const (
51	PolicyTypeACL PolicyType = iota
52	PolicyTypeRGP
53	PolicyTypeEGP
54
55	// Triggers a lookup in the map to figure out if ACL or RGP
56	PolicyTypeToken
57)
58
59func (p PolicyType) String() string {
60	switch p {
61	case PolicyTypeACL:
62		return "acl"
63	case PolicyTypeRGP:
64		return "rgp"
65	case PolicyTypeEGP:
66		return "egp"
67	}
68
69	return ""
70}
71
72var (
73	cap2Int = map[string]uint32{
74		DenyCapability:   DenyCapabilityInt,
75		CreateCapability: CreateCapabilityInt,
76		ReadCapability:   ReadCapabilityInt,
77		UpdateCapability: UpdateCapabilityInt,
78		DeleteCapability: DeleteCapabilityInt,
79		ListCapability:   ListCapabilityInt,
80		SudoCapability:   SudoCapabilityInt,
81	}
82)
83
84type egpPath struct {
85	Path string `json:"path"`
86	Glob bool   `json:"glob"`
87}
88
89// Policy is used to represent the policy specified by an ACL configuration.
90type Policy struct {
91	sentinelPolicy
92	Name      string       `hcl:"name"`
93	Paths     []*PathRules `hcl:"-"`
94	Raw       string
95	Type      PolicyType
96	Templated bool
97	namespace *namespace.Namespace
98}
99
100// ShallowClone returns a shallow clone of the policy. This should not be used
101// if any of the reference-typed fields are going to be modified
102func (p *Policy) ShallowClone() *Policy {
103	return &Policy{
104		sentinelPolicy: p.sentinelPolicy,
105		Name:           p.Name,
106		Paths:          p.Paths,
107		Raw:            p.Raw,
108		Type:           p.Type,
109		Templated:      p.Templated,
110		namespace:      p.namespace,
111	}
112}
113
114// PathRules represents a policy for a path in the namespace.
115type PathRules struct {
116	Path                string
117	Policy              string
118	Permissions         *ACLPermissions
119	IsPrefix            bool
120	HasSegmentWildcards bool
121	Capabilities        []string
122
123	// These keys are used at the top level to make the HCL nicer; we store in
124	// the ACLPermissions object though
125	MinWrappingTTLHCL     interface{}              `hcl:"min_wrapping_ttl"`
126	MaxWrappingTTLHCL     interface{}              `hcl:"max_wrapping_ttl"`
127	AllowedParametersHCL  map[string][]interface{} `hcl:"allowed_parameters"`
128	DeniedParametersHCL   map[string][]interface{} `hcl:"denied_parameters"`
129	RequiredParametersHCL []string                 `hcl:"required_parameters"`
130	MFAMethodsHCL         []string                 `hcl:"mfa_methods"`
131	ControlGroupHCL       *ControlGroupHCL         `hcl:"control_group"`
132}
133
134type ControlGroupHCL struct {
135	TTL     interface{}                    `hcl:"ttl"`
136	Factors map[string]*ControlGroupFactor `hcl:"factor"`
137}
138
139type ControlGroup struct {
140	TTL     time.Duration
141	Factors []*ControlGroupFactor
142}
143
144type ControlGroupFactor struct {
145	Name     string
146	Identity *IdentityFactor `hcl:"identity"`
147}
148
149type IdentityFactor struct {
150	GroupIDs          []string `hcl:"group_ids"`
151	GroupNames        []string `hcl:"group_names"`
152	ApprovalsRequired int      `hcl:"approvals"`
153}
154
155type ACLPermissions struct {
156	CapabilitiesBitmap uint32
157	MinWrappingTTL     time.Duration
158	MaxWrappingTTL     time.Duration
159	AllowedParameters  map[string][]interface{}
160	DeniedParameters   map[string][]interface{}
161	RequiredParameters []string
162	MFAMethods         []string
163	ControlGroup       *ControlGroup
164}
165
166func (p *ACLPermissions) Clone() (*ACLPermissions, error) {
167	ret := &ACLPermissions{
168		CapabilitiesBitmap: p.CapabilitiesBitmap,
169		MinWrappingTTL:     p.MinWrappingTTL,
170		MaxWrappingTTL:     p.MaxWrappingTTL,
171		RequiredParameters: p.RequiredParameters[:],
172	}
173
174	switch {
175	case p.AllowedParameters == nil:
176	case len(p.AllowedParameters) == 0:
177		ret.AllowedParameters = make(map[string][]interface{})
178	default:
179		clonedAllowed, err := copystructure.Copy(p.AllowedParameters)
180		if err != nil {
181			return nil, err
182		}
183		ret.AllowedParameters = clonedAllowed.(map[string][]interface{})
184	}
185
186	switch {
187	case p.DeniedParameters == nil:
188	case len(p.DeniedParameters) == 0:
189		ret.DeniedParameters = make(map[string][]interface{})
190	default:
191		clonedDenied, err := copystructure.Copy(p.DeniedParameters)
192		if err != nil {
193			return nil, err
194		}
195		ret.DeniedParameters = clonedDenied.(map[string][]interface{})
196	}
197
198	switch {
199	case p.MFAMethods == nil:
200	case len(p.MFAMethods) == 0:
201		ret.MFAMethods = []string{}
202	default:
203		clonedMFAMethods, err := copystructure.Copy(p.MFAMethods)
204		if err != nil {
205			return nil, err
206		}
207		ret.MFAMethods = clonedMFAMethods.([]string)
208	}
209
210	switch {
211	case p.ControlGroup == nil:
212	default:
213		clonedControlGroup, err := copystructure.Copy(p.ControlGroup)
214		if err != nil {
215			return nil, err
216		}
217		ret.ControlGroup = clonedControlGroup.(*ControlGroup)
218	}
219
220	return ret, nil
221}
222
223// ParseACLPolicy is used to parse the specified ACL rules into an
224// intermediary set of policies, before being compiled into
225// the ACL
226func ParseACLPolicy(ns *namespace.Namespace, rules string) (*Policy, error) {
227	return parseACLPolicyWithTemplating(ns, rules, false, nil, nil)
228}
229
230// parseACLPolicyWithTemplating performs the actual work and checks whether we
231// should perform substitutions. If performTemplating is true we know that it
232// is templated so we don't check again, otherwise we check to see if it's a
233// templated policy.
234func parseACLPolicyWithTemplating(ns *namespace.Namespace, rules string, performTemplating bool, entity *identity.Entity, groups []*identity.Group) (*Policy, error) {
235	// Parse the rules
236	root, err := hcl.Parse(rules)
237	if err != nil {
238		return nil, errwrap.Wrapf("failed to parse policy: {{err}}", err)
239	}
240
241	// Top-level item should be the object list
242	list, ok := root.Node.(*ast.ObjectList)
243	if !ok {
244		return nil, fmt.Errorf("failed to parse policy: does not contain a root object")
245	}
246
247	// Check for invalid top-level keys
248	valid := []string{
249		"name",
250		"path",
251	}
252	if err := hclutil.CheckHCLKeys(list, valid); err != nil {
253		return nil, errwrap.Wrapf("failed to parse policy: {{err}}", err)
254	}
255
256	// Create the initial policy and store the raw text of the rules
257	p := Policy{
258		Raw:       rules,
259		Type:      PolicyTypeACL,
260		namespace: ns,
261	}
262	if err := hcl.DecodeObject(&p, list); err != nil {
263		return nil, errwrap.Wrapf("failed to parse policy: {{err}}", err)
264	}
265
266	if o := list.Filter("path"); len(o.Items) > 0 {
267		if err := parsePaths(&p, o, performTemplating, entity, groups); err != nil {
268			return nil, errwrap.Wrapf("failed to parse policy: {{err}}", err)
269		}
270	}
271
272	return &p, nil
273}
274
275func parsePaths(result *Policy, list *ast.ObjectList, performTemplating bool, entity *identity.Entity, groups []*identity.Group) error {
276	paths := make([]*PathRules, 0, len(list.Items))
277	for _, item := range list.Items {
278		key := "path"
279		if len(item.Keys) > 0 {
280			key = item.Keys[0].Token.Value().(string)
281		}
282
283		// Check the path
284		if performTemplating {
285			_, templated, err := identitytpl.PopulateString(identitytpl.PopulateStringInput{
286				Mode:        identitytpl.ACLTemplating,
287				String:      key,
288				Entity:      identity.ToSDKEntity(entity),
289				Groups:      identity.ToSDKGroups(groups),
290				NamespaceID: result.namespace.ID,
291			})
292			if err != nil {
293				continue
294			}
295			key = templated
296		} else {
297			hasTemplating, _, err := identitytpl.PopulateString(identitytpl.PopulateStringInput{
298				Mode:              identitytpl.ACLTemplating,
299				ValidityCheckOnly: true,
300				String:            key,
301			})
302			if err != nil {
303				return errwrap.Wrapf("failed to validate policy templating: {{err}}", err)
304			}
305			if hasTemplating {
306				result.Templated = true
307			}
308		}
309
310		valid := []string{
311			"comment",
312			"policy",
313			"capabilities",
314			"allowed_parameters",
315			"denied_parameters",
316			"required_parameters",
317			"min_wrapping_ttl",
318			"max_wrapping_ttl",
319			"mfa_methods",
320			"control_group",
321		}
322		if err := hclutil.CheckHCLKeys(item.Val, valid); err != nil {
323			return multierror.Prefix(err, fmt.Sprintf("path %q:", key))
324		}
325
326		var pc PathRules
327
328		// allocate memory so that DecodeObject can initialize the ACLPermissions struct
329		pc.Permissions = new(ACLPermissions)
330
331		pc.Path = key
332
333		if err := hcl.DecodeObject(&pc, item.Val); err != nil {
334			return multierror.Prefix(err, fmt.Sprintf("path %q:", key))
335		}
336
337		// Strip a leading '/' as paths in Vault start after the / in the API path
338		if len(pc.Path) > 0 && pc.Path[0] == '/' {
339			pc.Path = pc.Path[1:]
340		}
341
342		// Ensure we are using the full request path internally
343		pc.Path = result.namespace.Path + pc.Path
344
345		if strings.Contains(pc.Path, "+*") {
346			return fmt.Errorf("path %q: invalid use of wildcards ('+*' is forbidden)", pc.Path)
347		}
348
349		if pc.Path == "+" || strings.Count(pc.Path, "/+") > 0 || strings.HasPrefix(pc.Path, "+/") {
350			pc.HasSegmentWildcards = true
351		}
352
353		if strings.HasSuffix(pc.Path, "*") {
354			// If there are segment wildcards, don't actually strip the
355			// trailing asterisk, but don't want to hit the default case
356			if !pc.HasSegmentWildcards {
357				// Strip the glob character if found
358				pc.Path = strings.TrimSuffix(pc.Path, "*")
359				pc.IsPrefix = true
360			}
361		}
362
363		// Map old-style policies into capabilities
364		if len(pc.Policy) > 0 {
365			switch pc.Policy {
366			case OldDenyPathPolicy:
367				pc.Capabilities = []string{DenyCapability}
368			case OldReadPathPolicy:
369				pc.Capabilities = append(pc.Capabilities, []string{ReadCapability, ListCapability}...)
370			case OldWritePathPolicy:
371				pc.Capabilities = append(pc.Capabilities, []string{CreateCapability, ReadCapability, UpdateCapability, DeleteCapability, ListCapability}...)
372			case OldSudoPathPolicy:
373				pc.Capabilities = append(pc.Capabilities, []string{CreateCapability, ReadCapability, UpdateCapability, DeleteCapability, ListCapability, SudoCapability}...)
374			default:
375				return fmt.Errorf("path %q: invalid policy %q", key, pc.Policy)
376			}
377		}
378
379		// Initialize the map
380		pc.Permissions.CapabilitiesBitmap = 0
381		for _, cap := range pc.Capabilities {
382			switch cap {
383			// If it's deny, don't include any other capability
384			case DenyCapability:
385				pc.Capabilities = []string{DenyCapability}
386				pc.Permissions.CapabilitiesBitmap = DenyCapabilityInt
387				goto PathFinished
388			case CreateCapability, ReadCapability, UpdateCapability, DeleteCapability, ListCapability, SudoCapability:
389				pc.Permissions.CapabilitiesBitmap |= cap2Int[cap]
390			default:
391				return fmt.Errorf("path %q: invalid capability %q", key, cap)
392			}
393		}
394
395		if pc.AllowedParametersHCL != nil {
396			pc.Permissions.AllowedParameters = make(map[string][]interface{}, len(pc.AllowedParametersHCL))
397			for key, val := range pc.AllowedParametersHCL {
398				pc.Permissions.AllowedParameters[strings.ToLower(key)] = val
399			}
400		}
401		if pc.DeniedParametersHCL != nil {
402			pc.Permissions.DeniedParameters = make(map[string][]interface{}, len(pc.DeniedParametersHCL))
403
404			for key, val := range pc.DeniedParametersHCL {
405				pc.Permissions.DeniedParameters[strings.ToLower(key)] = val
406			}
407		}
408		if pc.MinWrappingTTLHCL != nil {
409			dur, err := parseutil.ParseDurationSecond(pc.MinWrappingTTLHCL)
410			if err != nil {
411				return errwrap.Wrapf("error parsing min_wrapping_ttl: {{err}}", err)
412			}
413			pc.Permissions.MinWrappingTTL = dur
414		}
415		if pc.MaxWrappingTTLHCL != nil {
416			dur, err := parseutil.ParseDurationSecond(pc.MaxWrappingTTLHCL)
417			if err != nil {
418				return errwrap.Wrapf("error parsing max_wrapping_ttl: {{err}}", err)
419			}
420			pc.Permissions.MaxWrappingTTL = dur
421		}
422		if pc.MFAMethodsHCL != nil {
423			pc.Permissions.MFAMethods = make([]string, len(pc.MFAMethodsHCL))
424			for idx, item := range pc.MFAMethodsHCL {
425				pc.Permissions.MFAMethods[idx] = item
426			}
427		}
428		if pc.ControlGroupHCL != nil {
429			pc.Permissions.ControlGroup = new(ControlGroup)
430			if pc.ControlGroupHCL.TTL != nil {
431				dur, err := parseutil.ParseDurationSecond(pc.ControlGroupHCL.TTL)
432				if err != nil {
433					return errwrap.Wrapf("error parsing control group max ttl: {{err}}", err)
434				}
435				pc.Permissions.ControlGroup.TTL = dur
436			}
437
438			var factors []*ControlGroupFactor
439			if pc.ControlGroupHCL.Factors != nil {
440				for key, factor := range pc.ControlGroupHCL.Factors {
441					// Although we only have one factor here, we need to check to make sure there is at least
442					// one factor defined in this factor block.
443					if factor.Identity == nil {
444						return errors.New("no control_group factor provided")
445					}
446
447					if factor.Identity.ApprovalsRequired <= 0 ||
448						(len(factor.Identity.GroupIDs) == 0 && len(factor.Identity.GroupNames) == 0) {
449						return errors.New("must provide more than one identity group and approvals > 0")
450					}
451
452					factors = append(factors, &ControlGroupFactor{
453						Name:     key,
454						Identity: factor.Identity,
455					})
456				}
457			}
458			if len(factors) == 0 {
459				return errors.New("no control group factors provided")
460			}
461			pc.Permissions.ControlGroup.Factors = factors
462		}
463		if pc.Permissions.MinWrappingTTL != 0 &&
464			pc.Permissions.MaxWrappingTTL != 0 &&
465			pc.Permissions.MaxWrappingTTL < pc.Permissions.MinWrappingTTL {
466			return errors.New("max_wrapping_ttl cannot be less than min_wrapping_ttl")
467		}
468		if len(pc.RequiredParametersHCL) > 0 {
469			pc.Permissions.RequiredParameters = pc.RequiredParametersHCL[:]
470		}
471
472	PathFinished:
473		paths = append(paths, &pc)
474	}
475
476	result.Paths = paths
477	return nil
478}
479