dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/utils/rbac/rbac_engine.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 provides service-level and method-level access control for a 25 // service. See 26 // https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/rbac/v3/rbac.proto#role-based-access-control-rbac 27 // for documentation. 28 package rbac 29 30 import ( 31 "context" 32 "crypto/x509" 33 "errors" 34 "fmt" 35 "net" 36 "strconv" 37 ) 38 39 import ( 40 v3rbacpb "github.com/envoyproxy/go-control-plane/envoy/config/rbac/v3" 41 42 "google.golang.org/grpc" 43 "google.golang.org/grpc/codes" 44 "google.golang.org/grpc/credentials" 45 "google.golang.org/grpc/grpclog" 46 "google.golang.org/grpc/metadata" 47 "google.golang.org/grpc/peer" 48 "google.golang.org/grpc/status" 49 ) 50 51 import ( 52 "dubbo.apache.org/dubbo-go/v3/xds/utils/transport" 53 ) 54 55 const logLevel = 2 56 57 var logger = grpclog.Component("rbac") 58 59 var getConnection = transport.GetConnection 60 61 // ChainEngine represents a chain of RBAC Engines, used to make authorization 62 // decisions on incoming RPCs. 63 type ChainEngine struct { 64 chainedEngines []*engine 65 } 66 67 // NewChainEngine returns a chain of RBAC engines, used to make authorization 68 // decisions on incoming RPCs. Returns a non-nil error for invalid policies. 69 func NewChainEngine(policies []*v3rbacpb.RBAC) (*ChainEngine, error) { 70 engines := make([]*engine, 0, len(policies)) 71 for _, policy := range policies { 72 engine, err := newEngine(policy) 73 if err != nil { 74 return nil, err 75 } 76 engines = append(engines, engine) 77 } 78 return &ChainEngine{chainedEngines: engines}, nil 79 } 80 81 // IsAuthorized determines if an incoming RPC is authorized based on the chain of RBAC 82 // engines and their associated actions. 83 // 84 // Errors returned by this function are compatible with the status package. 85 func (cre *ChainEngine) IsAuthorized(ctx context.Context) error { 86 // This conversion step (i.e. pulling things out of ctx) can be done once, 87 // and then be used for the whole chain of RBAC Engines. 88 rpcData, err := newRPCData(ctx) 89 if err != nil { 90 logger.Errorf("newRPCData: %v", err) 91 return status.Errorf(codes.Internal, "gRPC RBAC: %v", err) 92 } 93 for _, engine := range cre.chainedEngines { 94 matchingPolicyName, ok := engine.findMatchingPolicy(rpcData) 95 if logger.V(logLevel) && ok { 96 logger.Infof("incoming RPC matched to policy %v in engine with action %v", matchingPolicyName, engine.action) 97 } 98 99 switch { 100 case engine.action == v3rbacpb.RBAC_ALLOW && !ok: 101 return status.Errorf(codes.PermissionDenied, "incoming RPC did not match an allow policy") 102 case engine.action == v3rbacpb.RBAC_DENY && ok: 103 return status.Errorf(codes.PermissionDenied, "incoming RPC matched a deny policy %q", matchingPolicyName) 104 } 105 // Every policy in the engine list must be queried. Thus, iterate to the 106 // next policy. 107 } 108 // If the incoming RPC gets through all of the engines successfully (i.e. 109 // doesn't not match an allow or match a deny engine), the RPC is authorized 110 // to proceed. 111 return nil 112 } 113 114 // engine is used for matching incoming RPCs to policies. 115 type engine struct { 116 policies map[string]*policyMatcher 117 // action must be ALLOW or DENY. 118 action v3rbacpb.RBAC_Action 119 } 120 121 // newEngine creates an RBAC Engine based on the contents of policy. Returns a 122 // non-nil error if the policy is invalid. 123 func newEngine(config *v3rbacpb.RBAC) (*engine, error) { 124 a := config.GetAction() 125 if a != v3rbacpb.RBAC_ALLOW && a != v3rbacpb.RBAC_DENY { 126 return nil, fmt.Errorf("unsupported action %s", config.Action) 127 } 128 129 policies := make(map[string]*policyMatcher, len(config.GetPolicies())) 130 for name, policy := range config.GetPolicies() { 131 matcher, err := newPolicyMatcher(policy) 132 if err != nil { 133 return nil, err 134 } 135 policies[name] = matcher 136 } 137 return &engine{ 138 policies: policies, 139 action: a, 140 }, nil 141 } 142 143 // findMatchingPolicy determines if an incoming RPC matches a policy. On a 144 // successful match, it returns the name of the matching policy and a true bool 145 // to specify that there was a matching policy found. It returns false in 146 // the case of not finding a matching policy. 147 func (r *engine) findMatchingPolicy(rpcData *rpcData) (string, bool) { 148 for policy, matcher := range r.policies { 149 if matcher.match(rpcData) { 150 return policy, true 151 } 152 } 153 return "", false 154 } 155 156 // newRPCData takes an incoming context (should be a context representing state 157 // needed for server RPC Call with metadata, peer info (used for source ip/port 158 // and TLS information) and connection (used for destination ip/port) piped into 159 // it) and the method name of the Service being called server side and populates 160 // an rpcData struct ready to be passed to the RBAC Engine to find a matching 161 // policy. 162 func newRPCData(ctx context.Context) (*rpcData, error) { 163 // The caller should populate all of these fields (i.e. for empty headers, 164 // pipe an empty md into context). 165 md, ok := metadata.FromIncomingContext(ctx) 166 if !ok { 167 return nil, errors.New("missing metadata in incoming context") 168 } 169 // ":method can be hard-coded to POST if unavailable" - A41 170 md[":method"] = []string{"POST"} 171 // "If the transport exposes TE in Metadata, then RBAC must special-case the 172 // header to treat it as not present." - A41 173 delete(md, "TE") 174 175 pi, ok := peer.FromContext(ctx) 176 if !ok { 177 return nil, errors.New("missing peer info in incoming context") 178 } 179 180 // The methodName will be available in the passed in ctx from a unary or streaming 181 // interceptor, as grpc.Server pipes in a transport stream which contains the methodName 182 // into contexts available in both unary or streaming interceptors. 183 mn, ok := grpc.Method(ctx) 184 if !ok { 185 return nil, errors.New("missing method in incoming context") 186 } 187 188 // The connection is needed in order to find the destination address and 189 // port of the incoming RPC Call. 190 conn := getConnection(ctx) 191 if conn == nil { 192 return nil, errors.New("missing connection in incoming context") 193 } 194 _, dPort, err := net.SplitHostPort(conn.LocalAddr().String()) 195 if err != nil { 196 return nil, fmt.Errorf("error parsing local address: %v", err) 197 } 198 dp, err := strconv.ParseUint(dPort, 10, 32) 199 if err != nil { 200 return nil, fmt.Errorf("error parsing local address: %v", err) 201 } 202 203 var authType string 204 var peerCertificates []*x509.Certificate 205 if pi.AuthInfo != nil { 206 tlsInfo, ok := pi.AuthInfo.(credentials.TLSInfo) 207 if ok { 208 authType = pi.AuthInfo.AuthType() 209 peerCertificates = tlsInfo.State.PeerCertificates 210 } 211 } 212 213 return &rpcData{ 214 md: md, 215 peerInfo: pi, 216 fullMethod: mn, 217 destinationPort: uint32(dp), 218 localAddr: conn.LocalAddr(), 219 authType: authType, 220 certs: peerCertificates, 221 }, nil 222 } 223 224 // rpcData wraps data pulled from an incoming RPC that the RBAC engine needs to 225 // find a matching policy. 226 type rpcData struct { 227 // md is the HTTP Headers that are present in the incoming RPC. 228 md metadata.MD 229 // peerInfo is information about the downstream peer. 230 peerInfo *peer.Peer 231 // fullMethod is the method name being called on the upstream service. 232 fullMethod string 233 // destinationPort is the port that the RPC is being sent to on the 234 // server. 235 destinationPort uint32 236 // localAddr is the address that the RPC is being sent to. 237 localAddr net.Addr 238 // authType is the type of authentication e.g. "tls". 239 authType string 240 // certs are the certificates presented by the peer during a TLS 241 // handshake. 242 certs []*x509.Certificate 243 }