google.golang.org/grpc@v1.62.1/xds/internal/httpfilter/rbac/rbac.go (about)

     1  /*
     2   *
     3   * Copyright 2021 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * 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  // Package rbac implements the Envoy RBAC HTTP filter.
    20  package rbac
    21  
    22  import (
    23  	"context"
    24  	"errors"
    25  	"fmt"
    26  	"strings"
    27  
    28  	"google.golang.org/grpc/internal"
    29  	"google.golang.org/grpc/internal/resolver"
    30  	"google.golang.org/grpc/internal/xds/rbac"
    31  	"google.golang.org/grpc/xds/internal/httpfilter"
    32  	"google.golang.org/protobuf/proto"
    33  	"google.golang.org/protobuf/types/known/anypb"
    34  
    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  
    39  func init() {
    40  	httpfilter.Register(builder{})
    41  
    42  	// TODO: Remove these once the RBAC env var is removed.
    43  	internal.RegisterRBACHTTPFilterForTesting = func() {
    44  		httpfilter.Register(builder{})
    45  	}
    46  	internal.UnregisterRBACHTTPFilterForTesting = func() {
    47  		for _, typeURL := range builder.TypeURLs(builder{}) {
    48  			httpfilter.UnregisterForTesting(typeURL)
    49  		}
    50  	}
    51  }
    52  
    53  type builder struct {
    54  }
    55  
    56  type config struct {
    57  	httpfilter.FilterConfig
    58  	chainEngine *rbac.ChainEngine
    59  }
    60  
    61  func (builder) TypeURLs() []string {
    62  	return []string{
    63  		"type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBAC",
    64  		"type.googleapis.com/envoy.extensions.filters.http.rbac.v3.RBACPerRoute",
    65  	}
    66  }
    67  
    68  // Parsing is the same for the base config and the override config.
    69  func parseConfig(rbacCfg *rpb.RBAC) (httpfilter.FilterConfig, error) {
    70  	// All the validation logic described in A41.
    71  	for _, policy := range rbacCfg.GetRules().GetPolicies() {
    72  		// "Policy.condition and Policy.checked_condition must cause a
    73  		// validation failure if present." - A41
    74  		if policy.Condition != nil {
    75  			return nil, errors.New("rbac: Policy.condition is present")
    76  		}
    77  		if policy.CheckedCondition != nil {
    78  			return nil, errors.New("rbac: policy.CheckedCondition is present")
    79  		}
    80  
    81  		// "It is also a validation failure if Permission or Principal has a
    82  		// header matcher for a grpc- prefixed header name or :scheme." - A41
    83  		for _, principal := range policy.Principals {
    84  			name := principal.GetHeader().GetName()
    85  			if name == ":scheme" || strings.HasPrefix(name, "grpc-") {
    86  				return nil, fmt.Errorf("rbac: principal header matcher for %v is :scheme or starts with grpc", name)
    87  			}
    88  		}
    89  		for _, permission := range policy.Permissions {
    90  			name := permission.GetHeader().GetName()
    91  			if name == ":scheme" || strings.HasPrefix(name, "grpc-") {
    92  				return nil, fmt.Errorf("rbac: permission header matcher for %v is :scheme or starts with grpc", name)
    93  			}
    94  		}
    95  	}
    96  
    97  	// "Envoy aliases :authority and Host in its header map implementation, so
    98  	// they should be treated equivalent for the RBAC matchers; there must be no
    99  	// behavior change depending on which of the two header names is used in the
   100  	// RBAC policy." - A41. Loop through config's principals and policies, change
   101  	// any header matcher with value "host" to :authority", as that is what
   102  	// grpc-go shifts both headers to in transport layer.
   103  	for _, policy := range rbacCfg.GetRules().GetPolicies() {
   104  		for _, principal := range policy.Principals {
   105  			if principal.GetHeader().GetName() == "host" {
   106  				principal.GetHeader().Name = ":authority"
   107  			}
   108  		}
   109  		for _, permission := range policy.Permissions {
   110  			if permission.GetHeader().GetName() == "host" {
   111  				permission.GetHeader().Name = ":authority"
   112  			}
   113  		}
   114  	}
   115  
   116  	// Two cases where this HTTP Filter is a no op:
   117  	// "If absent, no enforcing RBAC policy will be applied" - RBAC
   118  	// Documentation for Rules field.
   119  	// "At this time, if the RBAC.action is Action.LOG then the policy will be
   120  	// completely ignored, as if RBAC was not configurated." - A41
   121  	if rbacCfg.Rules == nil || rbacCfg.GetRules().GetAction() == v3rbacpb.RBAC_LOG {
   122  		return config{}, nil
   123  	}
   124  
   125  	// TODO(gregorycooke) - change the call chain to here so we have the filter
   126  	// name to input here instead of an empty string. It will come from here:
   127  	// https://github.com/grpc/grpc-go/blob/eff0942e95d93112921414aee758e619ec86f26f/xds/internal/xdsclient/xdsresource/unmarshal_lds.go#L199
   128  	ce, err := rbac.NewChainEngine([]*v3rbacpb.RBAC{rbacCfg.GetRules()}, "")
   129  	if err != nil {
   130  		// "At this time, if the RBAC.action is Action.LOG then the policy will be
   131  		// completely ignored, as if RBAC was not configurated." - A41
   132  		if rbacCfg.GetRules().GetAction() != v3rbacpb.RBAC_LOG {
   133  			return nil, fmt.Errorf("rbac: error constructing matching engine: %v", err)
   134  		}
   135  	}
   136  
   137  	return config{chainEngine: ce}, nil
   138  }
   139  
   140  func (builder) ParseFilterConfig(cfg proto.Message) (httpfilter.FilterConfig, error) {
   141  	if cfg == nil {
   142  		return nil, fmt.Errorf("rbac: nil configuration message provided")
   143  	}
   144  	any, ok := cfg.(*anypb.Any)
   145  	if !ok {
   146  		return nil, fmt.Errorf("rbac: error parsing config %v: unknown type %T", cfg, cfg)
   147  	}
   148  	msg := new(rpb.RBAC)
   149  	if err := any.UnmarshalTo(msg); err != nil {
   150  		return nil, fmt.Errorf("rbac: error parsing config %v: %v", cfg, err)
   151  	}
   152  	return parseConfig(msg)
   153  }
   154  
   155  func (builder) ParseFilterConfigOverride(override proto.Message) (httpfilter.FilterConfig, error) {
   156  	if override == nil {
   157  		return nil, fmt.Errorf("rbac: nil configuration message provided")
   158  	}
   159  	any, ok := override.(*anypb.Any)
   160  	if !ok {
   161  		return nil, fmt.Errorf("rbac: error parsing override config %v: unknown type %T", override, override)
   162  	}
   163  	msg := new(rpb.RBACPerRoute)
   164  	if err := any.UnmarshalTo(msg); err != nil {
   165  		return nil, fmt.Errorf("rbac: error parsing override config %v: %v", override, err)
   166  	}
   167  	return parseConfig(msg.Rbac)
   168  }
   169  
   170  func (builder) IsTerminal() bool {
   171  	return false
   172  }
   173  
   174  var _ httpfilter.ServerInterceptorBuilder = builder{}
   175  
   176  // BuildServerInterceptor is an optional interface builder implements in order
   177  // to signify it works server side.
   178  func (builder) BuildServerInterceptor(cfg httpfilter.FilterConfig, override httpfilter.FilterConfig) (resolver.ServerInterceptor, error) {
   179  	if cfg == nil {
   180  		return nil, fmt.Errorf("rbac: nil config provided")
   181  	}
   182  
   183  	c, ok := cfg.(config)
   184  	if !ok {
   185  		return nil, fmt.Errorf("rbac: incorrect config type provided (%T): %v", cfg, cfg)
   186  	}
   187  
   188  	if override != nil {
   189  		// override completely replaces the listener configuration; but we
   190  		// still validate the listener config type.
   191  		c, ok = override.(config)
   192  		if !ok {
   193  			return nil, fmt.Errorf("rbac: incorrect override config type provided (%T): %v", override, override)
   194  		}
   195  	}
   196  
   197  	// RBAC HTTP Filter is a no op from one of these two cases:
   198  	// "If absent, no enforcing RBAC policy will be applied" - RBAC
   199  	// Documentation for Rules field.
   200  	// "At this time, if the RBAC.action is Action.LOG then the policy will be
   201  	// completely ignored, as if RBAC was not configurated." - A41
   202  	if c.chainEngine == nil {
   203  		return nil, nil
   204  	}
   205  	return &interceptor{chainEngine: c.chainEngine}, nil
   206  }
   207  
   208  type interceptor struct {
   209  	chainEngine *rbac.ChainEngine
   210  }
   211  
   212  func (i *interceptor) AllowRPC(ctx context.Context) error {
   213  	return i.chainEngine.IsAuthorized(ctx)
   214  }