dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/httpfilter/rbac/rbac.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* 19 * 20 * Copyright 2021 gRPC authors. 21 * 22 */ 23 24 // Package rbac implements the Envoy RBAC HTTP filter. 25 package rbac 26 27 import ( 28 "context" 29 "errors" 30 "fmt" 31 "strings" 32 ) 33 34 import ( 35 v3rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3" 36 rpb "github.com/envoyproxy/go-control-plane/envoy/extensions/filters/http/rbac/v3" 37 38 "github.com/golang/protobuf/proto" 39 "github.com/golang/protobuf/ptypes" 40 41 "google.golang.org/protobuf/types/known/anypb" 42 ) 43 44 import ( 45 "dubbo.apache.org/dubbo-go/v3/xds/httpfilter" 46 "dubbo.apache.org/dubbo-go/v3/xds/utils/envconfig" 47 "dubbo.apache.org/dubbo-go/v3/xds/utils/rbac" 48 "dubbo.apache.org/dubbo-go/v3/xds/utils/resolver" 49 ) 50 51 func init() { 52 if envconfig.XDSRBAC { 53 httpfilter.Register(builder{}) 54 } 55 } 56 57 // RegisterForTesting registers the RBAC HTTP Filter for testing purposes, regardless 58 // of the RBAC environment variable. This is needed because there is no way to set the RBAC 59 // environment variable to true in a test before init() in this package is run. 60 func RegisterForTesting() { 61 httpfilter.Register(builder{}) 62 } 63 64 // UnregisterForTesting unregisters the RBAC HTTP Filter for testing purposes. This is needed because 65 // there is no way to unregister the HTTP Filter after registering it solely for testing purposes using 66 // rbac.RegisterForTesting() 67 func UnregisterForTesting() { 68 for _, typeURL := range builder.TypeURLs(builder{}) { 69 httpfilter.UnregisterForTesting(typeURL) 70 } 71 } 72 73 type builder struct { 74 } 75 76 type config struct { 77 httpfilter.FilterConfig 78 chainEngine *rbac.ChainEngine 79 } 80 81 func (builder) TypeURLs() []string { 82 return []string{ 83 "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC", 84 "type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute", 85 } 86 } 87 88 // Parsing is the same for the base config and the override config. 89 func parseConfig(rbacCfg *rpb.RBAC) (httpfilter.FilterConfig, error) { 90 // All the validation logic described in A41. 91 for _, policy := range rbacCfg.GetRules().GetPolicies() { 92 // "Policy.condition and Policy.checked_condition must cause a 93 // validation failure if present." - A41 94 if policy.Condition != nil { 95 return nil, errors.New("rbac: Policy.condition is present") 96 } 97 if policy.CheckedCondition != nil { 98 return nil, errors.New("rbac: policy.CheckedCondition is present") 99 } 100 101 // "It is also a validation failure if Permission or Principal has a 102 // header matcher for a grpc- prefixed header name or :scheme." - A41 103 for _, principal := range policy.Principals { 104 name := principal.GetHeader().GetName() 105 if name == ":scheme" || strings.HasPrefix(name, "grpc-") { 106 return nil, fmt.Errorf("rbac: principal header matcher for %v is :scheme or starts with grpc", name) 107 } 108 } 109 for _, permission := range policy.Permissions { 110 name := permission.GetHeader().GetName() 111 if name == ":scheme" || strings.HasPrefix(name, "grpc-") { 112 return nil, fmt.Errorf("rbac: permission header matcher for %v is :scheme or starts with grpc", name) 113 } 114 } 115 } 116 117 // "Envoy aliases :authority and Host in its header map implementation, so 118 // they should be treated equivalent for the RBAC matchers; there must be no 119 // behavior change depending on which of the two header names is used in the 120 // RBAC policy." - A41. Loop through config's principals and policies, change 121 // any header matcher with value "host" to :authority", as that is what 122 // grpc-go shifts both headers to in transport layer. 123 for _, policy := range rbacCfg.GetRules().GetPolicies() { 124 for _, principal := range policy.Principals { 125 if principal.GetHeader().GetName() == "host" { 126 principal.GetHeader().Name = ":authority" 127 } 128 } 129 for _, permission := range policy.Permissions { 130 if permission.GetHeader().GetName() == "host" { 131 permission.GetHeader().Name = ":authority" 132 } 133 } 134 } 135 136 // Two cases where this HTTP Filter is a no op: 137 // "If absent, no enforcing RBAC policy will be applied" - RBAC 138 // Documentation for Rules field. 139 // "At this time, if the RBAC.action is Action.LOG then the policy will be 140 // completely ignored, as if RBAC was not configurated." - A41 141 if rbacCfg.Rules == nil || rbacCfg.GetRules().GetAction() == v3rbacpb.RBAC_LOG { 142 return config{}, nil 143 } 144 145 ce, err := rbac.NewChainEngine([]*v3rbacpb.RBAC{rbacCfg.GetRules()}) 146 if err != nil { 147 // "At this time, if the RBAC.action is Action.LOG then the policy will be 148 // completely ignored, as if RBAC was not configurated." - A41 149 if rbacCfg.GetRules().GetAction() != v3rbacpb.RBAC_LOG { 150 return nil, fmt.Errorf("rbac: error constructing matching engine: %v", err) 151 } 152 } 153 154 return config{chainEngine: ce}, nil 155 } 156 157 func (builder) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) { 158 if cfg == nil { 159 return nil, fmt.Errorf("rbac: nil configuration message provided") 160 } 161 any, ok := cfg.(*anypb.Any) 162 if !ok { 163 return nil, fmt.Errorf("rbac: error parsing config %v: unknown type %T", cfg, cfg) 164 } 165 msg := new(rpb.RBAC) 166 if err := ptypes.UnmarshalAny(any, msg); err != nil { 167 return nil, fmt.Errorf("rbac: error parsing config %v: %v", cfg, err) 168 } 169 return parseConfig(msg) 170 } 171 172 func (builder) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) { 173 if override == nil { 174 return nil, fmt.Errorf("rbac: nil configuration message provided") 175 } 176 any, ok := override.(*anypb.Any) 177 if !ok { 178 return nil, fmt.Errorf("rbac: error parsing override config %v: unknown type %T", override, override) 179 } 180 msg := new(rpb.RBACPerRoute) 181 if err := ptypes.UnmarshalAny(any, msg); err != nil { 182 return nil, fmt.Errorf("rbac: error parsing override config %v: %v", override, err) 183 } 184 return parseConfig(msg.Rbac) 185 } 186 187 func (builder) IsTerminal() bool { 188 return false 189 } 190 191 var _ httpfilter.ServerInterceptorBuilder = builder{} 192 193 // BuildServerInterceptor is an optional interface builder implements in order 194 // to signify it works server side. 195 func (builder) BuildServerInterceptor(cfg httpfilter.FilterConfig, override httpfilter.FilterConfig) (resolver.ServerInterceptor, error) { 196 if cfg == nil { 197 return nil, fmt.Errorf("rbac: nil config provided") 198 } 199 200 c, ok := cfg.(config) 201 if !ok { 202 return nil, fmt.Errorf("rbac: incorrect config type provided (%T): %v", cfg, cfg) 203 } 204 205 if override != nil { 206 // override completely replaces the listener configuration; but we 207 // still validate the listener config type. 208 c, ok = override.(config) 209 if !ok { 210 return nil, fmt.Errorf("rbac: incorrect override config type provided (%T): %v", override, override) 211 } 212 } 213 214 // RBAC HTTP Filter is a no op from one of these two cases: 215 // "If absent, no enforcing RBAC policy will be applied" - RBAC 216 // Documentation for Rules field. 217 // "At this time, if the RBAC.action is Action.LOG then the policy will be 218 // completely ignored, as if RBAC was not configurated." - A41 219 if c.chainEngine == nil { 220 return nil, nil 221 } 222 return &interceptor{chainEngine: c.chainEngine}, nil 223 } 224 225 type interceptor struct { 226 chainEngine *rbac.ChainEngine 227 } 228 229 func (i *interceptor) AllowRPC(ctx context.Context) error { 230 return i.chainEngine.IsAuthorized(ctx) 231 }