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 }