policy.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. package policy
  2. import (
  3. "github.com/porter-dev/porter/api/types"
  4. )
  5. // HasScopeAccess checks that a user can perform an action (`verb`) against a specific
  6. // resource (`resource+scope`) according to a `policy`.
  7. func HasScopeAccess(
  8. policy []*types.PolicyDocument,
  9. reqScopes map[types.PermissionScope]*types.RequestAction,
  10. ) bool {
  11. // iterate through policy documents until a match is found
  12. for _, policyDoc := range policy {
  13. // check that policy document is valid for current API server
  14. isValid, matchDocs := populateAndVerifyPolicyDocument(
  15. policyDoc,
  16. types.ScopeHeirarchy,
  17. types.ProjectScope,
  18. types.ReadWriteVerbGroup(),
  19. reqScopes,
  20. nil,
  21. )
  22. if !isValid {
  23. continue
  24. }
  25. for matchScope, matchDoc := range matchDocs {
  26. // for the matching scope, make sure it matches the allowed resources if the
  27. // resource list is explicitly set
  28. if len(matchDoc.Resources) > 0 && reqScopes[matchScope].Verb != types.APIVerbList {
  29. if !isResourceAllowed(matchDoc, reqScopes[matchScope].Resource) {
  30. isValid = false
  31. }
  32. }
  33. // for the matching scope, make sure it matches the allowed verbs
  34. if !isVerbAllowed(matchDoc, reqScopes[matchScope].Verb) {
  35. isValid = false
  36. }
  37. }
  38. if isValid {
  39. return true
  40. }
  41. }
  42. return false
  43. }
  44. func isResourceAllowed(
  45. matchDoc *types.PolicyDocument,
  46. resource types.NameOrUInt,
  47. ) bool {
  48. valid := false
  49. for _, allowedResource := range matchDoc.Resources {
  50. if allowedResource == resource {
  51. valid = true
  52. break
  53. }
  54. }
  55. return valid
  56. }
  57. func isVerbAllowed(
  58. matchDoc *types.PolicyDocument,
  59. verb types.APIVerb,
  60. ) bool {
  61. valid := false
  62. for _, allowedVerb := range matchDoc.Verbs {
  63. if allowedVerb == verb {
  64. valid = true
  65. }
  66. }
  67. return valid
  68. }
  69. // populateAndVerifyPolicyDocument makes sure that the policy document is valid, and populates
  70. // the policy document with values based on the parent permissions. Since we only want to
  71. // iterate through the PolicyDocument once, we also search for a matching doc and return it.
  72. // See test cases for examples.
  73. func populateAndVerifyPolicyDocument(
  74. policyDoc *types.PolicyDocument,
  75. tree types.ScopeTree,
  76. currScope types.PermissionScope,
  77. parentVerbs []types.APIVerb,
  78. reqScopes map[types.PermissionScope]*types.RequestAction,
  79. currMatchDocs map[types.PermissionScope]*types.PolicyDocument,
  80. ) (ok bool, matchDocs map[types.PermissionScope]*types.PolicyDocument) {
  81. if currMatchDocs == nil {
  82. currMatchDocs = make(map[types.PermissionScope]*types.PolicyDocument)
  83. }
  84. matchDocs = currMatchDocs
  85. currDoc := policyDoc
  86. if policyDoc == nil {
  87. currDoc = &types.PolicyDocument{
  88. Scope: currScope,
  89. // we only set the verbs to the parentVerbs when the policy document is nil
  90. // in the first place. We don't case on res.Verbs being empty, since this
  91. // may be desired.
  92. Verbs: parentVerbs,
  93. }
  94. }
  95. subTree, ok := tree[currDoc.Scope]
  96. if !ok || currDoc.Scope != currScope {
  97. return false, matchDocs
  98. }
  99. processedChildren := 0
  100. // by default, we pass the parent's verbs to the child.
  101. passedParentVerbs := currDoc.Verbs
  102. // however, if the current scope is a project scope, we don't pass the parent's verbs to
  103. // the child. This is to avoid additional verbs being added later as a child of the ProjectScope,
  104. // which would unintentionally grant permission to future-added scopes.
  105. if currScope == types.ProjectScope {
  106. passedParentVerbs = []types.APIVerb{}
  107. }
  108. for currScope := range subTree {
  109. if _, exists := currDoc.Children[currScope]; exists {
  110. processedChildren++
  111. }
  112. ok, matchDocs = populateAndVerifyPolicyDocument(
  113. currDoc.Children[currScope],
  114. subTree,
  115. currScope,
  116. passedParentVerbs,
  117. reqScopes,
  118. matchDocs,
  119. )
  120. if !ok {
  121. break
  122. }
  123. }
  124. // make sure all children of the current document were actually processed: if not,
  125. // the policy document is invalid
  126. if processedChildren != len(currDoc.Children) {
  127. return false, matchDocs
  128. }
  129. if _, ok := reqScopes[currScope]; ok && currDoc.Scope == currScope {
  130. matchDocs[currScope] = currDoc
  131. }
  132. return ok, matchDocs
  133. }