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