1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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
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
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
102
103
104
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
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
173
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
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
195
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
207
208
209 corsMux := http.NewServeMux()
210 corsHandler := cors.AllowAll().Handler(corsMux)
211 http.Handle("/", corsHandler)
212
213
214
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
226
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
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
258 corsMux.HandleFunc("/healthz", func(resp http.ResponseWriter, req *http.Request) {
259
260 if _, err := resp.Write([]byte("ok")); err != nil {
261 klog.Errorf("resp.Write(): %v", err)
262 }
263 })
264
265 if metricsAt != *httpEndpoint {
266
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
276 http.Handle("/metrics", promhttp.Handler())
277 }
278
279
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
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
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
309
310 shutdownWG.Wait()
311 klog.Flush()
312 }
313
314
315
316 func awaitSignal(doneFn func()) {
317
318 sigs := make(chan os.Signal, 1)
319 signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
320
321
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
358
359
360
361
362
363
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