...

Source file src/github.com/google/certificate-transparency-go/trillian/ctfe/ct_server/main.go

Documentation: github.com/google/certificate-transparency-go/trillian/ctfe/ct_server

     1  // Copyright 2016 Google LLC. 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  // The ct_server binary runs the CT personality.
    16  package main
    17  
    18  import (
    19  	"context"
    20  	"crypto"
    21  	"crypto/ecdsa"
    22  	"crypto/ed25519"
    23  	"crypto/rsa"
    24  	"flag"
    25  	"fmt"
    26  	"net/http"
    27  	"os"
    28  	"os/signal"
    29  	"strings"
    30  	"sync"
    31  	"syscall"
    32  	"time"
    33  
    34  	"github.com/google/certificate-transparency-go/trillian/ctfe"
    35  	"github.com/google/certificate-transparency-go/trillian/ctfe/configpb"
    36  	"github.com/google/trillian"
    37  	"github.com/google/trillian/crypto/keys"
    38  	"github.com/google/trillian/crypto/keys/der"
    39  	"github.com/google/trillian/crypto/keys/pem"
    40  	"github.com/google/trillian/crypto/keys/pkcs11"
    41  	"github.com/google/trillian/crypto/keyspb"
    42  	"github.com/google/trillian/monitoring/opencensus"
    43  	"github.com/google/trillian/monitoring/prometheus"
    44  	"github.com/prometheus/client_golang/prometheus/promhttp"
    45  	"github.com/rs/cors"
    46  	"github.com/tomasen/realip"
    47  	clientv3 "go.etcd.io/etcd/client/v3"
    48  	"go.etcd.io/etcd/client/v3/naming/endpoints"
    49  	"google.golang.org/grpc"
    50  	"google.golang.org/grpc/resolver"
    51  	"google.golang.org/grpc/resolver/manual"
    52  	"google.golang.org/protobuf/proto"
    53  	"k8s.io/klog/v2"
    54  )
    55  
    56  // Global flags that affect all log instances.
    57  var (
    58  	httpEndpoint       = flag.String("http_endpoint", "localhost:6962", "Endpoint for HTTP (host:port)")
    59  	metricsEndpoint    = flag.String("metrics_endpoint", "", "Endpoint for serving metrics; if left empty, metrics will be visible on --http_endpoint")
    60  	rpcBackend         = flag.String("log_rpc_server", "", "Backend specification; comma-separated list or etcd service name (if --etcd_servers specified). If unset backends are specified in config (as a LogMultiConfig proto)")
    61  	rpcDeadline        = flag.Duration("rpc_deadline", time.Second*10, "Deadline for backend RPC requests")
    62  	getSTHInterval     = flag.Duration("get_sth_interval", time.Second*180, "Interval between internal get-sth operations (0 to disable)")
    63  	logConfig          = flag.String("log_config", "", "File holding log config in text proto format")
    64  	maxGetEntries      = flag.Int64("max_get_entries", 0, "Max number of entries we allow in a get-entries request (0=>use default 1000)")
    65  	etcdServers        = flag.String("etcd_servers", "", "A comma-separated list of etcd servers")
    66  	etcdHTTPService    = flag.String("etcd_http_service", "trillian-ctfe-http", "Service name to announce our HTTP endpoint under")
    67  	etcdMetricsService = flag.String("etcd_metrics_service", "trillian-ctfe-metrics-http", "Service name to announce our HTTP metrics endpoint under")
    68  	maskInternalErrors = flag.Bool("mask_internal_errors", false, "Don't return error strings with Internal Server Error HTTP responses")
    69  	tracing            = flag.Bool("tracing", false, "If true opencensus Stackdriver tracing will be enabled. See https://opencensus.io/.")
    70  	tracingProjectID   = flag.String("tracing_project_id", "", "project ID to pass to stackdriver. Can be empty for GCP, consult docs for other platforms.")
    71  	tracingPercent     = flag.Int("tracing_percent", 0, "Percent of requests to be traced. Zero is a special case to use the DefaultSampler")
    72  	quotaRemote        = flag.Bool("quota_remote", true, "Enable requesting of quota for IP address sending incoming requests")
    73  	quotaIntermediate  = flag.Bool("quota_intermediate", true, "Enable requesting of quota for intermediate certificates in submitted chains")
    74  	handlerPrefix      = flag.String("handler_prefix", "", "If set e.g. to '/logs' will prefix all handlers that don't define a custom prefix")
    75  	pkcs11ModulePath   = flag.String("pkcs11_module_path", "", "Path to the PKCS#11 module to use for keys that use the PKCS#11 interface")
    76  )
    77  
    78  const unknownRemoteUser = "UNKNOWN_REMOTE"
    79  
    80  // nolint:staticcheck
    81  func main() {
    82  	klog.InitFlags(nil)
    83  	flag.Parse()
    84  	ctx := context.Background()
    85  
    86  	keys.RegisterHandler(&keyspb.PEMKeyFile{}, pem.FromProto)
    87  	keys.RegisterHandler(&keyspb.PrivateKey{}, der.FromProto)
    88  	keys.RegisterHandler(&keyspb.PKCS11Config{}, func(ctx context.Context, pb proto.Message) (crypto.Signer, error) {
    89  		if cfg, ok := pb.(*keyspb.PKCS11Config); ok {
    90  			return pkcs11.FromConfig(*pkcs11ModulePath, cfg)
    91  		}
    92  		return nil, fmt.Errorf("pkcs11: got %T, want *keyspb.PKCS11Config", pb)
    93  	})
    94  
    95  	if *maxGetEntries > 0 {
    96  		ctfe.MaxGetEntriesAllowed = *maxGetEntries
    97  	}
    98  
    99  	var cfg *configpb.LogMultiConfig
   100  	var err error
   101  	// Get log config from file before we start. This is a different proto
   102  	// type if we're using a multi backend configuration (no rpcBackend set
   103  	// in flags). The single-backend config is converted to a multi config so
   104  	// they can be treated the same.
   105  	if len(*rpcBackend) > 0 {
   106  		var cfgs []*configpb.LogConfig
   107  		if cfgs, err = ctfe.LogConfigFromFile(*logConfig); err == nil {
   108  			cfg = ctfe.ToMultiLogConfig(cfgs, *rpcBackend)
   109  		}
   110  	} else {
   111  		cfg, err = ctfe.MultiLogConfigFromFile(*logConfig)
   112  	}
   113  
   114  	if err != nil {
   115  		klog.Exitf("Failed to read config: %v", err)
   116  	}
   117  
   118  	beMap, err := ctfe.ValidateLogMultiConfig(cfg)
   119  	if err != nil {
   120  		klog.Exitf("Invalid config: %v", err)
   121  	}
   122  
   123  	klog.CopyStandardLogTo("WARNING")
   124  	klog.Info("**** CT HTTP Server Starting ****")
   125  
   126  	metricsAt := *metricsEndpoint
   127  	if metricsAt == "" {
   128  		metricsAt = *httpEndpoint
   129  	}
   130  
   131  	dialOpts := []grpc.DialOption{grpc.WithInsecure()}
   132  	if len(*etcdServers) > 0 {
   133  		// Use etcd to provide endpoint resolution.
   134  		cfg := clientv3.Config{Endpoints: strings.Split(*etcdServers, ","), DialTimeout: 5 * time.Second}
   135  		client, err := clientv3.New(cfg)
   136  		if err != nil {
   137  			klog.Exitf("Failed to connect to etcd at %v: %v", *etcdServers, err)
   138  		}
   139  
   140  		httpManager, err := endpoints.NewManager(client, *etcdHTTPService)
   141  		if err != nil {
   142  			klog.Exitf("Failed to create etcd http manager: %v", err)
   143  		}
   144  		metricsManager, err := endpoints.NewManager(client, *etcdMetricsService)
   145  		if err != nil {
   146  			klog.Exitf("Failed to create etcd metrics manager: %v", err)
   147  		}
   148  
   149  		etcdHTTPKey := fmt.Sprintf("%s/%s", *etcdHTTPService, *httpEndpoint)
   150  		klog.Infof("Announcing our presence at %v with %+v", etcdHTTPKey, *httpEndpoint)
   151  		if err := httpManager.AddEndpoint(ctx, etcdHTTPKey, endpoints.Endpoint{Addr: *httpEndpoint}); err != nil {
   152  			klog.Exitf("AddEndpoint(): %v", err)
   153  		}
   154  
   155  		etcdMetricsKey := fmt.Sprintf("%s/%s", *etcdMetricsService, metricsAt)
   156  		klog.Infof("Announcing our presence in %v with %+v", *etcdMetricsService, metricsAt)
   157  		if err := metricsManager.AddEndpoint(ctx, etcdMetricsKey, endpoints.Endpoint{Addr: metricsAt}); err != nil {
   158  			klog.Exitf("AddEndpoint(): %v", err)
   159  		}
   160  
   161  		defer func() {
   162  			klog.Infof("Removing our presence in %v", etcdHTTPKey)
   163  			if err := httpManager.DeleteEndpoint(ctx, etcdHTTPKey); err != nil {
   164  				klog.Errorf("DeleteEndpoint(): %v", err)
   165  			}
   166  			klog.Infof("Removing our presence in %v", etcdMetricsKey)
   167  			if err := metricsManager.DeleteEndpoint(ctx, etcdMetricsKey); err != nil {
   168  				klog.Errorf("DeleteEndpoint(): %v", err)
   169  			}
   170  		}()
   171  	} else if strings.Contains(*rpcBackend, ",") {
   172  		// This should probably not be used in production. Either use etcd or a gRPC
   173  		// load balancer. It's only used by the integration tests.
   174  		klog.Warning("Multiple RPC backends from flags not recommended for production. Should probably be using etcd or a gRPC load balancer / proxy.")
   175  		res := manual.NewBuilderWithScheme("whatever")
   176  		backends := strings.Split(*rpcBackend, ",")
   177  		endpoints := make([]resolver.Endpoint, 0, len(backends))
   178  		for _, backend := range backends {
   179  			endpoints = append(endpoints, resolver.Endpoint{Addresses: []resolver.Address{{Addr: backend}}})
   180  		}
   181  		res.InitialState(resolver.State{Endpoints: endpoints})
   182  		resolver.SetDefaultScheme(res.Scheme())
   183  		dialOpts = append(dialOpts, grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin":{}}]}`), grpc.WithResolvers(res))
   184  	} else {
   185  		klog.Infof("Using regular DNS resolver")
   186  		dialOpts = append(dialOpts, grpc.WithDefaultServiceConfig(`{"loadBalancingConfig": [{"round_robin":{}}]}`))
   187  	}
   188  
   189  	// Dial all our log backends.
   190  	clientMap := make(map[string]trillian.TrillianLogClient)
   191  	for _, be := range beMap {
   192  		klog.Infof("Dialling backend: %v", be)
   193  		if len(beMap) == 1 {
   194  			// If there's only one of them we use the blocking option as we can't
   195  			// serve anything until connected.
   196  			dialOpts = append(dialOpts, grpc.WithBlock())
   197  		}
   198  		conn, err := grpc.Dial(be.BackendSpec, dialOpts...)
   199  		if err != nil {
   200  			klog.Exitf("Could not dial RPC server: %v: %v", be, err)
   201  		}
   202  		defer conn.Close()
   203  		clientMap[be.Name] = trillian.NewTrillianLogClient(conn)
   204  	}
   205  
   206  	// Allow cross-origin requests to all handlers registered on corsMux.
   207  	// This is safe for CT log handlers because the log is public and
   208  	// unauthenticated so cross-site scripting attacks are not a concern.
   209  	corsMux := http.NewServeMux()
   210  	corsHandler := cors.AllowAll().Handler(corsMux)
   211  	http.Handle("/", corsHandler)
   212  
   213  	// Register handlers for all the configured logs using the correct RPC
   214  	// client.
   215  	var publicKeys []crypto.PublicKey
   216  	for _, c := range cfg.LogConfigs.Config {
   217  		inst, err := setupAndRegister(ctx, clientMap[c.LogBackendName], *rpcDeadline, c, corsMux, *handlerPrefix, *maskInternalErrors)
   218  		if err != nil {
   219  			klog.Exitf("Failed to set up log instance for %+v: %v", cfg, err)
   220  		}
   221  		if *getSTHInterval > 0 {
   222  			go inst.RunUpdateSTH(ctx, *getSTHInterval)
   223  		}
   224  
   225  		// Ensure that this log does not share the same private key as any other
   226  		// log that has already been set up and registered.
   227  		if publicKey := inst.GetPublicKey(); publicKey != nil {
   228  			for _, p := range publicKeys {
   229  				switch pub := publicKey.(type) {
   230  				case *ecdsa.PublicKey:
   231  					if pub.Equal(p) {
   232  						klog.Exitf("Same private key used by more than one log")
   233  					}
   234  				case ed25519.PublicKey:
   235  					if pub.Equal(p) {
   236  						klog.Exitf("Same private key used by more than one log")
   237  					}
   238  				case *rsa.PublicKey:
   239  					if pub.Equal(p) {
   240  						klog.Exitf("Same private key used by more than one log")
   241  					}
   242  				}
   243  			}
   244  			publicKeys = append(publicKeys, publicKey)
   245  		}
   246  	}
   247  
   248  	// Return a 200 on the root, for GCE default health checking :/
   249  	corsMux.HandleFunc("/", func(resp http.ResponseWriter, req *http.Request) {
   250  		if req.URL.Path == "/" {
   251  			resp.WriteHeader(http.StatusOK)
   252  		} else {
   253  			resp.WriteHeader(http.StatusNotFound)
   254  		}
   255  	})
   256  
   257  	// Export a healthz target.
   258  	corsMux.HandleFunc("/healthz", func(resp http.ResponseWriter, req *http.Request) {
   259  		// TODO(al): Wire this up to tell the truth.
   260  		if _, err := resp.Write([]byte("ok")); err != nil {
   261  			klog.Errorf("resp.Write(): %v", err)
   262  		}
   263  	})
   264  
   265  	if metricsAt != *httpEndpoint {
   266  		// Run a separate handler for metrics.
   267  		go func() {
   268  			mux := http.NewServeMux()
   269  			mux.Handle("/metrics", promhttp.Handler())
   270  			metricsServer := http.Server{Addr: metricsAt, Handler: mux}
   271  			err := metricsServer.ListenAndServe()
   272  			klog.Warningf("Metrics server exited: %v", err)
   273  		}()
   274  	} else {
   275  		// Handle metrics on the DefaultServeMux.
   276  		http.Handle("/metrics", promhttp.Handler())
   277  	}
   278  
   279  	// If we're enabling tracing we need to use an instrumented http.Handler.
   280  	var handler http.Handler
   281  	if *tracing {
   282  		handler, err = opencensus.EnableHTTPServerTracing(*tracingProjectID, *tracingPercent)
   283  		if err != nil {
   284  			klog.Exitf("Failed to initialize stackdriver / opencensus tracing: %v", err)
   285  		}
   286  	}
   287  
   288  	// Bring up the HTTP server and serve until we get a signal not to.
   289  	srv := http.Server{Addr: *httpEndpoint, Handler: handler}
   290  	shutdownWG := new(sync.WaitGroup)
   291  	go awaitSignal(func() {
   292  		shutdownWG.Add(1)
   293  		defer shutdownWG.Done()
   294  		// Allow 60s for any pending requests to finish then terminate any stragglers
   295  		ctx, cancel := context.WithTimeout(context.Background(), time.Second*60)
   296  		defer cancel()
   297  		klog.Info("Shutting down HTTP server...")
   298  		if err := srv.Shutdown(ctx); err != nil {
   299  			klog.Errorf("srv.Shutdown(): %v", err)
   300  		}
   301  		klog.Info("HTTP server shutdown")
   302  	})
   303  
   304  	err = srv.ListenAndServe()
   305  	if err != http.ErrServerClosed {
   306  		klog.Warningf("Server exited: %v", err)
   307  	}
   308  	// Wait will only block if the function passed to awaitSignal was called,
   309  	// in which case it'll block until the HTTP server has gracefully shutdown
   310  	shutdownWG.Wait()
   311  	klog.Flush()
   312  }
   313  
   314  // awaitSignal waits for standard termination signals, then runs the given
   315  // function; it should be run as a separate goroutine.
   316  func awaitSignal(doneFn func()) {
   317  	// Arrange notification for the standard set of signals used to terminate a server
   318  	sigs := make(chan os.Signal, 1)
   319  	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
   320  
   321  	// Now block main and wait for a signal
   322  	sig := <-sigs
   323  	klog.Warningf("Signal received: %v", sig)
   324  	klog.Flush()
   325  
   326  	doneFn()
   327  }
   328  
   329  func setupAndRegister(ctx context.Context, client trillian.TrillianLogClient, deadline time.Duration, cfg *configpb.LogConfig, mux *http.ServeMux, globalHandlerPrefix string, maskInternalErrors bool) (*ctfe.Instance, error) {
   330  	vCfg, err := ctfe.ValidateLogConfig(cfg)
   331  	if err != nil {
   332  		return nil, err
   333  	}
   334  
   335  	opts := ctfe.InstanceOptions{
   336  		Validated:          vCfg,
   337  		Client:             client,
   338  		Deadline:           deadline,
   339  		MetricFactory:      prometheus.MetricFactory{},
   340  		RequestLog:         new(ctfe.DefaultRequestLog),
   341  		MaskInternalErrors: maskInternalErrors,
   342  	}
   343  	if *quotaRemote {
   344  		klog.Info("Enabling quota for requesting IP")
   345  		opts.RemoteQuotaUser = func(r *http.Request) string {
   346  			var remoteUser = realip.FromRequest(r)
   347  			if len(remoteUser) == 0 {
   348  				return unknownRemoteUser
   349  			}
   350  			return remoteUser
   351  		}
   352  	}
   353  	if *quotaIntermediate {
   354  		klog.Info("Enabling quota for intermediate certificates")
   355  		opts.CertificateQuotaUser = ctfe.QuotaUserForCert
   356  	}
   357  	// Full handler pattern will be of the form "/logs/yyz/ct/v1/add-chain", where "/logs" is the
   358  	// HandlerPrefix and "yyz" is the c.Prefix for this particular log. Use the default
   359  	// HandlerPrefix unless the log config overrides it. The custom prefix in
   360  	// the log configuration intended for use in migration scenarios where logs
   361  	// have an existing URL path that differs from the global one. For example
   362  	// if all new logs are served on "/logs/log/..." and a previously existing
   363  	// log is at "/log/..." this is now supported.
   364  	lhp := globalHandlerPrefix
   365  	if ohPrefix := cfg.OverrideHandlerPrefix; len(ohPrefix) > 0 {
   366  		klog.Infof("Log with prefix: %s is using a custom HandlerPrefix: %s", cfg.Prefix, ohPrefix)
   367  		lhp = "/" + strings.Trim(ohPrefix, "/")
   368  	}
   369  	inst, err := ctfe.SetUpInstance(ctx, opts)
   370  	if err != nil {
   371  		return nil, err
   372  	}
   373  	for path, handler := range inst.Handlers {
   374  		mux.Handle(lhp+path, handler)
   375  	}
   376  	return inst, nil
   377  }
   378  

View as plain text