1
16
17
18
19
20
21
22
23 package authz
24
25 import (
26 "bytes"
27 "encoding/json"
28 "fmt"
29 "strings"
30
31 v1xdsudpatypepb "github.com/cncf/xds/go/udpa/type/v1"
32 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
33 v3rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
34 v3routepb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
35 v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
36 "google.golang.org/protobuf/types/known/anypb"
37 "google.golang.org/protobuf/types/known/structpb"
38 )
39
40
41
42 const typeURLPrefix = "grpc.authz.audit_logging/"
43
44 type header struct {
45 Key string
46 Values []string
47 }
48
49 type peer struct {
50 Principals []string
51 }
52
53 type request struct {
54 Paths []string
55 Headers []header
56 }
57
58 type rule struct {
59 Name string
60 Source peer
61 Request request
62 }
63
64 type auditLogger struct {
65 Name string `json:"name"`
66 Config *structpb.Struct `json:"config"`
67 IsOptional bool `json:"is_optional"`
68 }
69
70 type auditLoggingOptions struct {
71 AuditCondition string `json:"audit_condition"`
72 AuditLoggers []*auditLogger `json:"audit_loggers"`
73 }
74
75
76 type authorizationPolicy struct {
77 Name string
78 DenyRules []rule `json:"deny_rules"`
79 AllowRules []rule `json:"allow_rules"`
80 AuditLoggingOptions auditLoggingOptions `json:"audit_logging_options"`
81 }
82
83 func principalOr(principals []*v3rbacpb.Principal) *v3rbacpb.Principal {
84 return &v3rbacpb.Principal{
85 Identifier: &v3rbacpb.Principal_OrIds{
86 OrIds: &v3rbacpb.Principal_Set{
87 Ids: principals,
88 },
89 },
90 }
91 }
92
93 func permissionOr(permission []*v3rbacpb.Permission) *v3rbacpb.Permission {
94 return &v3rbacpb.Permission{
95 Rule: &v3rbacpb.Permission_OrRules{
96 OrRules: &v3rbacpb.Permission_Set{
97 Rules: permission,
98 },
99 },
100 }
101 }
102
103 func permissionAnd(permission []*v3rbacpb.Permission) *v3rbacpb.Permission {
104 return &v3rbacpb.Permission{
105 Rule: &v3rbacpb.Permission_AndRules{
106 AndRules: &v3rbacpb.Permission_Set{
107 Rules: permission,
108 },
109 },
110 }
111 }
112
113 func getStringMatcher(value string) *v3matcherpb.StringMatcher {
114 switch {
115 case value == "*":
116 return &v3matcherpb.StringMatcher{
117 MatchPattern: &v3matcherpb.StringMatcher_SafeRegex{
118 SafeRegex: &v3matcherpb.RegexMatcher{Regex: ".+"}},
119 }
120 case strings.HasSuffix(value, "*"):
121 prefix := strings.TrimSuffix(value, "*")
122 return &v3matcherpb.StringMatcher{
123 MatchPattern: &v3matcherpb.StringMatcher_Prefix{Prefix: prefix},
124 }
125 case strings.HasPrefix(value, "*"):
126 suffix := strings.TrimPrefix(value, "*")
127 return &v3matcherpb.StringMatcher{
128 MatchPattern: &v3matcherpb.StringMatcher_Suffix{Suffix: suffix},
129 }
130 default:
131 return &v3matcherpb.StringMatcher{
132 MatchPattern: &v3matcherpb.StringMatcher_Exact{Exact: value},
133 }
134 }
135 }
136
137 func getHeaderMatcher(key, value string) *v3routepb.HeaderMatcher {
138 switch {
139 case value == "*":
140 return &v3routepb.HeaderMatcher{
141 Name: key,
142 HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SafeRegexMatch{
143 SafeRegexMatch: &v3matcherpb.RegexMatcher{Regex: ".+"}},
144 }
145 case strings.HasSuffix(value, "*"):
146 prefix := strings.TrimSuffix(value, "*")
147 return &v3routepb.HeaderMatcher{
148 Name: key,
149 HeaderMatchSpecifier: &v3routepb.HeaderMatcher_PrefixMatch{PrefixMatch: prefix},
150 }
151 case strings.HasPrefix(value, "*"):
152 suffix := strings.TrimPrefix(value, "*")
153 return &v3routepb.HeaderMatcher{
154 Name: key,
155 HeaderMatchSpecifier: &v3routepb.HeaderMatcher_SuffixMatch{SuffixMatch: suffix},
156 }
157 default:
158 return &v3routepb.HeaderMatcher{
159 Name: key,
160 HeaderMatchSpecifier: &v3routepb.HeaderMatcher_ExactMatch{ExactMatch: value},
161 }
162 }
163 }
164
165 func parsePrincipalNames(principalNames []string) []*v3rbacpb.Principal {
166 ps := make([]*v3rbacpb.Principal, 0, len(principalNames))
167 for _, principalName := range principalNames {
168 newPrincipalName := &v3rbacpb.Principal{
169 Identifier: &v3rbacpb.Principal_Authenticated_{
170 Authenticated: &v3rbacpb.Principal_Authenticated{
171 PrincipalName: getStringMatcher(principalName),
172 },
173 }}
174 ps = append(ps, newPrincipalName)
175 }
176 return ps
177 }
178
179 func parsePeer(source peer) *v3rbacpb.Principal {
180 if len(source.Principals) == 0 {
181 return &v3rbacpb.Principal{
182 Identifier: &v3rbacpb.Principal_Any{
183 Any: true,
184 },
185 }
186 }
187 return principalOr(parsePrincipalNames(source.Principals))
188 }
189
190 func parsePaths(paths []string) []*v3rbacpb.Permission {
191 ps := make([]*v3rbacpb.Permission, 0, len(paths))
192 for _, path := range paths {
193 newPath := &v3rbacpb.Permission{
194 Rule: &v3rbacpb.Permission_UrlPath{
195 UrlPath: &v3matcherpb.PathMatcher{
196 Rule: &v3matcherpb.PathMatcher_Path{Path: getStringMatcher(path)}}}}
197 ps = append(ps, newPath)
198 }
199 return ps
200 }
201
202 func parseHeaderValues(key string, values []string) []*v3rbacpb.Permission {
203 vs := make([]*v3rbacpb.Permission, 0, len(values))
204 for _, value := range values {
205 newHeader := &v3rbacpb.Permission{
206 Rule: &v3rbacpb.Permission_Header{
207 Header: getHeaderMatcher(key, value)}}
208 vs = append(vs, newHeader)
209 }
210 return vs
211 }
212
213 var unsupportedHeaders = map[string]bool{
214 "host": true,
215 "connection": true,
216 "keep-alive": true,
217 "proxy-authenticate": true,
218 "proxy-authorization": true,
219 "te": true,
220 "trailer": true,
221 "transfer-encoding": true,
222 "upgrade": true,
223 }
224
225 func unsupportedHeader(key string) bool {
226 return key[0] == ':' || strings.HasPrefix(key, "grpc-") || unsupportedHeaders[key]
227 }
228
229 func parseHeaders(headers []header) ([]*v3rbacpb.Permission, error) {
230 hs := make([]*v3rbacpb.Permission, 0, len(headers))
231 for i, header := range headers {
232 if header.Key == "" {
233 return nil, fmt.Errorf(`"headers" %d: "key" is not present`, i)
234 }
235 header.Key = strings.ToLower(header.Key)
236 if unsupportedHeader(header.Key) {
237 return nil, fmt.Errorf(`"headers" %d: unsupported "key" %s`, i, header.Key)
238 }
239 if len(header.Values) == 0 {
240 return nil, fmt.Errorf(`"headers" %d: "values" is not present`, i)
241 }
242 values := parseHeaderValues(header.Key, header.Values)
243 hs = append(hs, permissionOr(values))
244 }
245 return hs, nil
246 }
247
248 func parseRequest(request request) (*v3rbacpb.Permission, error) {
249 var and []*v3rbacpb.Permission
250 if len(request.Paths) > 0 {
251 and = append(and, permissionOr(parsePaths(request.Paths)))
252 }
253 if len(request.Headers) > 0 {
254 headers, err := parseHeaders(request.Headers)
255 if err != nil {
256 return nil, err
257 }
258 and = append(and, permissionAnd(headers))
259 }
260 if len(and) > 0 {
261 return permissionAnd(and), nil
262 }
263 return &v3rbacpb.Permission{
264 Rule: &v3rbacpb.Permission_Any{
265 Any: true,
266 },
267 }, nil
268 }
269
270 func parseRules(rules []rule, prefixName string) (map[string]*v3rbacpb.Policy, error) {
271 policies := make(map[string]*v3rbacpb.Policy)
272 for i, rule := range rules {
273 if rule.Name == "" {
274 return policies, fmt.Errorf(`%d: "name" is not present`, i)
275 }
276 permission, err := parseRequest(rule.Request)
277 if err != nil {
278 return nil, fmt.Errorf("%d: %v", i, err)
279 }
280 policyName := prefixName + "_" + rule.Name
281 policies[policyName] = &v3rbacpb.Policy{
282 Principals: []*v3rbacpb.Principal{parsePeer(rule.Source)},
283 Permissions: []*v3rbacpb.Permission{permission},
284 }
285 }
286 return policies, nil
287 }
288
289
290
291
292 func (options *auditLoggingOptions) toProtos() (allow *v3rbacpb.RBAC_AuditLoggingOptions, deny *v3rbacpb.RBAC_AuditLoggingOptions, err error) {
293 allow = &v3rbacpb.RBAC_AuditLoggingOptions{}
294 deny = &v3rbacpb.RBAC_AuditLoggingOptions{}
295
296 if options.AuditCondition != "" {
297 rbacCondition, ok := v3rbacpb.RBAC_AuditLoggingOptions_AuditCondition_value[options.AuditCondition]
298 if !ok {
299 return nil, nil, fmt.Errorf("failed to parse AuditCondition %v. Allowed values {NONE, ON_DENY, ON_ALLOW, ON_DENY_AND_ALLOW}", options.AuditCondition)
300 }
301 allow.AuditCondition = v3rbacpb.RBAC_AuditLoggingOptions_AuditCondition(rbacCondition)
302 deny.AuditCondition = toDenyCondition(v3rbacpb.RBAC_AuditLoggingOptions_AuditCondition(rbacCondition))
303 }
304
305 for i, config := range options.AuditLoggers {
306 if config.Name == "" {
307 return nil, nil, fmt.Errorf("missing required field: name in audit_logging_options.audit_loggers[%v]", i)
308 }
309 if config.Config == nil {
310 config.Config = &structpb.Struct{}
311 }
312 typedStruct := &v1xdsudpatypepb.TypedStruct{
313 TypeUrl: typeURLPrefix + config.Name,
314 Value: config.Config,
315 }
316 customConfig, err := anypb.New(typedStruct)
317 if err != nil {
318 return nil, nil, fmt.Errorf("error parsing custom audit logger config: %v", err)
319 }
320
321 logger := &v3corepb.TypedExtensionConfig{Name: config.Name, TypedConfig: customConfig}
322 rbacConfig := v3rbacpb.RBAC_AuditLoggingOptions_AuditLoggerConfig{
323 IsOptional: config.IsOptional,
324 AuditLogger: logger,
325 }
326 allow.LoggerConfigs = append(allow.LoggerConfigs, &rbacConfig)
327 deny.LoggerConfigs = append(deny.LoggerConfigs, &rbacConfig)
328 }
329
330 return allow, deny, nil
331 }
332
333
334
335 func toDenyCondition(condition v3rbacpb.RBAC_AuditLoggingOptions_AuditCondition) v3rbacpb.RBAC_AuditLoggingOptions_AuditCondition {
336
337
338
339
340
341
342
343
344 switch condition {
345 case v3rbacpb.RBAC_AuditLoggingOptions_NONE:
346 return v3rbacpb.RBAC_AuditLoggingOptions_NONE
347 case v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY:
348 return v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY
349 case v3rbacpb.RBAC_AuditLoggingOptions_ON_ALLOW:
350 return v3rbacpb.RBAC_AuditLoggingOptions_NONE
351 case v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY_AND_ALLOW:
352 return v3rbacpb.RBAC_AuditLoggingOptions_ON_DENY
353 default:
354 return v3rbacpb.RBAC_AuditLoggingOptions_NONE
355 }
356 }
357
358
359
360
361
362 func translatePolicy(policyStr string) ([]*v3rbacpb.RBAC, string, error) {
363 policy := &authorizationPolicy{}
364 d := json.NewDecoder(bytes.NewReader([]byte(policyStr)))
365 d.DisallowUnknownFields()
366 if err := d.Decode(policy); err != nil {
367 return nil, "", fmt.Errorf("failed to unmarshal policy: %v", err)
368 }
369 if policy.Name == "" {
370 return nil, "", fmt.Errorf(`"name" is not present`)
371 }
372 if len(policy.AllowRules) == 0 {
373 return nil, "", fmt.Errorf(`"allow_rules" is not present`)
374 }
375 allowLogger, denyLogger, err := policy.AuditLoggingOptions.toProtos()
376 if err != nil {
377 return nil, "", err
378 }
379 rbacs := make([]*v3rbacpb.RBAC, 0, 2)
380 if len(policy.DenyRules) > 0 {
381 denyPolicies, err := parseRules(policy.DenyRules, policy.Name)
382 if err != nil {
383 return nil, "", fmt.Errorf(`"deny_rules" %v`, err)
384 }
385 denyRBAC := &v3rbacpb.RBAC{
386 Action: v3rbacpb.RBAC_DENY,
387 Policies: denyPolicies,
388 AuditLoggingOptions: denyLogger,
389 }
390 rbacs = append(rbacs, denyRBAC)
391 }
392 allowPolicies, err := parseRules(policy.AllowRules, policy.Name)
393 if err != nil {
394 return nil, "", fmt.Errorf(`"allow_rules" %v`, err)
395 }
396 allowRBAC := &v3rbacpb.RBAC{Action: v3rbacpb.RBAC_ALLOW, Policies: allowPolicies, AuditLoggingOptions: allowLogger}
397 return append(rbacs, allowRBAC), policy.Name, nil
398 }
399
View as plain text