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  }