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  }