1
16
17
18 package abac
19
20 import (
21 "bufio"
22 "context"
23 "fmt"
24 "os"
25 "strings"
26
27 "k8s.io/klog/v2"
28
29 "k8s.io/apimachinery/pkg/runtime"
30 "k8s.io/apiserver/pkg/authentication/user"
31 "k8s.io/apiserver/pkg/authorization/authorizer"
32 "k8s.io/kubernetes/pkg/apis/abac"
33
34
35 _ "k8s.io/kubernetes/pkg/apis/abac/latest"
36 "k8s.io/kubernetes/pkg/apis/abac/v0"
37 )
38
39 type policyLoadError struct {
40 path string
41 line int
42 data []byte
43 err error
44 }
45
46 func (p policyLoadError) Error() string {
47 if p.line >= 0 {
48 return fmt.Sprintf("error reading policy file %s, line %d: %s: %v", p.path, p.line, string(p.data), p.err)
49 }
50 return fmt.Sprintf("error reading policy file %s: %v", p.path, p.err)
51 }
52
53
54 type PolicyList []*abac.Policy
55
56
57
58
59 func NewFromFile(path string) (PolicyList, error) {
60
61
62 file, err := os.Open(path)
63 if err != nil {
64 return nil, err
65 }
66 defer file.Close()
67
68 scanner := bufio.NewScanner(file)
69 pl := make(PolicyList, 0)
70
71 decoder := abac.Codecs.UniversalDecoder()
72
73 i := 0
74 unversionedLines := 0
75 for scanner.Scan() {
76 i++
77 p := &abac.Policy{}
78 b := scanner.Bytes()
79
80
81 trimmed := strings.TrimSpace(string(b))
82 if len(trimmed) == 0 || strings.HasPrefix(trimmed, "#") {
83 continue
84 }
85
86 decodedObj, _, err := decoder.Decode(b, nil, nil)
87 if err != nil {
88 if !(runtime.IsMissingVersion(err) || runtime.IsMissingKind(err) || runtime.IsNotRegisteredError(err)) {
89 return nil, policyLoadError{path, i, b, err}
90 }
91 unversionedLines++
92
93 oldPolicy := &v0.Policy{}
94 if err := runtime.DecodeInto(decoder, b, oldPolicy); err != nil {
95 return nil, policyLoadError{path, i, b, err}
96 }
97 if err := abac.Scheme.Convert(oldPolicy, p, nil); err != nil {
98 return nil, policyLoadError{path, i, b, err}
99 }
100 pl = append(pl, p)
101 continue
102 }
103
104 decodedPolicy, ok := decodedObj.(*abac.Policy)
105 if !ok {
106 return nil, policyLoadError{path, i, b, fmt.Errorf("unrecognized object: %#v", decodedObj)}
107 }
108 pl = append(pl, decodedPolicy)
109 }
110
111 if unversionedLines > 0 {
112 klog.Warningf("Policy file %s contained unversioned rules. See docs/admin/authorization.md#abac-mode for ABAC file format details.", path)
113 }
114
115 if err := scanner.Err(); err != nil {
116 return nil, policyLoadError{path, -1, nil, err}
117 }
118 return pl, nil
119 }
120
121 func matches(p abac.Policy, a authorizer.Attributes) bool {
122 if subjectMatches(p, a.GetUser()) {
123 if verbMatches(p, a) {
124
125 if resourceMatches(p, a) {
126 return true
127 }
128 if nonResourceMatches(p, a) {
129 return true
130 }
131 }
132 }
133 return false
134 }
135
136
137 func subjectMatches(p abac.Policy, user user.Info) bool {
138 matched := false
139
140 if user == nil {
141 return false
142 }
143 username := user.GetName()
144 groups := user.GetGroups()
145
146
147 if len(p.Spec.User) > 0 {
148 if p.Spec.User == "*" {
149 matched = true
150 } else {
151 matched = p.Spec.User == username
152 if !matched {
153 return false
154 }
155 }
156 }
157
158
159 if len(p.Spec.Group) > 0 {
160 if p.Spec.Group == "*" {
161 matched = true
162 } else {
163 matched = false
164 for _, group := range groups {
165 if p.Spec.Group == group {
166 matched = true
167 break
168 }
169 }
170 if !matched {
171 return false
172 }
173 }
174 }
175
176 return matched
177 }
178
179 func verbMatches(p abac.Policy, a authorizer.Attributes) bool {
180
181
182
183 if a.IsReadOnly() {
184 return true
185 }
186
187
188 if !p.Spec.Readonly {
189 return true
190 }
191
192 return false
193 }
194
195 func nonResourceMatches(p abac.Policy, a authorizer.Attributes) bool {
196
197 if !a.IsResourceRequest() {
198
199 if p.Spec.NonResourcePath == "*" {
200 return true
201 }
202
203 if p.Spec.NonResourcePath == a.GetPath() {
204 return true
205 }
206
207 if strings.HasSuffix(p.Spec.NonResourcePath, "*") && strings.HasPrefix(a.GetPath(), strings.TrimRight(p.Spec.NonResourcePath, "*")) {
208 return true
209 }
210 }
211 return false
212 }
213
214 func resourceMatches(p abac.Policy, a authorizer.Attributes) bool {
215
216 if a.IsResourceRequest() {
217 if p.Spec.Namespace == "*" || p.Spec.Namespace == a.GetNamespace() {
218 if p.Spec.Resource == "*" || p.Spec.Resource == a.GetResource() {
219 if p.Spec.APIGroup == "*" || p.Spec.APIGroup == a.GetAPIGroup() {
220 return true
221 }
222 }
223 }
224 }
225 return false
226 }
227
228
229 func (pl PolicyList) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
230 for _, p := range pl {
231 if matches(*p, a) {
232 return authorizer.DecisionAllow, "", nil
233 }
234 }
235 return authorizer.DecisionNoOpinion, "No policy matched.", nil
236
237
238
239 }
240
241
242 func (pl PolicyList) RulesFor(user user.Info, namespace string) ([]authorizer.ResourceRuleInfo, []authorizer.NonResourceRuleInfo, bool, error) {
243 var (
244 resourceRules []authorizer.ResourceRuleInfo
245 nonResourceRules []authorizer.NonResourceRuleInfo
246 )
247
248 for _, p := range pl {
249 if subjectMatches(*p, user) {
250 if p.Spec.Namespace == "*" || p.Spec.Namespace == namespace {
251 if len(p.Spec.Resource) > 0 {
252 r := authorizer.DefaultResourceRuleInfo{
253 Verbs: getVerbs(p.Spec.Readonly),
254 APIGroups: []string{p.Spec.APIGroup},
255 Resources: []string{p.Spec.Resource},
256 }
257 var resourceRule authorizer.ResourceRuleInfo = &r
258 resourceRules = append(resourceRules, resourceRule)
259 }
260 if len(p.Spec.NonResourcePath) > 0 {
261 r := authorizer.DefaultNonResourceRuleInfo{
262 Verbs: getVerbs(p.Spec.Readonly),
263 NonResourceURLs: []string{p.Spec.NonResourcePath},
264 }
265 var nonResourceRule authorizer.NonResourceRuleInfo = &r
266 nonResourceRules = append(nonResourceRules, nonResourceRule)
267 }
268 }
269 }
270 }
271 return resourceRules, nonResourceRules, false, nil
272 }
273
274 func getVerbs(isReadOnly bool) []string {
275 if isReadOnly {
276 return []string{"get", "list", "watch"}
277 }
278 return []string{"*"}
279 }
280
View as plain text