google.golang.org/grpc@v1.72.2/internal/xds/rbac/matchers.go (about)

     1  /*
     2   * Copyright 2021 gRPC authors.
     3   *
     4   * Licensed under the Apache License, Version 2.0 (the "License");
     5   * you may not use this file except in compliance with the License.
     6   * You may obtain a copy of the License at
     7   *
     8   *     http://www.apache.org/licenses/LICENSE-2.0
     9   *
    10   * Unless required by applicable law or agreed to in writing, software
    11   * distributed under the License is distributed on an "AS IS" BASIS,
    12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13   * See the License for the specific language governing permissions and
    14   * limitations under the License.
    15   */
    16  
    17  package rbac
    18  
    19  import (
    20  	"errors"
    21  	"fmt"
    22  	"net"
    23  	"net/netip"
    24  	"regexp"
    25  
    26  	v3corepb "github.com/envoyproxy/go-control-plane/envoy/config/core/v3"
    27  	v3rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3"
    28  	v3route_componentspb "github.com/envoyproxy/go-control-plane/envoy/config/route/v3"
    29  	v3matcherpb "github.com/envoyproxy/go-control-plane/envoy/type/matcher/v3"
    30  	internalmatcher "google.golang.org/grpc/internal/xds/matcher"
    31  )
    32  
    33  // matcher is an interface that takes data about incoming RPC's and returns
    34  // whether it matches with whatever matcher implements this interface.
    35  type matcher interface {
    36  	match(data *rpcData) bool
    37  }
    38  
    39  // policyMatcher helps determine whether an incoming RPC call matches a policy.
    40  // A policy is a logical role (e.g. Service Admin), which is comprised of
    41  // permissions and principals. A principal is an identity (or identities) for a
    42  // downstream subject which are assigned the policy (role), and a permission is
    43  // an action(s) that a principal(s) can take. A policy matches if both a
    44  // permission and a principal match, which will be determined by the child or
    45  // permissions and principal matchers. policyMatcher implements the matcher
    46  // interface.
    47  type policyMatcher struct {
    48  	permissions *orMatcher
    49  	principals  *orMatcher
    50  }
    51  
    52  func newPolicyMatcher(policy *v3rbacpb.Policy) (*policyMatcher, error) {
    53  	permissions, err := matchersFromPermissions(policy.Permissions)
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	principals, err := matchersFromPrincipals(policy.Principals)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  	return &policyMatcher{
    62  		permissions: &orMatcher{matchers: permissions},
    63  		principals:  &orMatcher{matchers: principals},
    64  	}, nil
    65  }
    66  
    67  func (pm *policyMatcher) match(data *rpcData) bool {
    68  	// A policy matches if and only if at least one of its permissions match the
    69  	// action taking place AND at least one if its principals match the
    70  	// downstream peer.
    71  	return pm.permissions.match(data) && pm.principals.match(data)
    72  }
    73  
    74  // matchersFromPermissions takes a list of permissions (can also be
    75  // a single permission, e.g. from a not matcher which is logically !permission)
    76  // and returns a list of matchers which correspond to that permission. This will
    77  // be called in many instances throughout the initial construction of the RBAC
    78  // engine from the AND and OR matchers and also from the NOT matcher.
    79  func matchersFromPermissions(permissions []*v3rbacpb.Permission) ([]matcher, error) {
    80  	var matchers []matcher
    81  	for _, permission := range permissions {
    82  		switch permission.GetRule().(type) {
    83  		case *v3rbacpb.Permission_AndRules:
    84  			mList, err := matchersFromPermissions(permission.GetAndRules().Rules)
    85  			if err != nil {
    86  				return nil, err
    87  			}
    88  			matchers = append(matchers, &andMatcher{matchers: mList})
    89  		case *v3rbacpb.Permission_OrRules:
    90  			mList, err := matchersFromPermissions(permission.GetOrRules().Rules)
    91  			if err != nil {
    92  				return nil, err
    93  			}
    94  			matchers = append(matchers, &orMatcher{matchers: mList})
    95  		case *v3rbacpb.Permission_Any:
    96  			matchers = append(matchers, &alwaysMatcher{})
    97  		case *v3rbacpb.Permission_Header:
    98  			m, err := newHeaderMatcher(permission.GetHeader())
    99  			if err != nil {
   100  				return nil, err
   101  			}
   102  			matchers = append(matchers, m)
   103  		case *v3rbacpb.Permission_UrlPath:
   104  			m, err := newURLPathMatcher(permission.GetUrlPath())
   105  			if err != nil {
   106  				return nil, err
   107  			}
   108  			matchers = append(matchers, m)
   109  		case *v3rbacpb.Permission_DestinationIp:
   110  			// Due to this being on server side, the destination IP is the local
   111  			// IP.
   112  			m, err := newLocalIPMatcher(permission.GetDestinationIp())
   113  			if err != nil {
   114  				return nil, err
   115  			}
   116  			matchers = append(matchers, m)
   117  		case *v3rbacpb.Permission_DestinationPort:
   118  			matchers = append(matchers, newPortMatcher(permission.GetDestinationPort()))
   119  		case *v3rbacpb.Permission_NotRule:
   120  			mList, err := matchersFromPermissions([]*v3rbacpb.Permission{{Rule: permission.GetNotRule().Rule}})
   121  			if err != nil {
   122  				return nil, err
   123  			}
   124  			matchers = append(matchers, &notMatcher{matcherToNot: mList[0]})
   125  		case *v3rbacpb.Permission_Metadata:
   126  			// Never matches - so no-op if not inverted, always match if
   127  			// inverted.
   128  			if permission.GetMetadata().GetInvert() { // Test metadata being no-op and also metadata with invert always matching
   129  				matchers = append(matchers, &alwaysMatcher{})
   130  			}
   131  		case *v3rbacpb.Permission_RequestedServerName:
   132  			// Not supported in gRPC RBAC currently - a permission typed as
   133  			// requested server name in the initial config will be a no-op.
   134  		}
   135  	}
   136  	return matchers, nil
   137  }
   138  
   139  func matchersFromPrincipals(principals []*v3rbacpb.Principal) ([]matcher, error) {
   140  	var matchers []matcher
   141  	for _, principal := range principals {
   142  		switch principal.GetIdentifier().(type) {
   143  		case *v3rbacpb.Principal_AndIds:
   144  			mList, err := matchersFromPrincipals(principal.GetAndIds().Ids)
   145  			if err != nil {
   146  				return nil, err
   147  			}
   148  			matchers = append(matchers, &andMatcher{matchers: mList})
   149  		case *v3rbacpb.Principal_OrIds:
   150  			mList, err := matchersFromPrincipals(principal.GetOrIds().Ids)
   151  			if err != nil {
   152  				return nil, err
   153  			}
   154  			matchers = append(matchers, &orMatcher{matchers: mList})
   155  		case *v3rbacpb.Principal_Any:
   156  			matchers = append(matchers, &alwaysMatcher{})
   157  		case *v3rbacpb.Principal_Authenticated_:
   158  			authenticatedMatcher, err := newAuthenticatedMatcher(principal.GetAuthenticated())
   159  			if err != nil {
   160  				return nil, err
   161  			}
   162  			matchers = append(matchers, authenticatedMatcher)
   163  		case *v3rbacpb.Principal_DirectRemoteIp:
   164  			m, err := newRemoteIPMatcher(principal.GetDirectRemoteIp())
   165  			if err != nil {
   166  				return nil, err
   167  			}
   168  			matchers = append(matchers, m)
   169  		case *v3rbacpb.Principal_Header:
   170  			// Do we need an error here?
   171  			m, err := newHeaderMatcher(principal.GetHeader())
   172  			if err != nil {
   173  				return nil, err
   174  			}
   175  			matchers = append(matchers, m)
   176  		case *v3rbacpb.Principal_UrlPath:
   177  			m, err := newURLPathMatcher(principal.GetUrlPath())
   178  			if err != nil {
   179  				return nil, err
   180  			}
   181  			matchers = append(matchers, m)
   182  		case *v3rbacpb.Principal_NotId:
   183  			mList, err := matchersFromPrincipals([]*v3rbacpb.Principal{{Identifier: principal.GetNotId().Identifier}})
   184  			if err != nil {
   185  				return nil, err
   186  			}
   187  			matchers = append(matchers, &notMatcher{matcherToNot: mList[0]})
   188  		case *v3rbacpb.Principal_SourceIp:
   189  			// The source ip principal identifier is deprecated. Thus, a
   190  			// principal typed as a source ip in the identifier will be a no-op.
   191  			// The config should use DirectRemoteIp instead.
   192  		case *v3rbacpb.Principal_RemoteIp:
   193  			// RBAC in gRPC treats direct_remote_ip and remote_ip as logically
   194  			// equivalent, as per A41.
   195  			m, err := newRemoteIPMatcher(principal.GetRemoteIp())
   196  			if err != nil {
   197  				return nil, err
   198  			}
   199  			matchers = append(matchers, m)
   200  		case *v3rbacpb.Principal_Metadata:
   201  			// Not supported in gRPC RBAC currently - a principal typed as
   202  			// Metadata in the initial config will be a no-op.
   203  		}
   204  	}
   205  	return matchers, nil
   206  }
   207  
   208  // orMatcher is a matcher where it successfully matches if one of it's
   209  // children successfully match. It also logically represents a principal or
   210  // permission, but can also be it's own entity further down the tree of
   211  // matchers. orMatcher implements the matcher interface.
   212  type orMatcher struct {
   213  	matchers []matcher
   214  }
   215  
   216  func (om *orMatcher) match(data *rpcData) bool {
   217  	// Range through child matchers and pass in data about incoming RPC, and
   218  	// only one child matcher has to match to be logically successful.
   219  	for _, m := range om.matchers {
   220  		if m.match(data) {
   221  			return true
   222  		}
   223  	}
   224  	return false
   225  }
   226  
   227  // andMatcher is a matcher that is successful if every child matcher
   228  // matches. andMatcher implements the matcher interface.
   229  type andMatcher struct {
   230  	matchers []matcher
   231  }
   232  
   233  func (am *andMatcher) match(data *rpcData) bool {
   234  	for _, m := range am.matchers {
   235  		if !m.match(data) {
   236  			return false
   237  		}
   238  	}
   239  	return true
   240  }
   241  
   242  // alwaysMatcher is a matcher that will always match. This logically
   243  // represents an any rule for a permission or a principal. alwaysMatcher
   244  // implements the matcher interface.
   245  type alwaysMatcher struct {
   246  }
   247  
   248  func (am *alwaysMatcher) match(*rpcData) bool {
   249  	return true
   250  }
   251  
   252  // notMatcher is a matcher that nots an underlying matcher. notMatcher
   253  // implements the matcher interface.
   254  type notMatcher struct {
   255  	matcherToNot matcher
   256  }
   257  
   258  func (nm *notMatcher) match(data *rpcData) bool {
   259  	return !nm.matcherToNot.match(data)
   260  }
   261  
   262  // headerMatcher is a matcher that matches on incoming HTTP Headers present
   263  // in the incoming RPC. headerMatcher implements the matcher interface.
   264  type headerMatcher struct {
   265  	matcher internalmatcher.HeaderMatcher
   266  }
   267  
   268  func newHeaderMatcher(headerMatcherConfig *v3route_componentspb.HeaderMatcher) (*headerMatcher, error) {
   269  	var m internalmatcher.HeaderMatcher
   270  	switch headerMatcherConfig.HeaderMatchSpecifier.(type) {
   271  	case *v3route_componentspb.HeaderMatcher_ExactMatch:
   272  		m = internalmatcher.NewHeaderExactMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetExactMatch(), headerMatcherConfig.InvertMatch)
   273  	case *v3route_componentspb.HeaderMatcher_SafeRegexMatch:
   274  		regex, err := regexp.Compile(headerMatcherConfig.GetSafeRegexMatch().Regex)
   275  		if err != nil {
   276  			return nil, err
   277  		}
   278  		m = internalmatcher.NewHeaderRegexMatcher(headerMatcherConfig.Name, regex, headerMatcherConfig.InvertMatch)
   279  	case *v3route_componentspb.HeaderMatcher_RangeMatch:
   280  		m = internalmatcher.NewHeaderRangeMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetRangeMatch().Start, headerMatcherConfig.GetRangeMatch().End, headerMatcherConfig.InvertMatch)
   281  	case *v3route_componentspb.HeaderMatcher_PresentMatch:
   282  		m = internalmatcher.NewHeaderPresentMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetPresentMatch(), headerMatcherConfig.InvertMatch)
   283  	case *v3route_componentspb.HeaderMatcher_PrefixMatch:
   284  		m = internalmatcher.NewHeaderPrefixMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetPrefixMatch(), headerMatcherConfig.InvertMatch)
   285  	case *v3route_componentspb.HeaderMatcher_SuffixMatch:
   286  		m = internalmatcher.NewHeaderSuffixMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetSuffixMatch(), headerMatcherConfig.InvertMatch)
   287  	case *v3route_componentspb.HeaderMatcher_ContainsMatch:
   288  		m = internalmatcher.NewHeaderContainsMatcher(headerMatcherConfig.Name, headerMatcherConfig.GetContainsMatch(), headerMatcherConfig.InvertMatch)
   289  	case *v3route_componentspb.HeaderMatcher_StringMatch:
   290  		sm, err := internalmatcher.StringMatcherFromProto(headerMatcherConfig.GetStringMatch())
   291  		if err != nil {
   292  			return nil, fmt.Errorf("invalid string matcher %+v: %v", headerMatcherConfig.GetStringMatch(), err)
   293  		}
   294  		m = internalmatcher.NewHeaderStringMatcher(headerMatcherConfig.Name, sm, headerMatcherConfig.InvertMatch)
   295  	default:
   296  		return nil, errors.New("unknown header matcher type")
   297  	}
   298  	return &headerMatcher{matcher: m}, nil
   299  }
   300  
   301  func (hm *headerMatcher) match(data *rpcData) bool {
   302  	return hm.matcher.Match(data.md)
   303  }
   304  
   305  // urlPathMatcher matches on the URL Path of the incoming RPC. In gRPC, this
   306  // logically maps to the full method name the RPC is calling on the server side.
   307  // urlPathMatcher implements the matcher interface.
   308  type urlPathMatcher struct {
   309  	stringMatcher internalmatcher.StringMatcher
   310  }
   311  
   312  func newURLPathMatcher(pathMatcher *v3matcherpb.PathMatcher) (*urlPathMatcher, error) {
   313  	stringMatcher, err := internalmatcher.StringMatcherFromProto(pathMatcher.GetPath())
   314  	if err != nil {
   315  		return nil, err
   316  	}
   317  	return &urlPathMatcher{stringMatcher: stringMatcher}, nil
   318  }
   319  
   320  func (upm *urlPathMatcher) match(data *rpcData) bool {
   321  	return upm.stringMatcher.Match(data.fullMethod)
   322  }
   323  
   324  // remoteIPMatcher and localIPMatcher both are matchers that match against
   325  // a CIDR Range. Two different matchers are needed as the remote and destination
   326  // ip addresses come from different parts of the data about incoming RPC's
   327  // passed in. Matching a CIDR Range means to determine whether the IP Address
   328  // falls within the CIDR Range or not. They both implement the matcher
   329  // interface.
   330  type remoteIPMatcher struct {
   331  	// ipNet represents the CidrRange that this matcher was configured with.
   332  	// This is what will remote and destination IP's will be matched against.
   333  	ipNet *net.IPNet
   334  }
   335  
   336  func newRemoteIPMatcher(cidrRange *v3corepb.CidrRange) (*remoteIPMatcher, error) {
   337  	// Convert configuration to a cidrRangeString, as Go standard library has
   338  	// methods that parse cidr string.
   339  	cidrRangeString := fmt.Sprintf("%s/%d", cidrRange.AddressPrefix, cidrRange.PrefixLen.Value)
   340  	_, ipNet, err := net.ParseCIDR(cidrRangeString)
   341  	if err != nil {
   342  		return nil, err
   343  	}
   344  	return &remoteIPMatcher{ipNet: ipNet}, nil
   345  }
   346  
   347  func (sim *remoteIPMatcher) match(data *rpcData) bool {
   348  	ip, _ := netip.ParseAddr(data.peerInfo.Addr.String())
   349  	return sim.ipNet.Contains(net.IP(ip.AsSlice()))
   350  }
   351  
   352  type localIPMatcher struct {
   353  	ipNet *net.IPNet
   354  }
   355  
   356  func newLocalIPMatcher(cidrRange *v3corepb.CidrRange) (*localIPMatcher, error) {
   357  	cidrRangeString := fmt.Sprintf("%s/%d", cidrRange.AddressPrefix, cidrRange.PrefixLen.Value)
   358  	_, ipNet, err := net.ParseCIDR(cidrRangeString)
   359  	if err != nil {
   360  		return nil, err
   361  	}
   362  	return &localIPMatcher{ipNet: ipNet}, nil
   363  }
   364  
   365  func (dim *localIPMatcher) match(data *rpcData) bool {
   366  	ip, _ := netip.ParseAddr(data.localAddr.String())
   367  	return dim.ipNet.Contains(net.IP(ip.AsSlice()))
   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  }