dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/utils/rbac/matchers.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 25 26 import ( 27 "errors" 28 "fmt" 29 "net" 30 "regexp" 31 ) 32 33 import ( 34 v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3" 35 v3rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3" 36 v3route_componentspb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3" 37 v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3" 38 ) 39 40 import ( 41 internalmatcher "dubbo.apache.org/dubbo-go/v3/xds/utils/matcher" 42 ) 43 44 // matcher is an interface that takes data about incoming RPC's and returns 45 // whether it matches with whatever matcher implements this interface. 46 type matcher interface { 47 match(data *rpcData) bool 48 } 49 50 // policyMatcher helps determine whether an incoming RPC call matches a policy. 51 // A policy is a logical role (e.g. Service Admin), which is comprised of 52 // permissions and principals. A principal is an identity (or identities) for a 53 // downstream subject which are assigned the policy (role), and a permission is 54 // an action(s) that a principal(s) can take. A policy matches if both a 55 // permission and a principal match, which will be determined by the child or 56 // permissions and principal matchers. policyMatcher implements the matcher 57 // interface. 58 type policyMatcher struct { 59 permissions *orMatcher 60 principals *orMatcher 61 } 62 63 func newPolicyMatcher(policy *v3rbacpb.Policy) (*policyMatcher, error) { 64 permissions, err := matchersFromPermissions(policy.Permissions) 65 if err != nil { 66 return nil, err 67 } 68 principals, err := matchersFromPrincipals(policy.Principals) 69 if err != nil { 70 return nil, err 71 } 72 return &policyMatcher{ 73 permissions: &orMatcher{matchers: permissions}, 74 principals: &orMatcher{matchers: principals}, 75 }, nil 76 } 77 78 func (pm *policyMatcher) match(data *rpcData) bool { 79 // A policy matches if and only if at least one of its permissions match the 80 // action taking place AND at least one if its principals match the 81 // downstream peer. 82 return pm.permissions.match(data) && pm.principals.match(data) 83 } 84 85 // matchersFromPermissions takes a list of permissions (can also be 86 // a single permission, e.g. from a not matcher which is logically !permission) 87 // and returns a list of matchers which correspond to that permission. This will 88 // be called in many instances throughout the initial construction of the RBAC 89 // engine from the AND and OR matchers and also from the NOT matcher. 90 func matchersFromPermissions(permissions []*v3rbacpb.Permission) ([]matcher, error) { 91 var matchers []matcher 92 for _, permission := range permissions { 93 switch permission.GetRule().(type) { 94 case *v3rbacpb.Permission_AndRules: 95 mList, err := matchersFromPermissions(permission.GetAndRules().Rules) 96 if err != nil { 97 return nil, err 98 } 99 matchers = append(matchers, &andMatcher{matchers: mList}) 100 case *v3rbacpb.Permission_OrRules: 101 mList, err := matchersFromPermissions(permission.GetOrRules().Rules) 102 if err != nil { 103 return nil, err 104 } 105 matchers = append(matchers, &orMatcher{matchers: mList}) 106 case *v3rbacpb.Permission_Any: 107 matchers = append(matchers, &alwaysMatcher{}) 108 case *v3rbacpb.Permission_Header: 109 m, err := newHeaderMatcher(permission.GetHeader()) 110 if err != nil { 111 return nil, err 112 } 113 matchers = append(matchers, m) 114 case *v3rbacpb.Permission_UrlPath: 115 m, err := newURLPathMatcher(permission.GetUrlPath()) 116 if err != nil { 117 return nil, err 118 } 119 matchers = append(matchers, m) 120 case *v3rbacpb.Permission_DestinationIp: 121 // Due to this being on server side, the destination IP is the local 122 // IP. 123 m, err := newLocalIPMatcher(permission.GetDestinationIp()) 124 if err != nil { 125 return nil, err 126 } 127 matchers = append(matchers, m) 128 case *v3rbacpb.Permission_DestinationPort: 129 matchers = append(matchers, newPortMatcher(permission.GetDestinationPort())) 130 case *v3rbacpb.Permission_NotRule: 131 mList, err := matchersFromPermissions([]*v3rbacpb.Permission{{Rule: permission.GetNotRule().Rule}}) 132 if err != nil { 133 return nil, err 134 } 135 matchers = append(matchers, ¬Matcher{matcherToNot: mList[0]}) 136 case *v3rbacpb.Permission_Metadata: 137 // Not supported in gRPC RBAC currently - a permission typed as 138 // Metadata in the initial config will be a no-op. 139 case *v3rbacpb.Permission_RequestedServerName: 140 // Not supported in gRPC RBAC currently - a permission typed as 141 // requested server name in the initial config will be a no-op. 142 } 143 } 144 return matchers, nil 145 } 146 147 func matchersFromPrincipals(principals []*v3rbacpb.Principal) ([]matcher, error) { 148 var matchers []matcher 149 for _, principal := range principals { 150 switch principal.GetIdentifier().(type) { 151 case *v3rbacpb.Principal_AndIds: 152 mList, err := matchersFromPrincipals(principal.GetAndIds().Ids) 153 if err != nil { 154 return nil, err 155 } 156 matchers = append(matchers, &andMatcher{matchers: mList}) 157 case *v3rbacpb.Principal_OrIds: 158 mList, err := matchersFromPrincipals(principal.GetOrIds().Ids) 159 if err != nil { 160 return nil, err 161 } 162 matchers = append(matchers, &orMatcher{matchers: mList}) 163 case *v3rbacpb.Principal_Any: 164 matchers = append(matchers, &alwaysMatcher{}) 165 case *v3rbacpb.Principal_Authenticated_: 166 authenticatedMatcher, err := newAuthenticatedMatcher(principal.GetAuthenticated()) 167 if err != nil { 168 return nil, err 169 } 170 matchers = append(matchers, authenticatedMatcher) 171 case *v3rbacpb.Principal_DirectRemoteIp: 172 m, err := newRemoteIPMatcher(principal.GetDirectRemoteIp()) 173 if err != nil { 174 return nil, err 175 } 176 matchers = append(matchers, m) 177 case *v3rbacpb.Principal_Header: 178 // Do we need an error here? 179 m, err := newHeaderMatcher(principal.GetHeader()) 180 if err != nil { 181 return nil, err 182 } 183 matchers = append(matchers, m) 184 case *v3rbacpb.Principal_UrlPath: 185 m, err := newURLPathMatcher(principal.GetUrlPath()) 186 if err != nil { 187 return nil, err 188 } 189 matchers = append(matchers, m) 190 case *v3rbacpb.Principal_NotId: 191 mList, err := matchersFromPrincipals([]*v3rbacpb.Principal{{Identifier: principal.GetNotId().Identifier}}) 192 if err != nil { 193 return nil, err 194 } 195 matchers = append(matchers, ¬Matcher{matcherToNot: mList[0]}) 196 case *v3rbacpb.Principal_SourceIp: 197 // The source ip principal identifier is deprecated. Thus, a 198 // principal typed as a source ip in the identifier will be a no-op. 199 // The config should use DirectRemoteIp instead. 200 case *v3rbacpb.Principal_RemoteIp: 201 // RBAC in gRPC treats direct_remote_ip and remote_ip as logically 202 // equivalent, as per A41. 203 m, err := newRemoteIPMatcher(principal.GetRemoteIp()) 204 if err != nil { 205 return nil, err 206 } 207 matchers = append(matchers, m) 208 case *v3rbacpb.Principal_Metadata: 209 // Not supported in gRPC RBAC currently - a principal typed as 210 // Metadata in the initial config will be a no-op. 211 } 212 } 213 return matchers, nil 214 } 215 216 // orMatcher is a matcher where it successfully matches if one of it's 217 // children successfully match. It also logically represents a principal or 218 // permission, but can also be it's own entity further down the tree of 219 // matchers. orMatcher implements the matcher interface. 220 type orMatcher struct { 221 matchers []matcher 222 } 223 224 func (om *orMatcher) match(data *rpcData) bool { 225 // Range through child matchers and pass in data about incoming RPC, and 226 // only one child matcher has to match to be logically successful. 227 for _, m := range om.matchers { 228 if m.match(data) { 229 return true 230 } 231 } 232 return false 233 } 234 235 // andMatcher is a matcher that is successful if every child matcher 236 // matches. andMatcher implements the matcher interface. 237 type andMatcher struct { 238 matchers []matcher 239 } 240 241 func (am *andMatcher) match(data *rpcData) bool { 242 for _, m := range am.matchers { 243 if !m.match(data) { 244 return false 245 } 246 } 247 return true 248 } 249 250 // alwaysMatcher is a matcher that will always match. This logically 251 // represents an any rule for a permission or a principal. alwaysMatcher 252 // implements the matcher interface. 253 type alwaysMatcher struct { 254 } 255 256 func (am *alwaysMatcher) match(data *rpcData) bool { 257 return true 258 } 259 260 // notMatcher is a matcher that nots an underlying matcher. notMatcher 261 // implements the matcher interface. 262 type notMatcher struct { 263 matcherToNot matcher 264 } 265 266 func (nm *notMatcher) match(data *rpcData) bool { 267 return !nm.matcherToNot.match(data) 268 } 269 270 // headerMatcher is a matcher that matches on incoming HTTP Headers present 271 // in the incoming RPC. headerMatcher implements the matcher interface. 272 type headerMatcher struct { 273 matcher internalmatcher.HeaderMatcher 274 } 275 276 func newHeaderMatcher(headerMatcherConfig *v3route_componentspb.HeaderMatcher) (*headerMatcher, error) { 277 var m internalmatcher.HeaderMatcher 278 switch headerMatcherConfig.HeaderMatchSpecifier.(type) { 279 case *v3route_componentspb.HeaderMatcher_ExactMatch: 280 m = internalmatcher.NewHeaderExactMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetExactMatch(), headerMatcherConfig.InvertMatch) 281 case *v3route_componentspb.HeaderMatcher_SafeRegexMatch: 282 regex, err := regexp.Compile(headerMatcherConfig.GetSafeRegexMatch().Regex) 283 if err != nil { 284 return nil, err 285 } 286 m = internalmatcher.NewHeaderRegexMatcher(headerMatcherConfig.Name, regex, headerMatcherConfig.InvertMatch) 287 case *v3route_componentspb.HeaderMatcher_RangeMatch: 288 m = internalmatcher.NewHeaderRangeMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetRangeMatch().Start, headerMatcherConfig.GetRangeMatch().End, headerMatcherConfig.InvertMatch) 289 case *v3route_componentspb.HeaderMatcher_PresentMatch: 290 m = internalmatcher.NewHeaderPresentMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetPresentMatch(), headerMatcherConfig.InvertMatch) 291 case *v3route_componentspb.HeaderMatcher_PrefixMatch: 292 m = internalmatcher.NewHeaderPrefixMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetPrefixMatch(), headerMatcherConfig.InvertMatch) 293 case *v3route_componentspb.HeaderMatcher_SuffixMatch: 294 m = internalmatcher.NewHeaderSuffixMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetSuffixMatch(), headerMatcherConfig.InvertMatch) 295 case *v3route_componentspb.HeaderMatcher_ContainsMatch: 296 m = internalmatcher.NewHeaderContainsMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetContainsMatch(), headerMatcherConfig.InvertMatch) 297 default: 298 return nil, errors.New("unknown header matcher type") 299 } 300 return &headerMatcher{matcher: m}, nil 301 } 302 303 func (hm *headerMatcher) match(data *rpcData) bool { 304 return hm.matcher.Match(data.md) 305 } 306 307 // urlPathMatcher matches on the URL Path of the incoming RPC. In gRPC, this 308 // logically maps to the full method name the RPC is calling on the server side. 309 // urlPathMatcher implements the matcher interface. 310 type urlPathMatcher struct { 311 stringMatcher internalmatcher.StringMatcher 312 } 313 314 func newURLPathMatcher(pathMatcher *v3matcherpb.PathMatcher) (*urlPathMatcher, error) { 315 stringMatcher, err := internalmatcher.StringMatcherFromProto(pathMatcher.GetPath()) 316 if err != nil { 317 return nil, err 318 } 319 return &urlPathMatcher{stringMatcher: stringMatcher}, nil 320 } 321 322 func (upm *urlPathMatcher) match(data *rpcData) bool { 323 return upm.stringMatcher.Match(data.fullMethod) 324 } 325 326 // remoteIPMatcher and localIPMatcher both are matchers that match against 327 // a CIDR Range. Two different matchers are needed as the remote and destination 328 // ip addresses come from different parts of the data about incoming RPC's 329 // passed in. Matching a CIDR Range means to determine whether the IP Address 330 // falls within the CIDR Range or not. They both implement the matcher 331 // interface. 332 type remoteIPMatcher struct { 333 // ipNet represents the CidrRange that this matcher was configured with. 334 // This is what will remote and destination IP's will be matched against. 335 ipNet *net.IPNet 336 } 337 338 func newRemoteIPMatcher(cidrRange *v3corepb.CidrRange) (*remoteIPMatcher, error) { 339 // Convert configuration to a cidrRangeString, as Go standard library has 340 // methods that parse cidr string. 341 cidrRangeString := fmt.Sprintf("%s/%d", cidrRange.AddressPrefix, cidrRange.PrefixLen.Value) 342 _, ipNet, err := net.ParseCIDR(cidrRangeString) 343 if err != nil { 344 return nil, err 345 } 346 return &remoteIPMatcher{ipNet: ipNet}, nil 347 } 348 349 func (sim *remoteIPMatcher) match(data *rpcData) bool { 350 return sim.ipNet.Contains(net.IP(net.ParseIP(data.peerInfo.Addr.String()))) 351 } 352 353 type localIPMatcher struct { 354 ipNet *net.IPNet 355 } 356 357 func newLocalIPMatcher(cidrRange *v3corepb.CidrRange) (*localIPMatcher, error) { 358 cidrRangeString := fmt.Sprintf("%s/%d", cidrRange.AddressPrefix, cidrRange.PrefixLen.Value) 359 _, ipNet, err := net.ParseCIDR(cidrRangeString) 360 if err != nil { 361 return nil, err 362 } 363 return &localIPMatcher{ipNet: ipNet}, nil 364 } 365 366 func (dim *localIPMatcher) match(data *rpcData) bool { 367 return dim.ipNet.Contains(net.IP(net.ParseIP(data.localAddr.String()))) 368 } 369 370 // portMatcher matches on whether the destination port of the RPC matches the 371 // destination port this matcher was instantiated with. portMatcher 372 // implements the matcher interface. 373 type portMatcher struct { 374 destinationPort uint32 375 } 376 377 func newPortMatcher(destinationPort uint32) *portMatcher { 378 return &portMatcher{destinationPort: destinationPort} 379 } 380 381 func (pm *portMatcher) match(data *rpcData) bool { 382 return data.destinationPort == pm.destinationPort 383 } 384 385 // authenticatedMatcher matches on the name of the Principal. If set, the URI 386 // SAN or DNS SAN in that order is used from the certificate, otherwise the 387 // subject field is used. If unset, it applies to any user that is 388 // authenticated. authenticatedMatcher implements the matcher interface. 389 type authenticatedMatcher struct { 390 stringMatcher *internalmatcher.StringMatcher 391 } 392 393 func newAuthenticatedMatcher(authenticatedMatcherConfig *v3rbacpb.Principal_Authenticated) (*authenticatedMatcher, error) { 394 // Represents this line in the RBAC documentation = "If unset, it applies to 395 // any user that is authenticated" (see package-level comments). 396 if authenticatedMatcherConfig.PrincipalName == nil { 397 return &authenticatedMatcher{}, nil 398 } 399 stringMatcher, err := internalmatcher.StringMatcherFromProto(authenticatedMatcherConfig.PrincipalName) 400 if err != nil { 401 return nil, err 402 } 403 return &authenticatedMatcher{stringMatcher: &stringMatcher}, nil 404 } 405 406 func (am *authenticatedMatcher) match(data *rpcData) bool { 407 if data.authType != "tls" { 408 // Connection is not authenticated. 409 return false 410 } 411 if am.stringMatcher == nil { 412 // Allows any authenticated user. 413 return true 414 } 415 // "If there is no client certificate (thus no SAN nor Subject), check if "" 416 // (empty string) matches. If it matches, the principal_name is said to 417 // match" - A41 418 if len(data.certs) == 0 { 419 return am.stringMatcher.Match("") 420 } 421 cert := data.certs[0] 422 // The order of matching as per the RBAC documentation (see package-level comments) 423 // is as follows: URI SANs, DNS SANs, and then subject name. 424 for _, uriSAN := range cert.URIs { 425 if am.stringMatcher.Match(uriSAN.String()) { 426 return true 427 } 428 } 429 for _, dnsSAN := range cert.DNSNames { 430 if am.stringMatcher.Match(dnsSAN) { 431 return true 432 } 433 } 434 return am.stringMatcher.Match(cert.Subject.String()) 435 }