github.com/letsencrypt/trillian@v1.1.2-0.20180615153820-ae375a99d36a/server/interceptor/interceptor.go (about) 1 // Copyright 2017 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package interceptor defines gRPC interceptors for Trillian. 16 package interceptor 17 18 import ( 19 "context" 20 "fmt" 21 "sync" 22 "time" 23 24 "github.com/golang/glog" 25 "github.com/google/trillian" 26 "github.com/google/trillian/monitoring" 27 "github.com/google/trillian/quota" 28 "github.com/google/trillian/quota/etcd/quotapb" 29 "github.com/google/trillian/server/errors" 30 "github.com/google/trillian/storage" 31 "github.com/google/trillian/trees" 32 "go.opencensus.io/trace" 33 "google.golang.org/grpc" 34 "google.golang.org/grpc/codes" 35 "google.golang.org/grpc/status" 36 ) 37 38 const ( 39 badInfoReason = "bad_info" 40 badTreeReason = "bad_tree" 41 insufficientTokensReason = "insufficient_tokens" 42 getTreeStage = "get_tree" 43 getTokensStage = "get_tokens" 44 traceSpanRoot = "github/com/google/trillian/server/interceptor" 45 ) 46 47 var ( 48 // PutTokensTimeout is the timeout used for PutTokens calls. 49 // PutTokens happens in a separate goroutine and with an independent context, therefore it has 50 // its own timeout, separate from the RPC that causes the calls. 51 PutTokensTimeout = 5 * time.Second 52 53 requestCounter monitoring.Counter 54 requestDeniedCounter monitoring.Counter 55 contextErrCounter monitoring.Counter 56 metricsOnce sync.Once 57 ) 58 59 // RequestProcessor encapsulates the logic to intercept a request, split into separate stages: 60 // before and after the handler is invoked. 61 type RequestProcessor interface { 62 63 // Before implements all interceptor logic that happens before the handler is called. 64 // It returns a (potentially) modified context that's passed forward to the handler (and After), 65 // plus an error, in case the request should be interrupted before the handler is invoked. 66 Before(ctx context.Context, req interface{}) (context.Context, error) 67 68 // After implements all interceptor logic that happens after the handler is invoked. 69 // Before must be invoked prior to After and the same RequestProcessor instance must to be used 70 // to process a given request. 71 After(ctx context.Context, resp interface{}, handlerErr error) 72 } 73 74 // TrillianInterceptor checks that: 75 // * Requests addressing a tree have the correct tree type and tree state; 76 // * TODO(codingllama): Requests are properly authenticated / authorized ; and 77 // * Requests are rate limited appropriately. 78 type TrillianInterceptor struct { 79 admin storage.AdminStorage 80 qm quota.Manager 81 82 // quotaDryRun controls whether lack of tokens actually blocks requests (if set to true, no 83 // requests are blocked by lack of tokens). 84 quotaDryRun bool 85 } 86 87 // New returns a new TrillianInterceptor instance. 88 func New(admin storage.AdminStorage, qm quota.Manager, quotaDryRun bool, mf monitoring.MetricFactory) *TrillianInterceptor { 89 metricsOnce.Do(func() { initMetrics(mf) }) 90 return &TrillianInterceptor{ 91 admin: admin, 92 qm: qm, 93 quotaDryRun: quotaDryRun, 94 } 95 } 96 97 func initMetrics(mf monitoring.MetricFactory) { 98 if mf == nil { 99 mf = monitoring.InertMetricFactory{} 100 } 101 quota.InitMetrics(mf) 102 requestCounter = mf.NewCounter( 103 "interceptor_request_count", 104 "Total number of intercepted requests", 105 monitoring.TreeIDLabel) 106 requestDeniedCounter = mf.NewCounter( 107 "interceptor_request_denied_count", 108 "Number of requests by denied, labeled according to the reason for denial", 109 "reason", monitoring.TreeIDLabel, "quota_user") 110 contextErrCounter = mf.NewCounter( 111 "interceptor_context_err_counter", 112 "Total number of times request context has been cancelled or deadline exceeded by stage", 113 "stage") 114 } 115 116 func incRequestDeniedCounter(reason string, treeID int64, quotaUser string) { 117 requestDeniedCounter.Inc(reason, fmt.Sprint(treeID), quotaUser) 118 } 119 120 // UnaryInterceptor executes the TrillianInterceptor logic for unary RPCs. 121 func (i *TrillianInterceptor) UnaryInterceptor(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 122 // Implement UnaryInterceptor using a RequestProcessor, so we 1. exercise it and 2. make it 123 // easier to port this logic to non-gRPC implementations. 124 rp := i.NewProcessor() 125 var err error 126 ctx, err = rp.Before(ctx, req) 127 if err != nil { 128 return nil, err 129 } 130 resp, err := handler(ctx, req) 131 rp.After(ctx, resp, err) 132 return resp, err 133 } 134 135 // NewProcessor returns a RequestProcessor for the TrillianInterceptor logic. 136 func (i *TrillianInterceptor) NewProcessor() RequestProcessor { 137 return &trillianProcessor{parent: i} 138 } 139 140 type trillianProcessor struct { 141 parent *TrillianInterceptor 142 info *rpcInfo 143 } 144 145 func (tp *trillianProcessor) Before(ctx context.Context, req interface{}) (context.Context, error) { 146 ctx, span := spanFor(ctx, "Before") 147 defer span.End() 148 quotaUser := tp.parent.qm.GetUser(ctx, req) 149 info, err := newRPCInfo(req, quotaUser) 150 if err != nil { 151 glog.Warningf("Failed to read tree info: %v", err) 152 incRequestDeniedCounter(badInfoReason, 0, quotaUser) 153 return ctx, err 154 } 155 tp.info = info 156 requestCounter.Inc(fmt.Sprint(info.treeID)) 157 158 // TODO(codingllama): Add auth interception 159 160 if info.getTree { 161 tree, err := trees.GetTree( 162 ctx, tp.parent.admin, info.treeID, trees.NewGetOpts(trees.Admin, info.treeTypes...)) 163 if err != nil { 164 incRequestDeniedCounter(badTreeReason, info.treeID, info.quotaUsers) 165 return ctx, err 166 } 167 if err := ctx.Err(); err != nil { 168 contextErrCounter.Inc(getTreeStage) 169 return ctx, err 170 } 171 ctx = trees.NewContext(ctx, tree) 172 } 173 174 if info.tokens > 0 && len(info.specs) > 0 { 175 err := tp.parent.qm.GetTokens(ctx, info.tokens, info.specs) 176 if err != nil { 177 if !tp.parent.quotaDryRun { 178 incRequestDeniedCounter(insufficientTokensReason, info.treeID, info.quotaUsers) 179 return ctx, status.Errorf(codes.ResourceExhausted, "quota exhausted: %v", err) 180 } 181 glog.Warningf("(quotaDryRun) Request %+v not denied due to dry run mode: %v", req, err) 182 } 183 quota.Metrics.IncAcquired(info.tokens, info.specs, err == nil) 184 if err = ctx.Err(); err != nil { 185 contextErrCounter.Inc(getTokensStage) 186 return ctx, err 187 } 188 } 189 190 return ctx, nil 191 } 192 193 func (tp *trillianProcessor) After(ctx context.Context, resp interface{}, handlerErr error) { 194 _, span := spanFor(ctx, "After") 195 defer span.End() 196 switch { 197 case tp.info == nil: 198 glog.Warningf("After called with nil rpcInfo, resp = [%+v], handlerErr = [%v]", resp, handlerErr) 199 return 200 case tp.info.tokens == 0: 201 // After() currently only does quota processing 202 return 203 } 204 205 // Decide if we have to replenish tokens. There are a few situations that require tokens to 206 // be replenished: 207 // * Invalid requests (a bad request shouldn't spend sequencing-based tokens, as it won't 208 // cause a corresponding sequencing to happen) 209 // * Requests that filter out duplicates (e.g., QueueLeaf and QueueLeaves, for the same 210 // reason as above: duplicates aren't queued for sequencing) 211 tokens := 0 212 if handlerErr != nil { 213 // Return the tokens spent by invalid requests 214 tokens = tp.info.tokens 215 } else { 216 switch resp := resp.(type) { 217 case *trillian.QueueLeafResponse: 218 if !isLeafOK(resp.GetQueuedLeaf()) { 219 tokens = 1 220 } 221 case *trillian.AddSequencedLeavesResponse: 222 for _, leaf := range resp.GetResults() { 223 if !isLeafOK(leaf) { 224 tokens++ 225 } 226 } 227 case *trillian.QueueLeavesResponse: 228 for _, leaf := range resp.GetQueuedLeaves() { 229 if !isLeafOK(leaf) { 230 tokens++ 231 } 232 } 233 } 234 } 235 if len(tp.info.specs) > 0 && tokens > 0 { 236 // Run PutTokens in a separate goroutine and with a separate context. 237 // It shouldn't block RPC completion, nor should it share the RPC's context deadline. 238 go func() { 239 ctx, span := spanFor(context.Background(), "After.PutTokens") 240 defer span.End() 241 ctx, cancel := context.WithTimeout(ctx, PutTokensTimeout) 242 defer cancel() 243 244 // TODO(codingllama): If PutTokens turns out to be unreliable we can still leak tokens. In 245 // this case, we may want to keep tabs on how many tokens we failed to replenish and bundle 246 // them up in the next PutTokens call (possibly as a QuotaManager decorator, or internally 247 // in its impl). 248 err := tp.parent.qm.PutTokens(ctx, tokens, tp.info.specs) 249 if err != nil { 250 glog.Warningf("Failed to replenish %v tokens: %v", tokens, err) 251 } 252 quota.Metrics.IncReturned(tokens, tp.info.specs, err == nil) 253 }() 254 } 255 } 256 257 func isLeafOK(leaf *trillian.QueuedLogLeaf) bool { 258 // Be biased in favor of OK, as that matches TrillianLogRPCServer's behavior. 259 return leaf == nil || leaf.Status == nil || leaf.Status.Code == int32(codes.OK) 260 } 261 262 type rpcInfo struct { 263 // getTree indicates whether the interceptor should populate treeID. 264 getTree bool 265 266 readonly bool 267 treeID int64 268 treeTypes []trillian.TreeType 269 270 specs []quota.Spec 271 tokens int 272 // Single string describing all of the users against which quota is requested. 273 quotaUsers string 274 } 275 276 // chargable is satisfied by request proto messages which contain a GetChargeTo 277 // accessor. 278 type chargable interface { 279 GetChargeTo() *trillian.ChargeTo 280 } 281 282 // chargedUsers returns user identifiers for any chargable user quotas. 283 func chargedUsers(req interface{}) []string { 284 c, ok := req.(chargable) 285 if !ok { 286 return nil 287 } 288 chargeTo := c.GetChargeTo() 289 if chargeTo == nil { 290 return nil 291 } 292 293 return chargeTo.User 294 } 295 296 func newRPCInfoForRequest(req interface{}) (*rpcInfo, error) { 297 // Set "safe" defaults: enable all interception and assume requests are readonly. 298 info := &rpcInfo{ 299 getTree: true, 300 readonly: true, 301 treeTypes: nil, 302 tokens: 0, 303 } 304 305 switch req := req.(type) { 306 307 // Not intercepted at all 308 case 309 // Quota configuration requests 310 *quotapb.CreateConfigRequest, 311 *quotapb.DeleteConfigRequest, 312 *quotapb.GetConfigRequest, 313 *quotapb.ListConfigsRequest, 314 *quotapb.UpdateConfigRequest: 315 info.getTree = false 316 info.readonly = false // Doesn't really matter as all interceptors are turned off 317 318 // Admin create 319 case *trillian.CreateTreeRequest: 320 info.getTree = false // Tree doesn't exist 321 info.readonly = false 322 323 // Admin list 324 case *trillian.ListTreesRequest: 325 info.getTree = false // Zero to many trees 326 327 // Admin / readonly 328 case *trillian.GetTreeRequest: 329 info.getTree = false // Read done within RPC handler 330 331 // Admin / readwrite 332 case *trillian.DeleteTreeRequest, 333 *trillian.UndeleteTreeRequest, 334 *trillian.UpdateTreeRequest: 335 info.getTree = false // Read-modify-write done within RPC handler 336 info.readonly = false 337 338 // (Log + Pre-ordered Log) / readonly 339 case *trillian.GetConsistencyProofRequest, 340 *trillian.GetEntryAndProofRequest, 341 *trillian.GetInclusionProofByHashRequest, 342 *trillian.GetInclusionProofRequest, 343 *trillian.GetLatestSignedLogRootRequest: 344 info.treeTypes = []trillian.TreeType{trillian.TreeType_LOG, trillian.TreeType_PREORDERED_LOG} 345 info.tokens = 1 346 case *trillian.GetLeavesByHashRequest: 347 info.treeTypes = []trillian.TreeType{trillian.TreeType_LOG, trillian.TreeType_PREORDERED_LOG} 348 info.tokens = len(req.GetLeafHash()) 349 case *trillian.GetLeavesByIndexRequest: 350 info.treeTypes = []trillian.TreeType{trillian.TreeType_LOG, trillian.TreeType_PREORDERED_LOG} 351 info.tokens = len(req.GetLeafIndex()) 352 case *trillian.GetLeavesByRangeRequest: 353 info.treeTypes = []trillian.TreeType{trillian.TreeType_LOG, trillian.TreeType_PREORDERED_LOG} 354 info.tokens = 1 355 if c := req.GetCount(); c > 1 { 356 info.tokens = int(c) 357 } 358 case *trillian.GetSequencedLeafCountRequest: 359 info.treeTypes = []trillian.TreeType{trillian.TreeType_LOG, trillian.TreeType_PREORDERED_LOG} 360 361 // Log / readwrite 362 case *trillian.QueueLeafRequest: 363 info.readonly = false 364 info.treeTypes = []trillian.TreeType{trillian.TreeType_LOG} 365 info.tokens = 1 366 case *trillian.QueueLeavesRequest: 367 info.readonly = false 368 info.treeTypes = []trillian.TreeType{trillian.TreeType_LOG} 369 info.tokens = len(req.GetLeaves()) 370 371 // Pre-ordered Log / readwrite 372 case *trillian.AddSequencedLeafRequest: 373 info.readonly = false 374 info.treeTypes = []trillian.TreeType{trillian.TreeType_PREORDERED_LOG} 375 info.tokens = 1 376 case *trillian.AddSequencedLeavesRequest: 377 info.readonly = false 378 info.treeTypes = []trillian.TreeType{trillian.TreeType_PREORDERED_LOG} 379 info.tokens = len(req.GetLeaves()) 380 381 // (Log + Pre-ordered Log) / readwrite 382 case *trillian.InitLogRequest: 383 info.readonly = false 384 info.treeTypes = []trillian.TreeType{trillian.TreeType_LOG, trillian.TreeType_PREORDERED_LOG} 385 info.tokens = 1 386 387 // Map / readonly 388 case *trillian.GetMapLeavesByRevisionRequest: 389 info.treeTypes = []trillian.TreeType{trillian.TreeType_MAP} 390 info.tokens = len(req.GetIndex()) 391 case *trillian.GetMapLeavesRequest: 392 info.treeTypes = []trillian.TreeType{trillian.TreeType_MAP} 393 info.tokens = len(req.GetIndex()) 394 case *trillian.GetSignedMapRootByRevisionRequest, 395 *trillian.GetSignedMapRootRequest: 396 info.treeTypes = []trillian.TreeType{trillian.TreeType_MAP} 397 info.tokens = 1 398 399 // Map / readwrite 400 case *trillian.SetMapLeavesRequest: 401 info.readonly = false 402 info.treeTypes = []trillian.TreeType{trillian.TreeType_MAP} 403 info.tokens = len(req.GetLeaves()) 404 case *trillian.InitMapRequest: 405 info.readonly = false 406 info.treeTypes = []trillian.TreeType{trillian.TreeType_MAP} 407 info.tokens = 1 408 409 default: 410 return nil, status.Errorf(codes.Internal, "newRPCInfo: unmapped request type: %T", req) 411 } 412 413 return info, nil 414 } 415 416 func newRPCInfo(req interface{}, quotaUser string) (*rpcInfo, error) { 417 info, err := newRPCInfoForRequest(req) 418 if err != nil { 419 return nil, err 420 } 421 422 if info.getTree || info.tokens > 0 { 423 switch req := req.(type) { 424 case logIDRequest: 425 info.treeID = req.GetLogId() 426 case mapIDRequest: 427 info.treeID = req.GetMapId() 428 case treeIDRequest: 429 info.treeID = req.GetTreeId() 430 case treeRequest: 431 info.treeID = req.GetTree().GetTreeId() 432 default: 433 return nil, status.Errorf(codes.Internal, "cannot retrieve treeID from request: %T", req) 434 } 435 } 436 437 if info.tokens > 0 { 438 kind := quota.Write 439 if info.readonly { 440 kind = quota.Read 441 } 442 443 info.quotaUsers = quotaUser 444 for _, user := range chargedUsers(req) { 445 info.specs = append(info.specs, quota.Spec{Group: quota.User, Kind: kind, User: user}) 446 if len(info.quotaUsers) > 0 { 447 info.quotaUsers += "+" 448 } 449 info.quotaUsers += user 450 } 451 info.specs = append(info.specs, []quota.Spec{ 452 {Group: quota.User, Kind: kind, User: quotaUser}, 453 {Group: quota.Tree, Kind: kind, TreeID: info.treeID}, 454 {Group: quota.Global, Kind: kind}, 455 }...) 456 } 457 458 return info, nil 459 } 460 461 type logIDRequest interface { 462 GetLogId() int64 463 } 464 465 type mapIDRequest interface { 466 GetMapId() int64 467 } 468 469 type treeIDRequest interface { 470 GetTreeId() int64 471 } 472 473 type treeRequest interface { 474 GetTree() *trillian.Tree 475 } 476 477 // Combine combines unary interceptors. 478 // They are nested in order, so interceptor[0] calls on to (and sees the result of) interceptor[1], etc. 479 func Combine(interceptors ...grpc.UnaryServerInterceptor) grpc.UnaryServerInterceptor { 480 return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 481 for i := len(interceptors) - 1; i >= 0; i-- { 482 intercept := interceptors[i] 483 baseHandler := handler 484 handler = func(ctx context.Context, req interface{}) (interface{}, error) { 485 return intercept(ctx, req, info, baseHandler) 486 } 487 } 488 return handler(ctx, req) 489 } 490 } 491 492 // ErrorWrapper is a grpc.UnaryServerInterceptor that wraps the errors emitted by the underlying handler. 493 func ErrorWrapper(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { 494 ctx, span := spanFor(ctx, "ErrorWrapper") 495 defer span.End() 496 rsp, err := handler(ctx, req) 497 return rsp, errors.WrapError(err) 498 } 499 500 func spanFor(ctx context.Context, name string) (context.Context, *trace.Span) { 501 return trace.StartSpan(ctx, fmt.Sprintf("%s.%s", traceSpanRoot, name)) 502 }