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