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