diff --git a/README.md b/README.md index 1720924fb..ac6918873 100644 --- a/README.md +++ b/README.md @@ -63,12 +63,10 @@ Here’s a high level overview of the components in play that we would like to b ## [Trillian](https://github.com/google/trillian) -For Trillian, there needs to be a database and a schema before Trillian services are able to function. Our assumption is that there is a provisioned mysql database, for our Github actions, we spin up a [container](https://hub.docker.com/_/mysql) that has the mysql running, and then we need to create a [schema](https://github.com/google/trillian/blob/master/storage/mysql/schema/storage.sql) for it. - -For this we create a Kubernetes Job, which runs against a given mysql database and verifies that all the tables and indices exist. It does not currently handle upgrades to schema, but this is a feature that could be added, but looking at the Change History of the schema, the schema seems to be stable and adding this feature seemed not worth doing at this point. - -So, we have a k8s Job called **‘CreateDB’** which is responsible for creating the schema for a given database. As a reminder, because this is a job, automation can gate any further action before this Job successfully completes. We can also (but not currently) make Trillian services depend on the output of ‘**CreateDB’** before proceeding (by using the mounting technique described above), but we have not had need for that yet because they recover if the schema does not exist. - +For Trillian, there needs to be a database before Trillian services are able to +function. Our assumption is that there is a provisioned mysql database, for our +Github actions, we spin up a [container](gcr.io/trillian-opensource-ci/db_server@sha256:e58334fead37d1f03c77c80f66008966e79739d85214b373b3c0a69f97c59359) that +has the mysql running, and Trillian [schema](https://github.com/google/trillian/blob/master/storage/mysql/schema/storage.sql) on it. ## [Rekor](https://github.com/sigstore/rekor) diff --git a/cmd/trillian/createdb/main.go b/cmd/trillian/createdb/main.go deleted file mode 100644 index a86b26124..000000000 --- a/cmd/trillian/createdb/main.go +++ /dev/null @@ -1,296 +0,0 @@ -/* -Copyright 2021 Chainguard, Inc. -SPDX-License-Identifier: Apache-2.0 -*/ - -package main - -import ( - "context" - "flag" - "fmt" - "log" - "strings" - "time" - - "database/sql" - - _ "github.com/go-sql-driver/mysql" - - "knative.dev/pkg/logging" - "knative.dev/pkg/signals" -) - -// These are from the Trillian schema, with the comments removed. -// https://github.com/google/trillian/blob/master/storage/mysql/schema/storage.sql -const ( - // This is used to query to see if there are indices on a particular table. - indexCheck = ` - select count(*) from information_schema.statistics where table_schema = ? and table_name=? and index_name=?; - ` - createTableTrees = ` - CREATE TABLE IF NOT EXISTS Trees( - TreeId BIGINT NOT NULL, - TreeState ENUM('ACTIVE', 'FROZEN', 'DRAINING') NOT NULL, - TreeType ENUM('LOG', 'MAP', 'PREORDERED_LOG') NOT NULL, - HashStrategy ENUM('RFC6962_SHA256', 'TEST_MAP_HASHER', 'OBJECT_RFC6962_SHA256', 'CONIKS_SHA512_256', 'CONIKS_SHA256') NOT NULL, - HashAlgorithm ENUM('SHA256') NOT NULL, - SignatureAlgorithm ENUM('ECDSA', 'RSA', 'ED25519') NOT NULL, - DisplayName VARCHAR(20), - Description VARCHAR(200), - CreateTimeMillis BIGINT NOT NULL, - UpdateTimeMillis BIGINT NOT NULL, - MaxRootDurationMillis BIGINT NOT NULL, - PrivateKey MEDIUMBLOB NOT NULL, - PublicKey MEDIUMBLOB NOT NULL, - Deleted BOOLEAN, - DeleteTimeMillis BIGINT, - PRIMARY KEY(TreeId) - ); -` - - createTableTreeControl = ` - CREATE TABLE IF NOT EXISTS TreeControl( - TreeId BIGINT NOT NULL, - SigningEnabled BOOLEAN NOT NULL, - SequencingEnabled BOOLEAN NOT NULL, - SequenceIntervalSeconds INTEGER NOT NULL, - PRIMARY KEY(TreeId), - FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE - ); -` - - createTableSubtree = ` -CREATE TABLE IF NOT EXISTS Subtree( - TreeId BIGINT NOT NULL, - SubtreeId VARBINARY(255) NOT NULL, - Nodes MEDIUMBLOB NOT NULL, - SubtreeRevision INTEGER NOT NULL, - -- Key columns must be in ASC order in order to benefit from group-by/min-max - -- optimization in MySQL. - PRIMARY KEY(TreeId, SubtreeId, SubtreeRevision), - FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE - ); -` - - createTableTreeHead = ` -CREATE TABLE IF NOT EXISTS TreeHead( - TreeId BIGINT NOT NULL, - TreeHeadTimestamp BIGINT, - TreeSize BIGINT, - RootHash VARBINARY(255) NOT NULL, - RootSignature VARBINARY(1024) NOT NULL, - TreeRevision BIGINT, - PRIMARY KEY(TreeId, TreeHeadTimestamp), - FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE - ); -` - - createIndexTreeHeadRevision = ` - CREATE UNIQUE INDEX TreeHeadRevisionIdx - ON TreeHead(TreeId, TreeRevision); -` - - createTableLeafData = ` - CREATE TABLE IF NOT EXISTS LeafData( - TreeId BIGINT NOT NULL, - -- This is a personality specific has of some subset of the leaf data. - -- It's only purpose is to allow Trillian to identify duplicate entries in - -- the context of the personality. - LeafIdentityHash VARBINARY(255) NOT NULL, - -- This is the data stored in the leaf for example in CT it contains a DER encoded - -- X.509 certificate but is application dependent - LeafValue LONGBLOB NOT NULL, - -- This is extra data that the application can associate with the leaf should it wish to. - -- This data is not included in signing and hashing. - ExtraData LONGBLOB, - -- The timestamp from when this leaf data was first queued for inclusion. - QueueTimestampNanos BIGINT NOT NULL, - PRIMARY KEY(TreeId, LeafIdentityHash), - FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE - ); -` - - createTableSequencedLeafData = ` -CREATE TABLE IF NOT EXISTS SequencedLeafData( - TreeId BIGINT NOT NULL, - SequenceNumber BIGINT UNSIGNED NOT NULL, - -- This is a personality specific has of some subset of the leaf data. - -- It's only purpose is to allow Trillian to identify duplicate entries in - -- the context of the personality. - LeafIdentityHash VARBINARY(255) NOT NULL, - -- This is a MerkleLeafHash as defined by the treehasher that the log uses. For example for - -- CT this hash will include the leaf prefix byte as well as the leaf data. - MerkleLeafHash VARBINARY(255) NOT NULL, - IntegrateTimestampNanos BIGINT NOT NULL, - PRIMARY KEY(TreeId, SequenceNumber), - FOREIGN KEY(TreeId) REFERENCES Trees(TreeId) ON DELETE CASCADE, - FOREIGN KEY(TreeId, LeafIdentityHash) REFERENCES LeafData(TreeId, LeafIdentityHash) ON DELETE CASCADE - ); -` - - createIndexSequencedLeafMerkle = ` - CREATE INDEX SequencedLeafMerkleIdx - ON SequencedLeafData(TreeId, MerkleLeafHash); -` - - createTableUnsequenced = ` - CREATE TABLE IF NOT EXISTS Unsequenced( - TreeId BIGINT NOT NULL, - -- The bucket field is to allow the use of time based ring bucketed schemes if desired. If - -- unused this should be set to zero for all entries. - Bucket INTEGER NOT NULL, - -- This is a personality specific hash of some subset of the leaf data. - -- It's only purpose is to allow Trillian to identify duplicate entries in - -- the context of the personality. - LeafIdentityHash VARBINARY(255) NOT NULL, - -- This is a MerkleLeafHash as defined by the treehasher that the log uses. For example for - -- CT this hash will include the leaf prefix byte as well as the leaf data. - MerkleLeafHash VARBINARY(255) NOT NULL, - QueueTimestampNanos BIGINT NOT NULL, - -- This is a SHA256 hash of the TreeID, LeafIdentityHash and QueueTimestampNanos. It is used - -- for batched deletes from the table when trillian_log_server and trillian_log_signer are - -- built with the batched_queue tag. - QueueID VARBINARY(32) DEFAULT NULL UNIQUE, - PRIMARY KEY (TreeId, Bucket, QueueTimestampNanos, LeafIdentityHash) - ); -` -) - -// We need to create the tables in certain order because other tables -// depend on others, so we list the order here and can't just yolo the -// createTables map. -var tables = []string{ - "Trees", - "TreeControl", - "Subtree", - "TreeHead", - "LeafData", - "SequencedLeafData", - "Unsequenced", -} - -// Map from table name to a statement that creates the table. -var createTables = map[string]string{ - "Trees": createTableTrees, - "TreeControl": createTableTreeControl, - "Subtree": createTableSubtree, - "TreeHead": createTableTreeHead, - "LeafData": createTableLeafData, - "SequencedLeafData": createTableSequencedLeafData, - "Unsequenced": createTableUnsequenced, -} - -type indexCreate struct { - tableName string - createStr string -} - -// Map from index name to table that should have it, which has statement -// to create the index if it's missing. -var createIndices = map[string]indexCreate{ - "TreeHeadRevisionIdx": {"TreeHead", createIndexTreeHeadRevision}, - "SequencedLeafMerkleIdx": {"SequencedLeafData", createIndexSequencedLeafMerkle}, -} - -type envConfig struct { - DatabaseName string `envconfig:"DATABASE" default:"trillian" required:"true"` - ExitDir string `envconfig:"EXIT_DIR" required:"false"` -} - -var ( - dbName = flag.String("db_name", "trillian", "Database name to tack on to the connection string to select the right db.") - mysqlURI = flag.String("mysql_uri", "", "SQL connection string in mysql format, for example: $(USER):$(PWD)@tcp($(HOST):3306)/$(DATABASE_NAME)") -) - -func main() { - flag.Parse() - if *mysqlURI == "" { - log.Panicf("Need to specify mysql_uri to know where to connect to") - } - if *dbName == "" { - log.Panicf("Need to specify database name") - } - connStr := fmt.Sprintf("%s/%s", strings.TrimSuffix(*mysqlURI, "/"), *dbName) - ctx := signals.NewContext() - db, err := sql.Open("mysql", connStr) - if err != nil { - log.Panicf("failed to open db connection: %v", err) - } - defer db.Close() - for i := 0; i < 5; i++ { - if err := db.Ping(); err == nil { - logging.FromContext(ctx).Infof("Ping to DB succeeded") - break - } - time.Sleep(2 * time.Second) - } - if err := db.Ping(); err != nil { - log.Panicf("failed to ping db: %v", err) - } - // Grab the tables - existingTables := map[string]bool{} - tableRows, err := db.Query("show tables") - if err != nil { - logging.FromContext(ctx).Panicf("show tables failed: %+v", err) - } - defer tableRows.Close() - for next := tableRows.Next(); next; next = tableRows.Next() { - var tableName string - err = tableRows.Scan(&tableName) - if err != nil { - logging.FromContext(ctx).Errorf("Failed to get row %+v", err) - } - existingTables[tableName] = true - } - - // Check the tables for existence and if they don't exist, create them. - for _, table := range tables { - if existingTables[table] { - logging.FromContext(ctx).Infof("Table %q exists", table) - continue - } - logging.FromContext(ctx).Infof("Table %q does not exist, creating", table) - if _, err = db.Exec(createTables[table]); err != nil { - logging.FromContext(ctx).Errorf("Failed to create table %q: %v", table, err) - } else { - logging.FromContext(ctx).Errorf("Created table %q", table) - } - } - - for indexName, tableAndCreate := range createIndices { - tableName := tableAndCreate.tableName - indexExists, err := indexExists(ctx, db, *dbName, indexName, tableName) - if err != nil { - logging.FromContext(ctx).Panicf("Failed to check %q on %q for existence", indexName, tableName) - } - if indexExists { - logging.FromContext(ctx).Infof("Index %q exists on %q", indexName, tableName) - continue - } - logging.FromContext(ctx).Infof("Index %q does not exist on %q, creating", indexName, tableName) - if _, err = db.Exec(tableAndCreate.createStr); err != nil { - logging.FromContext(ctx).Errorf("Failed to create index %q on %q: %v", indexName, tableName, err) - } else { - logging.FromContext(ctx).Errorf("Created index %q on table %q", indexName, tableName) - } - } -} - -func indexExists(ctx context.Context, db *sql.DB, dbName, indexName, table string) (bool, error) { - tableRows, err := db.Query(indexCheck, dbName, table, indexName) - if err != nil { - logging.FromContext(ctx).Panicf("checking for index failed: %+v", err) - return false, err - } - defer tableRows.Close() - var indexCount int64 - for next := tableRows.Next(); next; next = tableRows.Next() { - err = tableRows.Scan(&indexCount) - if err != nil { - logging.FromContext(ctx).Errorf("Failed to get row %+v", err) - } - logging.FromContext(ctx).Infof("Found index %q on table %q : %+v", indexName, table, indexCount) - } - return indexCount > 0, nil -} diff --git a/cmd/trillian/createtree/main.go b/cmd/trillian/createtree/main.go index ebeac8cb9..8b35f3570 100644 --- a/cmd/trillian/createtree/main.go +++ b/cmd/trillian/createtree/main.go @@ -11,7 +11,6 @@ import ( "fmt" "time" - "github.com/golang/glog" "github.com/google/trillian" "github.com/google/trillian/client" "github.com/google/trillian/client/rpcflags" @@ -79,7 +78,7 @@ func main() { } func createTree(ctx context.Context) (*trillian.Tree, error) { - req, err := newRequest() + req, err := newRequest(ctx) if err != nil { return nil, err } @@ -101,7 +100,7 @@ func createTree(ctx context.Context) (*trillian.Tree, error) { return client.CreateAndInitTree(ctx, req, adminClient, logClient) } -func newRequest() (*trillian.CreateTreeRequest, error) { +func newRequest(ctx context.Context) (*trillian.CreateTreeRequest, error) { ts, ok := trillian.TreeState_value[*treeState] if !ok { return nil, fmt.Errorf("unknown TreeState: %v", *treeState) @@ -119,7 +118,7 @@ func newRequest() (*trillian.CreateTreeRequest, error) { Description: *description, MaxRootDuration: durationpb.New(*maxRootDuration), }} - glog.Infof("Creating tree %+v", ctr.Tree) + logging.FromContext(ctx).Infof("Creating Tree: %+v", ctr.Tree) return ctr, nil } diff --git a/config/trillian/createdb/100-namespace.yaml b/config/trillian/createdb/100-namespace.yaml deleted file mode 100644 index 5bc5ec9fa..000000000 --- a/config/trillian/createdb/100-namespace.yaml +++ /dev/null @@ -1,7 +0,0 @@ -# Copyright 2021 Chainguard, Inc. -# SPDX-License-Identifier: Apache-2.0 - -kind: Namespace -apiVersion: v1 -metadata: - name: trillian-system diff --git a/config/trillian/createdb/101-secret.yaml b/config/trillian/createdb/101-secret.yaml deleted file mode 100644 index 839629ef6..000000000 --- a/config/trillian/createdb/101-secret.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# Copyright 2021 Chainguard, Inc. -# SPDX-License-Identifier: Apache-2.0 - -apiVersion: v1 -kind: Secret -metadata: - name: trillian-client - namespace: trillian-system -type: kubernetes.io/basic-auth -stringData: - host: mysql-trillian.trillian-system.svc - name: trillian - username: trillian - password: trillian diff --git a/config/trillian/createdb/101-service-account.yaml b/config/trillian/createdb/101-service-account.yaml deleted file mode 100644 index 4febaef08..000000000 --- a/config/trillian/createdb/101-service-account.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# Copyright 2021 Chainguard, Inc. -# SPDX-License-Identifier: Apache-2.0 - -apiVersion: v1 -kind: ServiceAccount -metadata: - name: createdb - namespace: trillian-system diff --git a/config/trillian/createdb/300-createdb.yaml b/config/trillian/createdb/300-createdb.yaml deleted file mode 100644 index 6476d4b07..000000000 --- a/config/trillian/createdb/300-createdb.yaml +++ /dev/null @@ -1,41 +0,0 @@ -# Copyright 2021 Chainguard, Inc. -# SPDX-License-Identifier: Apache-2.0 - -apiVersion: batch/v1 -kind: Job -metadata: - name: createdb - namespace: trillian-system -spec: - template: - spec: - serviceAccountName: createdb - restartPolicy: Never - containers: - - name: createdb - image: ko://github.com/vaikas/sigstore-scaffolding/cmd/trillian/createdb - args: [ - "--mysql_uri=$(MYSQL_USER):$(MYSQL_PASSWORD)@tcp($(DATABASE_HOSTNAME):3306)/", - "--db_name=trillian" - ] - env: - - name: DATABASE_NAME - valueFrom: - secretKeyRef: - name: trillian-client - key: name - - name: MYSQL_USER - valueFrom: - secretKeyRef: - name: trillian-client - key: username - - name: MYSQL_PASSWORD - valueFrom: - secretKeyRef: - name: trillian-client - key: password - - name: DATABASE_HOSTNAME - valueFrom: - secretKeyRef: - name: trillian-client - key: host diff --git a/config/trillian/createdb/placeholder.go b/config/trillian/createdb/placeholder.go deleted file mode 100644 index 1367a83ac..000000000 --- a/config/trillian/createdb/placeholder.go +++ /dev/null @@ -1,6 +0,0 @@ -/* -Copyright 2021 Chainguard, Inc. -SPDX-License-Identifier: Apache-2.0 -*/ - -package createdb diff --git a/config/trillian/mysql/101-secret.yaml b/config/trillian/mysql/101-secret.yaml index b1d13dcc3..8344a8de4 100644 --- a/config/trillian/mysql/101-secret.yaml +++ b/config/trillian/mysql/101-secret.yaml @@ -8,6 +8,7 @@ metadata: namespace: trillian-system type: kubernetes.io/basic-auth stringData: + host: mysql-trillian.trillian-system.svc database-name: trillian username: trillian password: trillian diff --git a/config/trillian/mysql/300-mysql-trillian.yaml b/config/trillian/mysql/300-mysql-trillian.yaml index ad1deafb3..3c64fbc33 100644 --- a/config/trillian/mysql/300-mysql-trillian.yaml +++ b/config/trillian/mysql/300-mysql-trillian.yaml @@ -22,7 +22,7 @@ metadata: app: mysql-trillian spec: containers: - - image: mysql:8.0 + - image: gcr.io/trillian-opensource-ci/db_server@sha256:e58334fead37d1f03c77c80f66008966e79739d85214b373b3c0a69f97c59359 name: mysql env: - name: MYSQL_ROOT_PASSWORD diff --git a/config/trillian/trillian-log-server/300-log-server.yaml b/config/trillian/trillian-log-server/300-log-server.yaml index b1cffed6d..67686aa3b 100644 --- a/config/trillian/trillian-log-server/300-log-server.yaml +++ b/config/trillian/trillian-log-server/300-log-server.yaml @@ -37,22 +37,22 @@ spec: - name: DATABASE_NAME valueFrom: secretKeyRef: - name: trillian-client - key: name + name: trillian-database + key: database-name - name: MYSQL_USER valueFrom: secretKeyRef: - name: trillian-client + name: trillian-database key: username - name: MYSQL_PASSWORD valueFrom: secretKeyRef: - name: trillian-client + name: trillian-database key: password - name: DATABASE_HOSTNAME valueFrom: secretKeyRef: - name: trillian-client + name: trillian-database key: host ports: - name: h2c diff --git a/config/trillian/trillian-log-signer/300-log-signer.yaml b/config/trillian/trillian-log-signer/300-log-signer.yaml index 1dcd1140e..f77154098 100644 --- a/config/trillian/trillian-log-signer/300-log-signer.yaml +++ b/config/trillian/trillian-log-signer/300-log-signer.yaml @@ -49,24 +49,24 @@ spec: - name: DATABASE_NAME valueFrom: secretKeyRef: - name: trillian-client - key: name + name: trillian-database + key: database-name - name: MYSQL_USER valueFrom: secretKeyRef: - name: trillian-client + name: trillian-database key: username - name: MYSQL_PASSWORD valueFrom: secretKeyRef: - name: trillian-client + name: trillian-database key: password - name: DATABASE_HOSTNAME valueFrom: secretKeyRef: - name: trillian-client + name: trillian-database key: host - image: gcr.io/projectsigstore/trillian_log_signer@sha256:fe90d523f6617974f70878918e4b31d49b2b46a86024bb2d6b01d2bbfed8edbf + image: gcr.io/projectsigstore/trillian_log_signer@sha256:fe90d523f6617974f70878918e4b31d49b2b46a86024bb2d6b01d2bbfed8edbf ports: - name: h2c containerPort: 8090