diff options
Diffstat (limited to 'cmd/sigsum-log-primary')
| -rw-r--r-- | cmd/sigsum-log-primary/README.md | 54 | ||||
| -rw-r--r-- | cmd/sigsum-log-primary/main.go | 233 | 
2 files changed, 287 insertions, 0 deletions
| diff --git a/cmd/sigsum-log-primary/README.md b/cmd/sigsum-log-primary/README.md new file mode 100644 index 0000000..a824173 --- /dev/null +++ b/cmd/sigsum-log-primary/README.md @@ -0,0 +1,54 @@ +# Run Trillian + sigsum-log-primary locally +Trillian uses a database.  So, we will need to set that up.  It is documented +[here](https://github.com/google/trillian#mysql-setup), and how to check that it +is setup properly +[here](https://github.com/google/certificate-transparency-go/blob/master/trillian/docs/ManualDeployment.md#data-storage). + +Other than the database we need Trillian log signer, Trillian log server, and +sigsum-log-primary. sigsum-log-primary has been tested with trillian v.1.3.13. +``` +$ go install github.com/google/trillian/cmd/trillian_log_signer@v1.3.13 +$ go install github.com/google/trillian/cmd/trillian_log_server@v1.3.13 +``` + +Start Trillian log signer: +``` +trillian_log_signer --logtostderr -v 9 --force_master --rpc_endpoint=localhost:6961 --http_endpoint=localhost:6964 --num_sequencers 1 --sequencer_interval 100ms --batch_size 100 +``` + +Start Trillian log server: +``` +trillian_log_server --logtostderr -v 9 --rpc_endpoint=localhost:6962 --http_endpoint=localhost:6963 +``` + +As described in more detail +[here](https://github.com/google/certificate-transparency-go/blob/master/trillian/docs/ManualDeployment.md#trillian-services), +we need to provision a Merkle tree once: +``` +$ go install github.com/google/trillian/cmd/createtree@v1.3.13 +$ createtree --admin_server localhost:6962 +<tree id> +``` + +Hang on to `<tree id>`.  Our sigsum-log-primary instance will use it when talking to +the Trillian log server to specify which Merkle tree we are working against. + +(If you take a look in the `Trees` table you will see that the tree has been +provisioned.) + +We will also need a public key-pair for sigsum-log-primary. +``` +$ go install git.sigsum.org/sigsum-go/cmd/sigsum-debug@latest +$ sigsum-debug key private | tee sk | sigsum-debug key public > vk +``` + +Start sigsum-log-primary: +``` +$ tree_id=<tree_id> +$ sk=<sk> +$ sigsum-log-primary --logtostderr -v 9 --http_endpoint localhost:6965 --log_rpc_server localhost:6962 --trillian_id $tree_id --key <(echo sk) +``` + +Quick test: +- curl http://localhost:6965/sigsum/v0/get-tree-head-to-cosign +- try `submit` and `cosign` commands in `cmd/tmp` diff --git a/cmd/sigsum-log-primary/main.go b/cmd/sigsum-log-primary/main.go new file mode 100644 index 0000000..f64643a --- /dev/null +++ b/cmd/sigsum-log-primary/main.go @@ -0,0 +1,233 @@ +// Package main provides a sigsum-log-primary binary +package main + +import ( +	"context" +	"encoding/hex" +	"flag" +	"fmt" +	"net/http" +	"os" +	"os/signal" +	"strings" +	"sync" +	"syscall" +	"time" + +	"github.com/google/trillian" +	"github.com/prometheus/client_golang/prometheus/promhttp" +	"google.golang.org/grpc" + +	"git.sigsum.org/log-go/internal/db" +	"git.sigsum.org/log-go/internal/node/primary" +	"git.sigsum.org/log-go/internal/state" +	"git.sigsum.org/log-go/internal/utils" +	"git.sigsum.org/sigsum-go/pkg/client" +	"git.sigsum.org/sigsum-go/pkg/dns" +	"git.sigsum.org/sigsum-go/pkg/log" +	"git.sigsum.org/sigsum-go/pkg/merkle" +	"git.sigsum.org/sigsum-go/pkg/types" +) + +var ( +	externalEndpoint = flag.String("external-endpoint", "localhost:6965", "host:port specification of where sigsum-log-primary serves clients") +	internalEndpoint = flag.String("internal-endpoint", "localhost:6967", "host:port specification of where sigsum-log-primary serves other log nodes") +	rpcBackend       = flag.String("trillian-rpc-server", "localhost:6962", "host:port specification of where Trillian serves clients") +	prefix           = flag.String("url-prefix", "", "a prefix that precedes /sigsum/v0/<endpoint>") +	trillianID       = flag.Int64("tree-id", 0, "tree identifier in the Trillian database") +	deadline         = flag.Duration("deadline", time.Second*10, "deadline for backend requests") +	key              = flag.String("key", "", "path to file with hex-encoded Ed25519 private key") +	witnesses        = flag.String("witnesses", "", "comma-separated list of trusted witness public keys in hex") +	maxRange         = flag.Int64("max-range", 10, "maximum number of entries that can be retrived in a single request") +	interval         = flag.Duration("interval", time.Second*30, "interval used to rotate the log's cosigned STH") +	shardStart       = flag.Int64("shard-interval-start", 0, "start of shard interval since the UNIX epoch in seconds") +	testMode         = flag.Bool("test-mode", false, "run in test mode (Default: false)") +	logFile          = flag.String("log-file", "", "file to write logs to (Default: stderr)") +	logLevel         = flag.String("log-level", "info", "log level (Available options: debug, info, warning, error. Default: info)") +	logColor         = flag.Bool("log-color", false, "colored logging output (Default: false)") +	secondaryURL     = flag.String("secondary-url", "", "secondary node endpoint for fetching latest replicated tree head") +	secondaryPubkey  = flag.String("secondary-pubkey", "", "hex-encoded Ed25519 public key for secondary node") + +	gitCommit = "unknown" +) + +func main() { +	flag.Parse() + +	if err := utils.SetupLogging(*logFile, *logLevel, *logColor); err != nil { +		log.Fatal("setup logging: %v", err) +	} +	log.Info("log-go git-commit %s", gitCommit) + +	// wait for clean-up before exit +	var wg sync.WaitGroup +	defer wg.Wait() +	ctx, cancel := context.WithCancel(context.Background()) +	defer cancel() + +	log.Debug("configuring log-go-primary") +	node, err := setupPrimaryFromFlags() +	if err != nil { +		log.Fatal("setup primary: %v", err) +	} + +	log.Debug("starting primary state manager routine") +	go func() { +		wg.Add(1) +		defer wg.Done() +		node.Stateman.Run(ctx) +		log.Debug("state manager shutdown") +		cancel() // must have state manager running +	}() + +	server := &http.Server{Addr: *externalEndpoint, Handler: node.PublicHTTPMux} +	intserver := &http.Server{Addr: *internalEndpoint, Handler: node.InternalHTTPMux} +	log.Debug("starting await routine") +	go await(ctx, func() { +		wg.Add(1) +		defer wg.Done() +		ctxInner, _ := context.WithTimeout(ctx, time.Second*60) +		log.Info("stopping http server, please wait...") +		server.Shutdown(ctxInner) +		log.Info("... done") +		log.Info("stopping internal api server, please wait...") +		intserver.Shutdown(ctxInner) +		log.Info("... done") +		log.Info("stopping go routines, please wait...") +		cancel() +		log.Info("... done") +	}) + +	go func() { +		wg.Add(1) +		defer wg.Done() +		log.Info("serving log nodes on %v/%v", *internalEndpoint, *prefix) +		if err = intserver.ListenAndServe(); err != http.ErrServerClosed { +			log.Error("serve(intserver): %v", err) +		} +		log.Debug("internal endpoints server shut down") +		cancel() +	}() + +	log.Info("serving clients on %v/%v", *externalEndpoint, *prefix) +	if err = server.ListenAndServe(); err != http.ErrServerClosed { +		log.Error("serve(server): %v", err) +	} + +} + +// setupPrimaryFromFlags() sets up a new sigsum primary node from flags. +func setupPrimaryFromFlags() (*primary.Primary, error) { +	var p primary.Primary +	var err error + +	// Setup logging configuration. +	p.Signer, p.Config.LogID, err = utils.NewLogIdentity(*key) +	if err != nil { +		return nil, fmt.Errorf("newLogIdentity: %v", err) +	} + +	p.Config.TreeID = *trillianID +	p.Config.Prefix = *prefix +	p.Config.MaxRange = *maxRange +	p.Config.Deadline = *deadline +	p.Config.Interval = *interval +	p.Config.ShardStart = uint64(*shardStart) +	if *shardStart < 0 { +		return nil, fmt.Errorf("shard start must be larger than zero") +	} +	p.Config.Witnesses, err = newWitnessMap(*witnesses) +	if err != nil { +		return nil, fmt.Errorf("newWitnessMap: %v", err) +	} + +	// Setup trillian client. +	dialOpts := []grpc.DialOption{grpc.WithInsecure(), grpc.WithBlock(), grpc.WithTimeout(p.Config.Deadline)} +	conn, err := grpc.Dial(*rpcBackend, dialOpts...) +	if err != nil { +		return nil, fmt.Errorf("Dial: %v", err) +	} +	p.TrillianClient = &db.TrillianClient{ +		TreeID: p.TreeID, +		GRPC:   trillian.NewTrillianLogClient(conn), +	} + +	// Setup secondary node configuration. +	if *secondaryURL != "" && *secondaryPubkey != "" { +		pubkey, err := utils.PubkeyFromHexString(*secondaryPubkey) +		if err != nil { +			return nil, fmt.Errorf("invalid secondary node pubkey: %v", err) +		} +		p.Secondary = client.New(client.Config{ +			LogURL: *secondaryURL, +			LogPub: *pubkey, +		}) +	} else { +		p.Secondary = client.New(client.Config{}) +	} + +	// Setup state manager. +	p.Stateman, err = state.NewStateManagerSingle(p.TrillianClient, p.Signer, p.Config.Interval, p.Config.Deadline, p.Secondary) +	if err != nil { +		return nil, fmt.Errorf("NewStateManagerSingle: %v", err) +	} +	if *testMode == false { +		p.DNS = dns.NewDefaultResolver() +	} else { +		p.DNS = dns.NewDummyResolver() +	} + +	// TODO: verify that GRPC.TreeType() == LOG. + +	// Register HTTP endpoints. +	mux := http.NewServeMux() +	for _, h := range p.PublicHTTPHandlers() { +		log.Debug("adding external handler: %s", h.Path()) +		mux.Handle(h.Path(), h) +	} +	p.PublicHTTPMux = mux + +	mux = http.NewServeMux() +	for _, h := range p.InternalHTTPHandlers() { +		log.Debug("adding internal handler: %s", h.Path()) +		mux.Handle(h.Path(), h) +	} +	p.InternalHTTPMux = mux + +	log.Debug("adding prometheus handler to internal mux, on path: /metrics") +	http.Handle("/metrics", promhttp.Handler()) + +	return &p, nil +} + +// newWitnessMap creates a new map of trusted witnesses +func newWitnessMap(witnesses string) (map[merkle.Hash]types.PublicKey, error) { +	w := make(map[merkle.Hash]types.PublicKey) +	if len(witnesses) > 0 { +		for _, witness := range strings.Split(witnesses, ",") { +			b, err := hex.DecodeString(witness) +			if err != nil { +				return nil, fmt.Errorf("DecodeString: %v", err) +			} + +			var vk types.PublicKey +			if n := copy(vk[:], b); n != types.PublicKeySize { +				return nil, fmt.Errorf("Invalid public key size: %v", n) +			} +			w[*merkle.HashFn(vk[:])] = vk +		} +	} +	return w, nil +} + +// await waits for a shutdown signal and then runs a clean-up function +func await(ctx context.Context, done func()) { +	sigs := make(chan os.Signal, 1) +	signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) +	select { +	case <-sigs: +	case <-ctx.Done(): +	} +	log.Debug("received shutdown signal") +	done() +} | 
