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