blob: 41c6b1a491fd767db06b3651841f3a8b081b6f1b [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package policy
18
19import (
20 "strings"
21
22 "k8s.io/apiserver/pkg/apis/audit"
23 "k8s.io/apiserver/pkg/authorization/authorizer"
24)
25
26const (
27 // DefaultAuditLevel is the default level to audit at, if no policy rules are matched.
28 DefaultAuditLevel = audit.LevelNone
29)
30
31// Checker exposes methods for checking the policy rules.
32type Checker interface {
33 // Check the audit level for a request with the given authorizer attributes.
34 LevelAndStages(authorizer.Attributes) (audit.Level, []audit.Stage)
35}
36
37// NewChecker creates a new policy checker.
38func NewChecker(policy *audit.Policy) Checker {
39 for i, rule := range policy.Rules {
40 policy.Rules[i].OmitStages = unionStages(policy.OmitStages, rule.OmitStages)
41 }
42 return &policyChecker{*policy}
43}
44
45func unionStages(stageLists ...[]audit.Stage) []audit.Stage {
46 m := make(map[audit.Stage]bool)
47 for _, sl := range stageLists {
48 for _, s := range sl {
49 m[s] = true
50 }
51 }
52 result := make([]audit.Stage, 0, len(m))
53 for key := range m {
54 result = append(result, key)
55 }
56 return result
57}
58
59// FakeChecker creates a checker that returns a constant level for all requests (for testing).
60func FakeChecker(level audit.Level, stage []audit.Stage) Checker {
61 return &fakeChecker{level, stage}
62}
63
64type policyChecker struct {
65 audit.Policy
66}
67
68func (p *policyChecker) LevelAndStages(attrs authorizer.Attributes) (audit.Level, []audit.Stage) {
69 for _, rule := range p.Rules {
70 if ruleMatches(&rule, attrs) {
71 return rule.Level, rule.OmitStages
72 }
73 }
74 return DefaultAuditLevel, p.OmitStages
75}
76
77// Check whether the rule matches the request attrs.
78func ruleMatches(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
79 user := attrs.GetUser()
80 if len(r.Users) > 0 {
81 if user == nil || !hasString(r.Users, user.GetName()) {
82 return false
83 }
84 }
85 if len(r.UserGroups) > 0 {
86 if user == nil {
87 return false
88 }
89 matched := false
90 for _, group := range user.GetGroups() {
91 if hasString(r.UserGroups, group) {
92 matched = true
93 break
94 }
95 }
96 if !matched {
97 return false
98 }
99 }
100 if len(r.Verbs) > 0 {
101 if !hasString(r.Verbs, attrs.GetVerb()) {
102 return false
103 }
104 }
105
106 if len(r.Namespaces) > 0 || len(r.Resources) > 0 {
107 return ruleMatchesResource(r, attrs)
108 }
109
110 if len(r.NonResourceURLs) > 0 {
111 return ruleMatchesNonResource(r, attrs)
112 }
113
114 return true
115}
116
117// Check whether the rule's non-resource URLs match the request attrs.
118func ruleMatchesNonResource(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
119 if attrs.IsResourceRequest() {
120 return false
121 }
122
123 path := attrs.GetPath()
124 for _, spec := range r.NonResourceURLs {
125 if pathMatches(path, spec) {
126 return true
127 }
128 }
129
130 return false
131}
132
133// Check whether the path matches the path specification.
134func pathMatches(path, spec string) bool {
135 // Allow wildcard match
136 if spec == "*" {
137 return true
138 }
139 // Allow exact match
140 if spec == path {
141 return true
142 }
143 // Allow a trailing * subpath match
144 if strings.HasSuffix(spec, "*") && strings.HasPrefix(path, strings.TrimRight(spec, "*")) {
145 return true
146 }
147 return false
148}
149
150// Check whether the rule's resource fields match the request attrs.
151func ruleMatchesResource(r *audit.PolicyRule, attrs authorizer.Attributes) bool {
152 if !attrs.IsResourceRequest() {
153 return false
154 }
155
156 if len(r.Namespaces) > 0 {
157 if !hasString(r.Namespaces, attrs.GetNamespace()) { // Non-namespaced resources use the empty string.
158 return false
159 }
160 }
161 if len(r.Resources) == 0 {
162 return true
163 }
164
165 apiGroup := attrs.GetAPIGroup()
166 resource := attrs.GetResource()
167 subresource := attrs.GetSubresource()
168 combinedResource := resource
169 // If subresource, the resource in the policy must match "(resource)/(subresource)"
170 if subresource != "" {
171 combinedResource = resource + "/" + subresource
172 }
173
174 name := attrs.GetName()
175
176 for _, gr := range r.Resources {
177 if gr.Group == apiGroup {
178 if len(gr.Resources) == 0 {
179 return true
180 }
181 for _, res := range gr.Resources {
182 if len(gr.ResourceNames) == 0 || hasString(gr.ResourceNames, name) {
183 // match "*"
184 if res == combinedResource || res == "*" {
185 return true
186 }
187 // match "*/subresource"
188 if len(subresource) > 0 && strings.HasPrefix(res, "*/") && subresource == strings.TrimLeft(res, "*/") {
189 return true
190 }
191 // match "resource/*"
192 if strings.HasSuffix(res, "/*") && resource == strings.TrimRight(res, "/*") {
193 return true
194 }
195 }
196 }
197 }
198 }
199 return false
200}
201
202// Utility function to check whether a string slice contains a string.
203func hasString(slice []string, value string) bool {
204 for _, s := range slice {
205 if s == value {
206 return true
207 }
208 }
209 return false
210}
211
212type fakeChecker struct {
213 level audit.Level
214 stage []audit.Stage
215}
216
217func (f *fakeChecker) LevelAndStages(_ authorizer.Attributes) (audit.Level, []audit.Stage) {
218 return f.level, f.stage
219}