1package vault 2 3import ( 4 "context" 5 "fmt" 6 "reflect" 7 "sort" 8 "strings" 9 10 "github.com/armon/go-radix" 11 "github.com/hashicorp/errwrap" 12 "github.com/hashicorp/go-multierror" 13 "github.com/hashicorp/vault/helper/identity" 14 "github.com/hashicorp/vault/helper/namespace" 15 "github.com/hashicorp/vault/sdk/helper/strutil" 16 "github.com/hashicorp/vault/sdk/logical" 17 "github.com/mitchellh/copystructure" 18) 19 20// ACL is used to wrap a set of policies to provide 21// an efficient interface for access control. 22type ACL struct { 23 // exactRules contains the path policies that are exact 24 exactRules *radix.Tree 25 26 // prefixRules contains the path policies that are a prefix 27 prefixRules *radix.Tree 28 29 segmentWildcardPaths map[string]interface{} 30 31 // root is enabled if the "root" named policy is present. 32 root bool 33 34 // Stores policies that are actually RGPs for later fetching 35 rgpPolicies []*Policy 36} 37 38type PolicyCheckOpts struct { 39 RootPrivsRequired bool 40 Unauth bool 41} 42 43type AuthResults struct { 44 ACLResults *ACLResults 45 Allowed bool 46 RootPrivs bool 47 DeniedError bool 48 Error *multierror.Error 49} 50 51type ACLResults struct { 52 Allowed bool 53 RootPrivs bool 54 IsRoot bool 55 MFAMethods []string 56 ControlGroup *ControlGroup 57 CapabilitiesBitmap uint32 58} 59 60// NewACL is used to construct a policy based ACL from a set of policies. 61func NewACL(ctx context.Context, policies []*Policy) (*ACL, error) { 62 // Initialize 63 a := &ACL{ 64 exactRules: radix.New(), 65 prefixRules: radix.New(), 66 segmentWildcardPaths: make(map[string]interface{}, len(policies)), 67 root: false, 68 } 69 70 ns, err := namespace.FromContext(ctx) 71 if err != nil { 72 return nil, err 73 } 74 if ns == nil { 75 return nil, namespace.ErrNoNamespace 76 } 77 78 // Inject each policy 79 for _, policy := range policies { 80 // Ignore a nil policy object 81 if policy == nil { 82 continue 83 } 84 85 switch policy.Type { 86 case PolicyTypeACL: 87 case PolicyTypeRGP: 88 a.rgpPolicies = append(a.rgpPolicies, policy) 89 continue 90 default: 91 return nil, fmt.Errorf("unable to parse policy (wrong type)") 92 } 93 94 // Check if this is root 95 if policy.Name == "root" { 96 if ns.ID != namespace.RootNamespaceID { 97 return nil, fmt.Errorf("root policy is only allowed in root namespace") 98 } 99 100 if len(policies) != 1 { 101 return nil, fmt.Errorf("other policies present along with root") 102 } 103 a.root = true 104 } 105 106 for _, pc := range policy.Paths { 107 var raw interface{} 108 var ok bool 109 var tree *radix.Tree 110 111 switch { 112 case pc.HasSegmentWildcards: 113 raw, ok = a.segmentWildcardPaths[pc.Path] 114 default: 115 // Check which tree to use 116 tree = a.exactRules 117 if pc.IsPrefix { 118 tree = a.prefixRules 119 } 120 121 // Check for an existing policy 122 raw, ok = tree.Get(pc.Path) 123 } 124 125 if !ok { 126 clonedPerms, err := pc.Permissions.Clone() 127 if err != nil { 128 return nil, errwrap.Wrapf("error cloning ACL permissions: {{err}}", err) 129 } 130 switch { 131 case pc.HasSegmentWildcards: 132 a.segmentWildcardPaths[pc.Path] = clonedPerms 133 default: 134 tree.Insert(pc.Path, clonedPerms) 135 } 136 continue 137 } 138 139 // these are the ones already in the tree 140 existingPerms := raw.(*ACLPermissions) 141 142 switch { 143 case existingPerms.CapabilitiesBitmap&DenyCapabilityInt > 0: 144 // If we are explicitly denied in the existing capability set, 145 // don't save anything else 146 continue 147 148 case pc.Permissions.CapabilitiesBitmap&DenyCapabilityInt > 0: 149 // If this new policy explicitly denies, only save the deny value 150 existingPerms.CapabilitiesBitmap = DenyCapabilityInt 151 existingPerms.AllowedParameters = nil 152 existingPerms.DeniedParameters = nil 153 goto INSERT 154 155 default: 156 // Insert the capabilities in this new policy into the existing 157 // value 158 existingPerms.CapabilitiesBitmap = existingPerms.CapabilitiesBitmap | pc.Permissions.CapabilitiesBitmap 159 } 160 161 // Note: In these stanzas, we're preferring minimum lifetimes. So 162 // we take the lesser of two specified max values, or we take the 163 // lesser of two specified min values, the idea being, allowing 164 // token lifetime to be minimum possible. 165 // 166 // If we have an existing max, and we either don't have a current 167 // max, or the current is greater than the previous, use the 168 // existing. 169 if pc.Permissions.MaxWrappingTTL > 0 && 170 (existingPerms.MaxWrappingTTL == 0 || 171 pc.Permissions.MaxWrappingTTL < existingPerms.MaxWrappingTTL) { 172 existingPerms.MaxWrappingTTL = pc.Permissions.MaxWrappingTTL 173 } 174 // If we have an existing min, and we either don't have a current 175 // min, or the current is greater than the previous, use the 176 // existing 177 if pc.Permissions.MinWrappingTTL > 0 && 178 (existingPerms.MinWrappingTTL == 0 || 179 pc.Permissions.MinWrappingTTL < existingPerms.MinWrappingTTL) { 180 existingPerms.MinWrappingTTL = pc.Permissions.MinWrappingTTL 181 } 182 183 if len(pc.Permissions.AllowedParameters) > 0 { 184 if existingPerms.AllowedParameters == nil { 185 clonedAllowed, err := copystructure.Copy(pc.Permissions.AllowedParameters) 186 if err != nil { 187 return nil, err 188 } 189 existingPerms.AllowedParameters = clonedAllowed.(map[string][]interface{}) 190 } else { 191 for key, value := range pc.Permissions.AllowedParameters { 192 pcValue, ok := existingPerms.AllowedParameters[key] 193 // If an empty array exist it should overwrite any other 194 // value. 195 if len(value) == 0 || (ok && len(pcValue) == 0) { 196 existingPerms.AllowedParameters[key] = []interface{}{} 197 } else { 198 // Merge the two maps, appending values on key conflict. 199 existingPerms.AllowedParameters[key] = append(value, existingPerms.AllowedParameters[key]...) 200 } 201 } 202 } 203 } 204 205 if len(pc.Permissions.DeniedParameters) > 0 { 206 if existingPerms.DeniedParameters == nil { 207 clonedDenied, err := copystructure.Copy(pc.Permissions.DeniedParameters) 208 if err != nil { 209 return nil, err 210 } 211 existingPerms.DeniedParameters = clonedDenied.(map[string][]interface{}) 212 } else { 213 for key, value := range pc.Permissions.DeniedParameters { 214 pcValue, ok := existingPerms.DeniedParameters[key] 215 // If an empty array exist it should overwrite any other 216 // value. 217 if len(value) == 0 || (ok && len(pcValue) == 0) { 218 existingPerms.DeniedParameters[key] = []interface{}{} 219 } else { 220 // Merge the two maps, appending values on key conflict. 221 existingPerms.DeniedParameters[key] = append(value, existingPerms.DeniedParameters[key]...) 222 } 223 } 224 } 225 } 226 227 if len(pc.Permissions.RequiredParameters) > 0 { 228 if len(existingPerms.RequiredParameters) == 0 { 229 existingPerms.RequiredParameters = pc.Permissions.RequiredParameters 230 } else { 231 for _, v := range pc.Permissions.RequiredParameters { 232 if !strutil.StrListContains(existingPerms.RequiredParameters, v) { 233 existingPerms.RequiredParameters = append(existingPerms.RequiredParameters, v) 234 } 235 } 236 } 237 } 238 239 if len(pc.Permissions.MFAMethods) > 0 { 240 if existingPerms.MFAMethods == nil { 241 existingPerms.MFAMethods = pc.Permissions.MFAMethods 242 } else { 243 for _, method := range pc.Permissions.MFAMethods { 244 existingPerms.MFAMethods = append(existingPerms.MFAMethods, method) 245 } 246 } 247 existingPerms.MFAMethods = strutil.RemoveDuplicates(existingPerms.MFAMethods, false) 248 } 249 250 // No need to dedupe this list since any authorization can satisfy any factor 251 if pc.Permissions.ControlGroup != nil { 252 if len(pc.Permissions.ControlGroup.Factors) > 0 { 253 if existingPerms.ControlGroup == nil { 254 existingPerms.ControlGroup = pc.Permissions.ControlGroup 255 } else { 256 for _, authz := range pc.Permissions.ControlGroup.Factors { 257 existingPerms.ControlGroup.Factors = append(existingPerms.ControlGroup.Factors, authz) 258 } 259 } 260 } 261 } 262 263 INSERT: 264 switch { 265 case pc.HasSegmentWildcards: 266 a.segmentWildcardPaths[pc.Path] = existingPerms 267 default: 268 tree.Insert(pc.Path, existingPerms) 269 } 270 } 271 } 272 return a, nil 273} 274 275func (a *ACL) Capabilities(ctx context.Context, path string) (pathCapabilities []string) { 276 req := &logical.Request{ 277 Path: path, 278 // doesn't matter, but use List to trigger fallback behavior so we can 279 // model real behavior 280 Operation: logical.ListOperation, 281 } 282 283 res := a.AllowOperation(ctx, req, true) 284 if res.IsRoot { 285 return []string{RootCapability} 286 } 287 288 capabilities := res.CapabilitiesBitmap 289 290 if capabilities&SudoCapabilityInt > 0 { 291 pathCapabilities = append(pathCapabilities, SudoCapability) 292 } 293 if capabilities&ReadCapabilityInt > 0 { 294 pathCapabilities = append(pathCapabilities, ReadCapability) 295 } 296 if capabilities&ListCapabilityInt > 0 { 297 pathCapabilities = append(pathCapabilities, ListCapability) 298 } 299 if capabilities&UpdateCapabilityInt > 0 { 300 pathCapabilities = append(pathCapabilities, UpdateCapability) 301 } 302 if capabilities&DeleteCapabilityInt > 0 { 303 pathCapabilities = append(pathCapabilities, DeleteCapability) 304 } 305 if capabilities&CreateCapabilityInt > 0 { 306 pathCapabilities = append(pathCapabilities, CreateCapability) 307 } 308 309 // If "deny" is explicitly set or if the path has no capabilities at all, 310 // set the path capabilities to "deny" 311 if capabilities&DenyCapabilityInt > 0 || len(pathCapabilities) == 0 { 312 pathCapabilities = []string{DenyCapability} 313 } 314 return 315} 316 317// AllowOperation is used to check if the given operation is permitted. 318func (a *ACL) AllowOperation(ctx context.Context, req *logical.Request, capCheckOnly bool) (ret *ACLResults) { 319 ret = new(ACLResults) 320 321 // Fast-path root 322 if a.root { 323 ret.Allowed = true 324 ret.RootPrivs = true 325 ret.IsRoot = true 326 return 327 } 328 op := req.Operation 329 330 // Help is always allowed 331 if op == logical.HelpOperation { 332 ret.Allowed = true 333 return 334 } 335 336 var permissions *ACLPermissions 337 338 ns, err := namespace.FromContext(ctx) 339 if err != nil { 340 return 341 } 342 path := ns.Path + req.Path 343 344 // The request path should take care of this already but this is useful for 345 // tests and as defense in depth 346 for { 347 if len(path) > 0 && path[0] == '/' { 348 path = path[1:] 349 } else { 350 break 351 } 352 } 353 354 // Find an exact matching rule, look for prefix if no match 355 var capabilities uint32 356 raw, ok := a.exactRules.Get(path) 357 if ok { 358 permissions = raw.(*ACLPermissions) 359 capabilities = permissions.CapabilitiesBitmap 360 goto CHECK 361 } 362 if op == logical.ListOperation { 363 raw, ok = a.exactRules.Get(strings.TrimSuffix(path, "/")) 364 if ok { 365 permissions = raw.(*ACLPermissions) 366 capabilities = permissions.CapabilitiesBitmap 367 goto CHECK 368 } 369 } 370 371 permissions = a.CheckAllowedFromNonExactPaths(path, false) 372 if permissions != nil { 373 capabilities = permissions.CapabilitiesBitmap 374 goto CHECK 375 } 376 377 // No exact, prefix, or segment wildcard paths found, return without 378 // setting allowed 379 return 380 381CHECK: 382 // Check if the minimum permissions are met 383 // If "deny" has been explicitly set, only deny will be in the map, so we 384 // only need to check for the existence of other values 385 ret.RootPrivs = capabilities&SudoCapabilityInt > 0 386 387 // This is after the RootPrivs check so we can gate on it being from sudo 388 // rather than policy root 389 if capCheckOnly { 390 ret.CapabilitiesBitmap = capabilities 391 return ret 392 } 393 394 ret.MFAMethods = permissions.MFAMethods 395 ret.ControlGroup = permissions.ControlGroup 396 397 operationAllowed := false 398 switch op { 399 case logical.ReadOperation: 400 operationAllowed = capabilities&ReadCapabilityInt > 0 401 case logical.ListOperation: 402 operationAllowed = capabilities&ListCapabilityInt > 0 403 case logical.UpdateOperation: 404 operationAllowed = capabilities&UpdateCapabilityInt > 0 405 case logical.DeleteOperation: 406 operationAllowed = capabilities&DeleteCapabilityInt > 0 407 case logical.CreateOperation: 408 operationAllowed = capabilities&CreateCapabilityInt > 0 409 410 // These three re-use UpdateCapabilityInt since that's the most appropriate 411 // capability/operation mapping 412 case logical.RevokeOperation, logical.RenewOperation, logical.RollbackOperation: 413 operationAllowed = capabilities&UpdateCapabilityInt > 0 414 415 default: 416 return 417 } 418 419 if !operationAllowed { 420 return 421 } 422 423 if permissions.MaxWrappingTTL > 0 { 424 if req.WrapInfo == nil || req.WrapInfo.TTL > permissions.MaxWrappingTTL { 425 return 426 } 427 } 428 if permissions.MinWrappingTTL > 0 { 429 if req.WrapInfo == nil || req.WrapInfo.TTL < permissions.MinWrappingTTL { 430 return 431 } 432 } 433 // This situation can happen because of merging, even though in a single 434 // path statement we check on ingress 435 if permissions.MinWrappingTTL != 0 && 436 permissions.MaxWrappingTTL != 0 && 437 permissions.MaxWrappingTTL < permissions.MinWrappingTTL { 438 return 439 } 440 441 // Only check parameter permissions for operations that can modify 442 // parameters. 443 if op == logical.ReadOperation || op == logical.UpdateOperation || op == logical.CreateOperation { 444 for _, parameter := range permissions.RequiredParameters { 445 if _, ok := req.Data[strings.ToLower(parameter)]; !ok { 446 return 447 } 448 } 449 450 // If there are no data fields, allow 451 if len(req.Data) == 0 { 452 ret.Allowed = true 453 return 454 } 455 456 if len(permissions.DeniedParameters) == 0 { 457 goto ALLOWED_PARAMETERS 458 } 459 460 // Check if all parameters have been denied 461 if _, ok := permissions.DeniedParameters["*"]; ok { 462 return 463 } 464 465 for parameter, value := range req.Data { 466 // Check if parameter has been explicitly denied 467 if valueSlice, ok := permissions.DeniedParameters[strings.ToLower(parameter)]; ok { 468 // If the value exists in denied values slice, deny 469 if valueInParameterList(value, valueSlice) { 470 return 471 } 472 } 473 } 474 475 ALLOWED_PARAMETERS: 476 // If we don't have any allowed parameters set, allow 477 if len(permissions.AllowedParameters) == 0 { 478 ret.Allowed = true 479 return 480 } 481 482 _, allowedAll := permissions.AllowedParameters["*"] 483 if len(permissions.AllowedParameters) == 1 && allowedAll { 484 ret.Allowed = true 485 return 486 } 487 488 for parameter, value := range req.Data { 489 valueSlice, ok := permissions.AllowedParameters[strings.ToLower(parameter)] 490 // Requested parameter is not in allowed list 491 if !ok && !allowedAll { 492 return 493 } 494 495 // If the value doesn't exists in the allowed values slice, 496 // deny 497 if ok && !valueInParameterList(value, valueSlice) { 498 return 499 } 500 } 501 } 502 503 ret.Allowed = true 504 return 505} 506 507type wcPathDescr struct { 508 firstWCOrGlob int 509 wildcards int 510 isPrefix bool 511 wcPath string 512 perms *ACLPermissions 513} 514 515// CheckAllowedFromNonExactPaths returns permissions corresponding to a 516// matching path with wildcards/globs. If bareMount is true, the path should 517// correspond to a mount prefix, and what is returned is either a non-nil set 518// of permissions from some allowed path underneath the mount (for use in mount 519// access checks), or nil indicating no non-deny permissions were found. 520func (a *ACL) CheckAllowedFromNonExactPaths(path string, bareMount bool) *ACLPermissions { 521 wcPathDescrs := make([]wcPathDescr, 0, len(a.segmentWildcardPaths)+1) 522 523 less := func(i, j int) bool { 524 // In the case of multiple matches, we use this priority order, 525 // which tries to most closely match longest-prefix: 526 // 527 // * First glob or wildcard position (prefer foo/a* over foo/+, 528 // foo/bar/+/baz over foo/+/bar/baz) 529 // * Whether it's a prefix (prefer foo/+/bar over foo/+/ba*, 530 // foo/+ over foo/*) 531 // * Number of wildcard segments (prefer foo/bar/+/baz over foo/+/+/baz) 532 // * Length check (prefer foo/+/bar/ba* over foo/+/bar/b*) 533 // * Lexicographical ordering (preferring less, arbitrarily) 534 // 535 // That final case (lexigraphical) should never really come up. It's more 536 // of a throwing-up-hands scenario akin to panic("should not be here") 537 // statements, but less panicky. 538 539 pdi, pdj := wcPathDescrs[i], wcPathDescrs[j] 540 541 // If the first wildcard (+) or glob (*) occurs earlier in pdi, 542 // pdi is lower priority 543 if pdi.firstWCOrGlob < pdj.firstWCOrGlob { 544 return true 545 } else if pdi.firstWCOrGlob > pdj.firstWCOrGlob { 546 return false 547 } 548 549 // If pdi ends in * and pdj doesn't, pdi is lower priority 550 if pdi.isPrefix && !pdj.isPrefix { 551 return true 552 } else if !pdi.isPrefix && pdj.isPrefix { 553 return false 554 } 555 556 // If pdi has more wc segs, pdi is lower priority 557 if pdi.wildcards > pdj.wildcards { 558 return true 559 } else if pdi.wildcards < pdj.wildcards { 560 return false 561 } 562 563 // If pdi is shorter, it is lower priority 564 if len(pdi.wcPath) < len(pdj.wcPath) { 565 return true 566 } else if len(pdi.wcPath) > len(pdj.wcPath) { 567 return false 568 } 569 570 // If pdi is smaller lexicographically, it is lower priority 571 if pdi.wcPath < pdj.wcPath { 572 return true 573 } else if pdi.wcPath > pdj.wcPath { 574 return false 575 } 576 return false 577 } 578 579 // Find a prefix rule if any. 580 { 581 prefix, raw, ok := a.prefixRules.LongestPrefix(path) 582 if ok { 583 if len(a.segmentWildcardPaths) == 0 { 584 return raw.(*ACLPermissions) 585 } 586 wcPathDescrs = append(wcPathDescrs, wcPathDescr{ 587 firstWCOrGlob: len(prefix), 588 wcPath: prefix, 589 isPrefix: true, 590 perms: raw.(*ACLPermissions), 591 }) 592 } 593 } 594 595 if len(a.segmentWildcardPaths) == 0 { 596 return nil 597 } 598 599 pathParts := strings.Split(path, "/") 600 601SWCPATH: 602 for fullWCPath := range a.segmentWildcardPaths { 603 if fullWCPath == "" { 604 continue 605 } 606 pd := wcPathDescr{firstWCOrGlob: strings.Index(fullWCPath, "+")} 607 608 currWCPath := fullWCPath 609 if currWCPath[len(currWCPath)-1] == '*' { 610 pd.isPrefix = true 611 currWCPath = currWCPath[0 : len(currWCPath)-1] 612 } 613 pd.wcPath = currWCPath 614 615 splitCurrWCPath := strings.Split(currWCPath, "/") 616 617 if !bareMount && len(pathParts) < len(splitCurrWCPath) { 618 // check if the path coming in is shorter; if so it can't match 619 continue 620 } 621 if !bareMount && !pd.isPrefix && len(splitCurrWCPath) != len(pathParts) { 622 // If it's not a prefix we expect the same number of segments 623 continue 624 } 625 626 segments := make([]string, 0, len(splitCurrWCPath)) 627 for i, aclPart := range splitCurrWCPath { 628 switch { 629 case aclPart == "+": 630 pd.wildcards++ 631 segments = append(segments, pathParts[i]) 632 633 case aclPart == pathParts[i]: 634 segments = append(segments, pathParts[i]) 635 636 case pd.isPrefix && i == len(splitCurrWCPath)-1 && strings.HasPrefix(pathParts[i], aclPart): 637 segments = append(segments, pathParts[i:]...) 638 639 case !bareMount: 640 // Found a mismatch, give up on this segmentWildcardPath 641 continue SWCPATH 642 } 643 644 // -2 because we're always invoked with a trailing "/" in case bareMount. 645 if bareMount && i == len(pathParts)-2 { 646 joinedPath := strings.Join(segments, "/") + "/" 647 // Check the current joined path so far. If we find a prefix, 648 // check permissions. If they're defined but not deny, success. 649 if strings.HasPrefix(joinedPath, path) { 650 permissions := a.segmentWildcardPaths[fullWCPath].(*ACLPermissions) 651 if permissions.CapabilitiesBitmap&DenyCapabilityInt == 0 && permissions.CapabilitiesBitmap > 0 { 652 return permissions 653 } 654 } 655 continue SWCPATH 656 } 657 } 658 pd.perms = a.segmentWildcardPaths[fullWCPath].(*ACLPermissions) 659 wcPathDescrs = append(wcPathDescrs, pd) 660 } 661 662 if bareMount || len(wcPathDescrs) == 0 { 663 return nil 664 } 665 666 // We don't do this in the bare mount check because we don't care about 667 // priority, we only care about any capability at all. 668 sort.Slice(wcPathDescrs, less) 669 670 return wcPathDescrs[len(wcPathDescrs)-1].perms 671} 672 673func (c *Core) performPolicyChecks(ctx context.Context, acl *ACL, te *logical.TokenEntry, req *logical.Request, inEntity *identity.Entity, opts *PolicyCheckOpts) *AuthResults { 674 ret := new(AuthResults) 675 676 // First, perform normal ACL checks if requested. The only time no ACL 677 // should be applied is if we are only processing EGPs against a login 678 // path in which case opts.Unauth will be set. 679 if acl != nil && !opts.Unauth { 680 ret.ACLResults = acl.AllowOperation(ctx, req, false) 681 ret.RootPrivs = ret.ACLResults.RootPrivs 682 // Root is always allowed; skip Sentinel/MFA checks 683 if ret.ACLResults.IsRoot { 684 //logger.Warn("token is root, skipping checks") 685 ret.Allowed = true 686 return ret 687 } 688 if !ret.ACLResults.Allowed { 689 return ret 690 } 691 if !ret.RootPrivs && opts.RootPrivsRequired { 692 return ret 693 } 694 } 695 696 c.performEntPolicyChecks(ctx, acl, te, req, inEntity, opts, ret) 697 698 return ret 699} 700 701func valueInParameterList(v interface{}, list []interface{}) bool { 702 // Empty list is equivalent to the item always existing in the list 703 if len(list) == 0 { 704 return true 705 } 706 707 return valueInSlice(v, list) 708} 709 710func valueInSlice(v interface{}, list []interface{}) bool { 711 for _, el := range list { 712 if el == nil || v == nil { 713 // It doesn't seem possible to set up a nil entry in the list, but it is possible 714 // to pass in a null entry in the API request being checked. Just in case, 715 // nil will match nil. 716 if el == v { 717 return true 718 } 719 } else if reflect.TypeOf(el).String() == "string" && reflect.TypeOf(v).String() == "string" { 720 item := el.(string) 721 val := v.(string) 722 723 if strutil.GlobbedStringsMatch(item, val) { 724 return true 725 } 726 } else if reflect.DeepEqual(el, v) { 727 return true 728 } 729 } 730 731 return false 732} 733