Skip to content

Commit 3116f53

Browse files
authoredNov 26, 2024
Add IssuanceChainStorage PostgreSQL implementation (google#1618)
* Duplicate MySQL files to new PostgreSQL directories, preserving git line history * Restore MySQL files * Replace 'MySQL' references with 'PostgreSQL', preserving case of each reference * Update imports * Convert schema * PostgreSQL doesn't have an equivalent of MySQL's strict mode * Use PostgreSQL parameter placeholder syntax * Database driver name is 'pgx' * Adapt duplicated key checking to PostgreSQL * Remove unused parameter * DSN URI scheme can be either postgresql:// or postgres:// * Plug PostgreSQL into NewIssuanceChainStorage * Check PostgreSQL DSN in ValidateLogConfig * Document PostgreSQL storage connection string format * Add resetpgctdb.sh * Comodo -> Sectigo * Fix tests by escaping dollar signs * Add postgresql-client to integration Dockerfile * USER_HOST is not relevant to PostgreSQL * Add PostgreSQL Cloud Build configuration * Update CHANGELOG.md * Correct docker package name * Use Trillian master branch in Cloud Build tests * Update log-server and log-signer hostnames in Cloud Build tests * Run psql directly rather than via docker * Set POSTGRESQL_HOST before running resetpgctdb.sh * Add integration and lifecycle test config for PostgreSQL * Three slashes needed in the PostgreSQL URIs in the integration config * For pgx, sql.Open expects the whole DSN, not just the part after the :// * Add hostname and port to the PostgreSQL URIs in the integration config * Grant INSERT and SELECT privileges on the IssuanceChain table * Specify correct database for GRANT * ctx is unused * 'IdentityHash' values are expected to always be the same length * Remove k8s related steps * Revert "Use Trillian master branch in Cloud Build tests" This reverts commit fa3dbdb. * Document POSTGRESQL_INSECURE in usage()
1 parent 2df206d commit 3116f53

18 files changed

+653
-12
lines changed
 

‎AUTHORS

+1-1
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
# Please keep the list sorted.
1010

1111
Alex Cohn <alex@alexcohn.com>
12-
Comodo CA Limited
1312
Ed Maste <emaste@freebsd.org>
1413
Elisha Silas <silaselisha66@gmail.com>
1514
Fiaz Hossain <fiaz.hossain@salesforce.com>
@@ -24,6 +23,7 @@ Nicholas Galbreath <nickg@client9.com>
2423
Oliver Weidner <Oliver.Weidner@gmail.com>
2524
PrimeKey Solutions AB
2625
Ruslan Kovalov <ruslan.kovalyov@gmail.com>
26+
Sectigo Limited
2727
Venafi, Inc.
2828
Vladimir Rutsky <vladimir@rutsky.org>
2929
Ximin Luo <infinity0@gmx.com>

‎CHANGELOG.md

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,16 @@
22

33
## HEAD
44

5+
### CTFE Storage Saving: Extra Data Issuance Chain Deduplication
6+
7+
This feature now supports PostgreSQL, in addition to the support for MySQL/MariaDB that was added in [v1.2.0](#v1.2.0).
8+
9+
Log operators can choose to enable this feature for new PostgreSQL-based CT logs by adding new CTFE configs in the [LogMultiConfig](trillian/ctfe/configpb/config.proto) and importing the [database schema](trillian/ctfe/storage/postgresql/schema.sql). The other available options are documented in the [v1.2.0](#v1.2.0) changelog entry.
10+
11+
This change is tested in Cloud Build tests using the `postgres:17` Docker image as of the time of writing.
12+
13+
* Add IssuanceChainStorage PostgreSQL implementation by @robstradling in https://github.com/google/certificate-transparency-go/pull/1618
14+
515
## v1.2.2
616

717
* Recommended Go version for development: 1.22

‎CONTRIBUTORS

+1-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ Paul Lietar <lietar@google.com>
5252
Pavel Kalinnikov <pkalinnikov@google.com> <pavelkalinnikov@gmail.com>
5353
Pierre Phaneuf <pphaneuf@google.com>
5454
Rob Percival <robpercival@google.com>
55-
Rob Stradling <rob@comodo.com>
55+
Rob Stradling <rob@sectigo.com>
5656
Roger Ng <rogerng@google.com> <roger2hk@gmail.com>
5757
Roland Shoemaker <roland@letsencrypt.org>
5858
Ruslan Kovalov <ruslan.kovalyov@gmail.com>

‎cloudbuild_postgresql.yaml

+142-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
#############################################################################
2+
## This file is based on cloudbuild.yaml, but targets PostgreSQL instead of
3+
## MySQL.
4+
#############################################################################
5+
16
timeout: 1200s
27
options:
38
machineType: N1_HIGHCPU_32
@@ -17,5 +22,140 @@ substitutions:
1722
logsBucket: 'gs://trillian-cloudbuild-logs'
1823

1924
steps:
20-
- name: 'bash'
21-
- name: 'bash'
25+
# First build a "ct_testbase" docker image which contains most of the tools we need for the later steps:
26+
- name: 'gcr.io/cloud-builders/docker'
27+
entrypoint: 'bash'
28+
args: ['-c', 'docker pull gcr.io/$PROJECT_ID/ct_testbase:latest || exit 0']
29+
- name: 'gcr.io/cloud-builders/docker'
30+
args: [
31+
'build',
32+
'-t', 'gcr.io/$PROJECT_ID/ct_testbase:latest',
33+
'--cache-from', 'gcr.io/$PROJECT_ID/ct_testbase:latest',
34+
'-f', './integration/Dockerfile',
35+
'.'
36+
]
37+
38+
# prepare spins up an ephemeral trillian instance for testing use.
39+
- name: gcr.io/$PROJECT_ID/ct_testbase
40+
entrypoint: 'bash'
41+
id: 'prepare'
42+
args:
43+
- '-exc'
44+
- |
45+
# Use latest versions of Trillian docker images built by the Trillian CI cloudbuilders.
46+
docker pull gcr.io/$PROJECT_ID/log_server:latest
47+
docker tag gcr.io/$PROJECT_ID/log_server:latest postgresql_trillian-log-server
48+
docker pull gcr.io/$PROJECT_ID/log_signer:latest
49+
docker tag gcr.io/$PROJECT_ID/log_signer:latest postgresql_trillian-log-signer
50+
51+
# Bring up an ephemeral trillian instance using the docker-compose config in the Trillian repo:
52+
export TRILLIAN_LOCATION="$$(go list -f '{{.Dir}}' github.com/google/trillian)"
53+
54+
# We need to fix up Trillian's docker-compose to connect to the CloudBuild network to that tests can use it:
55+
echo -e "networks:\n default:\n external:\n name: cloudbuild" >> $${TRILLIAN_LOCATION}/examples/deployment/postgresql/docker-compose.yml
56+
57+
docker-compose -f $${TRILLIAN_LOCATION}/examples/deployment/postgresql/docker-compose.yml pull postgresql trillian-log-server trillian-log-signer
58+
docker-compose -f $${TRILLIAN_LOCATION}/examples/deployment/postgresql/docker-compose.yml up -d postgresql trillian-log-server trillian-log-signer
59+
60+
# Install proto related bits and block on Trillian being ready
61+
- name: gcr.io/$PROJECT_ID/ct_testbase
62+
id: 'ci-ready'
63+
entrypoint: 'bash'
64+
args:
65+
- '-ec'
66+
- |
67+
go install \
68+
github.com/golang/protobuf/proto \
69+
github.com/golang/protobuf/protoc-gen-go \
70+
github.com/golang/mock/mockgen \
71+
go.etcd.io/etcd/v3 go.etcd.io/etcd/etcdctl/v3 \
72+
github.com/fullstorydev/grpcurl/cmd/grpcurl
73+
74+
# Generate all protoc and mockgen files
75+
go generate -run="protoc" ./...
76+
go generate -run="mockgen" ./...
77+
78+
# Cache all the modules we'll need too
79+
go mod download
80+
go test ./...
81+
82+
# Wait for trillian logserver to be up
83+
until nc -z postgresql_trillian-log-server_1 8090; do echo .; sleep 5; done
84+
85+
# Reset the CT test database
86+
export CT_GO_PATH="$$(go list -f '{{.Dir}}' github.com/google/certificate-transparency-go)"
87+
export POSTGRESQL_HOST="postgresql"
88+
yes | bash "$${CT_GO_PATH}/scripts/resetpgctdb.sh" --verbose
89+
waitFor: ['prepare']
90+
91+
# Run the presubmit tests
92+
- name: gcr.io/$PROJECT_ID/ct_testbase
93+
id: 'default_test'
94+
env:
95+
- 'GOFLAGS='
96+
- 'PRESUBMIT_OPTS=--no-linters --no-generate'
97+
- 'TRILLIAN_LOG_SERVERS=postgresql_trillian-log-server_1:8090'
98+
- 'TRILLIAN_LOG_SERVER_1=postgresql_trillian-log-server_1:8090'
99+
- 'CONFIG_SUBDIR=/postgresql'
100+
waitFor: ['ci-ready']
101+
102+
- name: gcr.io/$PROJECT_ID/ct_testbase
103+
id: 'race_detection'
104+
env:
105+
- 'GOFLAGS=-race'
106+
- 'PRESUBMIT_OPTS=--no-linters --no-generate'
107+
- 'TRILLIAN_LOG_SERVERS=postgresql_trillian-log-server_1:8090'
108+
- 'TRILLIAN_LOG_SERVER_1=postgresql_trillian-log-server_1:8090'
109+
- 'CONFIG_SUBDIR=/postgresql'
110+
waitFor: ['ci-ready']
111+
112+
- name: gcr.io/$PROJECT_ID/ct_testbase
113+
id: 'etcd_with_coverage'
114+
env:
115+
- 'GOFLAGS='
116+
- 'PRESUBMIT_OPTS=--no-linters --no-generate --coverage'
117+
- 'WITH_ETCD=true'
118+
- 'TRILLIAN_LOG_SERVERS=postgresql_trillian-log-server_1:8090'
119+
- 'TRILLIAN_LOG_SERVER_1=postgresql_trillian-log-server_1:8090'
120+
- 'CONFIG_SUBDIR=/postgresql'
121+
waitFor: ['ci-ready']
122+
123+
- name: gcr.io/$PROJECT_ID/ct_testbase
124+
id: 'etcd_with_race'
125+
env:
126+
- 'GOFLAGS=-race'
127+
- 'PRESUBMIT_OPTS=--no-linters --no-generate'
128+
- 'WITH_ETCD=true'
129+
- 'TRILLIAN_LOG_SERVERS=postgresql_trillian-log-server_1:8090'
130+
- 'TRILLIAN_LOG_SERVER_1=postgresql_trillian-log-server_1:8090'
131+
- 'CONFIG_SUBDIR=/postgresql'
132+
waitFor: ['ci-ready']
133+
134+
- name: gcr.io/$PROJECT_ID/ct_testbase
135+
id: 'with_pkcs11_and_race'
136+
env:
137+
- 'GOFLAGS=-race --tags=pkcs11'
138+
- 'PRESUBMIT_OPTS=--no-linters --no-generate'
139+
- 'WITH_PKCS11=true'
140+
- 'TRILLIAN_LOG_SERVERS=postgresql_trillian-log-server_1:8090'
141+
- 'TRILLIAN_LOG_SERVER_1=postgresql_trillian-log-server_1:8090'
142+
- 'CONFIG_SUBDIR=/postgresql'
143+
waitFor: ['ci-ready']
144+
145+
# Collect and submit codecoverage reports
146+
- name: 'gcr.io/cloud-builders/curl'
147+
id: 'codecov.io'
148+
entrypoint: bash
149+
args: ['-c', 'bash <(curl -s https://codecov.io/bash)']
150+
env:
151+
- 'VCS_COMMIT_ID=$COMMIT_SHA'
152+
- 'VCS_BRANCH_NAME=$BRANCH_NAME'
153+
- 'VCS_PULL_REQUEST=$_PR_NUMBER'
154+
- 'CI_BUILD_ID=$BUILD_ID'
155+
- 'CODECOV_TOKEN=$_CODECOV_TOKEN' # _CODECOV_TOKEN is specified in the cloud build trigger
156+
waitFor: ['etcd_with_coverage']
157+
158+
- name: gcr.io/$PROJECT_ID/ct_testbase
159+
id: 'ci_complete'
160+
entrypoint: /bin/true
161+
waitFor: ['codecov.io', 'default_test', 'race_detection', 'etcd_with_coverage', 'etcd_with_race', 'with_pkcs11_and_race']

‎go.mod

+5
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ require (
1111
github.com/google/trillian v1.7.0
1212
github.com/gorilla/mux v1.8.1
1313
github.com/hashicorp/golang-lru/v2 v2.0.7
14+
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438
15+
github.com/jackc/pgx/v5 v5.7.1
1416
github.com/kylelemons/godebug v1.1.0
1517
github.com/mattn/go-sqlite3 v1.14.24
1618
github.com/prometheus/client_golang v1.20.5
@@ -74,6 +76,9 @@ require (
7476
github.com/grpc-ecosystem/grpc-gateway v1.16.0 // indirect
7577
github.com/grpc-ecosystem/grpc-gateway/v2 v2.19.1 // indirect
7678
github.com/inconshreveable/mousetrap v1.1.0 // indirect
79+
github.com/jackc/pgpassfile v1.0.0 // indirect
80+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
81+
github.com/jackc/puddle/v2 v2.2.2 // indirect
7782
github.com/jhump/protoreflect v1.16.0 // indirect
7883
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect
7984
github.com/jonboulle/clockwork v0.4.0 // indirect

‎go.sum

+10
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,16 @@ github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs
147147
github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM=
148148
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
149149
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
150+
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438 h1:Dj0L5fhJ9F82ZJyVOmBx6msDp/kfd1t9GRfny/mfJA0=
151+
github.com/jackc/pgerrcode v0.0.0-20240316143900-6e2875d9b438/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds=
152+
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
153+
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
154+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
155+
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
156+
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
157+
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
158+
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
159+
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
150160
github.com/jhump/protoreflect v1.16.0 h1:54fZg+49widqXYQ0b+usAFHbMkBGR4PpXrsHc8+TBDg=
151161
github.com/jhump/protoreflect v1.16.0/go.mod h1:oYPd7nPvcBw/5wlDfm/AVmU9zH9BgqGCI469pGxfj/8=
152162
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 h1:liMMTbpW34dhU4az1GN0pTPADwNmvoRSeoZ6PItiqnY=

‎integration/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ WORKDIR /testbase
77
ARG GOFLAGS=""
88
ENV GOFLAGS=$GOFLAGS
99

10-
RUN apt-get update && apt-get -y install build-essential curl docker-compose lsof mariadb-client netcat-openbsd unzip wget xxd
10+
RUN apt-get update && apt-get -y install build-essential curl docker-compose lsof mariadb-client netcat-openbsd postgresql-client unzip wget xxd
1111

1212
RUN cd /usr/bin && curl -L -O https://github.com/jqlang/jq/releases/download/jq-1.7/jq-linux64 && mv jq-linux64 /usr/bin/jq && chmod +x /usr/bin/jq
1313
RUN curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.55.1

‎scripts/resetpgctdb.sh

+115
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
usage() {
6+
cat <<EOF
7+
$(basename $0) [--force] [--verbose] ...
8+
All unrecognised arguments will be passed through to the 'psql' command.
9+
Accepts environment variables:
10+
- POSTGRESQL_ROOT_USER: A user with sufficient rights to create/reset the CT
11+
database (default: root).
12+
- POSTGRESQL_ROOT_PASSWORD: The password for \$POSTGRESQL_ROOT_USER (default: none).
13+
- POSTGRESQL_HOST: The hostname of the PostgreSQL server (default: localhost).
14+
- POSTGRESQL_PORT: The port the PostgreSQL server is listening on (default: 5432).
15+
- POSTGRESQL_DATABASE: The name to give to the new CT user and database
16+
(default: defaultctdb).
17+
- POSTGRESQL_USER: The name to give to the new CT user (default: cttest).
18+
- POSTGRESQL_PASSWORD: The password to use for the new CT user
19+
(default: beeblebrox).
20+
- POSTGRESQL_INSECURE: If set, the script will not set a password for the new CT
21+
user (default: true).
22+
- POSTGRESQL_IN_CONTAINER: If set, the script will assume it is running in a Docker
23+
container and will exec into the container to operate (default: false).
24+
- POSTGRESQL_CONTAINER_NAME: The name of the Docker container to exec into (default:
25+
pgsql).
26+
EOF
27+
}
28+
29+
die() {
30+
echo "$*" > /dev/stderr
31+
exit 1
32+
}
33+
34+
collect_vars() {
35+
# set unset environment variables to defaults
36+
[ -z ${POSTGRESQL_ROOT_USER+x} ] && POSTGRESQL_ROOT_USER="postgres"
37+
[ -z ${POSTGRESQL_HOST+x} ] && POSTGRESQL_HOST="localhost"
38+
[ -z ${POSTGRESQL_PORT+x} ] && POSTGRESQL_PORT="5432"
39+
[ -z ${POSTGRESQL_DATABASE+x} ] && POSTGRESQL_DATABASE="defaultctdb"
40+
[ -z ${POSTGRESQL_USER+x} ] && POSTGRESQL_USER="cttest"
41+
[ -z ${POSTGRESQL_PASSWORD+x} ] && POSTGRESQL_PASSWORD="beeblebrox"
42+
[ -z ${POSTGRESQL_INSECURE+x} ] && POSTGRESQL_INSECURE="true"
43+
[ -z ${POSTGRESQL_IN_CONTAINER+x} ] && POSTGRESQL_IN_CONTAINER="false"
44+
[ -z ${POSTGRESQL_CONTAINER_NAME+x} ] && POSTGRESQL_CONTAINER_NAME="pgsql"
45+
FLAGS=()
46+
47+
# handle flags
48+
FORCE=false
49+
VERBOSE=false
50+
while [[ $# -gt 0 ]]; do
51+
case "$1" in
52+
--force) FORCE=true ;;
53+
--verbose) VERBOSE=true ;;
54+
--help) usage; exit ;;
55+
*) FLAGS+=("$1")
56+
esac
57+
shift 1
58+
done
59+
60+
FLAGS+=(-U "${POSTGRESQL_ROOT_USER}")
61+
FLAGS+=(--host "${POSTGRESQL_HOST}")
62+
FLAGS+=(--port "${POSTGRESQL_PORT}")
63+
64+
# Useful for debugging
65+
FLAGS+=(--echo-all)
66+
67+
# Optionally print flags (before appending password)
68+
[[ ${VERBOSE} = 'true' ]] && echo "- Using PostgreSQL Flags: ${FLAGS[@]}"
69+
70+
# append password if supplied
71+
[ -z ${POSTGRESQL_ROOT_PASSWORD+x} ] || FLAGS+=(-p"${POSTGRESQL_ROOT_PASSWORD}")
72+
73+
if [[ ${POSTGRESQL_IN_CONTAINER} = 'true' ]]; then
74+
CMD="docker exec -i ${POSTGRESQL_CONTAINER_NAME} psql"
75+
else
76+
CMD="psql"
77+
fi
78+
}
79+
80+
main() {
81+
collect_vars "$@"
82+
83+
readonly CT_GO_PATH=$(go list -f '{{.Dir}}' github.com/google/certificate-transparency-go)
84+
85+
echo "Warning: about to destroy and reset database '${POSTGRESQL_DATABASE}'"
86+
87+
[[ ${FORCE} = true ]] || read -p "Are you sure? [Y/N]: " -n 1 -r
88+
echo # Print newline following the above prompt
89+
90+
if [ -z ${REPLY+x} ] || [[ $REPLY =~ ^[Yy]$ ]]
91+
then
92+
echo "Resetting DB..."
93+
set -eux
94+
$CMD "${FLAGS[@]}" -c "DROP DATABASE IF EXISTS ${POSTGRESQL_DATABASE};" || \
95+
die "Error: Failed to drop database '${POSTGRESQL_DATABASE}'."
96+
$CMD "${FLAGS[@]}" -c "CREATE DATABASE ${POSTGRESQL_DATABASE};" || \
97+
die "Error: Failed to create database '${POSTGRESQL_DATABASE}'."
98+
if [[ ${POSTGRESQL_INSECURE} = 'true' ]]; then
99+
$CMD "${FLAGS[@]}" -c "CREATE USER ${POSTGRESQL_USER};" || \
100+
die "Error: Failed to create user '${POSTGRESQL_USER}'."
101+
else
102+
$CMD "${FLAGS[@]}" -c "CREATE USER ${POSTGRESQL_USER} WITH PASSWORD '${POSTGRESQL_PASSWORD}';" || \
103+
die "Error: Failed to create user '${POSTGRESQL_USER}'."
104+
fi
105+
$CMD "${FLAGS[@]}" -c "GRANT ALL PRIVILEGES ON DATABASE ${POSTGRESQL_DATABASE} TO ${POSTGRESQL_USER} WITH GRANT OPTION;" || \
106+
die "Error: Failed to grant '${POSTGRESQL_USER}' user all privileges on '${POSTGRESQL_DATABASE}'."
107+
$CMD "${FLAGS[@]}" -d ${POSTGRESQL_DATABASE} < ${CT_GO_PATH}/trillian/ctfe/storage/postgresql/schema.sql || \
108+
die "Error: Failed to create tables in '${POSTGRESQL_DATABASE}' database."
109+
$CMD "${FLAGS[@]}" -d ${POSTGRESQL_DATABASE} -c "GRANT INSERT, SELECT ON IssuanceChain TO ${POSTGRESQL_USER};" || \
110+
die "Error: Failed to grant '${POSTGRESQL_USER}' INSERT and SELECT privileges on IssuanceChain in '${POSTGRESQL_DATABASE}' database."
111+
echo "Reset Complete"
112+
fi
113+
}
114+
115+
main "$@"

‎trillian/ctfe/config.go

+10-4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import (
2626
ct "github.com/google/certificate-transparency-go"
2727
"github.com/google/certificate-transparency-go/trillian/ctfe/configpb"
2828
"github.com/google/certificate-transparency-go/x509"
29+
"github.com/jackc/pgx/v5/pgconn"
2930
"google.golang.org/protobuf/encoding/prototext"
3031
"google.golang.org/protobuf/proto"
3132
"k8s.io/klog/v2"
@@ -221,12 +222,17 @@ func ValidateLogConfig(cfg *configpb.LogConfig) (*ValidatedLogConfig, error) {
221222
return nil, errors.New("missing ctfe_storage_connection_string when issuance chain storage backend is CTFE")
222223
}
223224
// Validate CTFEStorageConnectionString
224-
if !strings.HasPrefix(cfg.CtfeStorageConnectionString, "mysql") {
225+
if strings.HasPrefix(cfg.CtfeStorageConnectionString, "mysql") {
226+
if _, err := mysql.ParseDSN(strings.Split(cfg.CtfeStorageConnectionString, "://")[1]); err != nil {
227+
return nil, errors.New("failed to parse ctfe_storage_connection_string for mysql driver")
228+
}
229+
} else if strings.HasPrefix(cfg.CtfeStorageConnectionString, "postgres") {
230+
if _, err := pgconn.ParseConfig(cfg.CtfeStorageConnectionString); err != nil {
231+
return nil, errors.New("failed to parse ctfe_storage_connection_string for postgresql pgx driver")
232+
}
233+
} else {
225234
return nil, errors.New("unsupported driver in ctfe_storage_connection_string")
226235
}
227-
if _, err := mysql.ParseDSN(strings.Split(cfg.CtfeStorageConnectionString, "://")[1]); err != nil {
228-
return nil, errors.New("failed to parse ctfe_storage_connection_string for mysql driver")
229-
}
230236
vCfg.CTFEStorageConnectionString = cfg.CtfeStorageConnectionString
231237
case configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC:
232238
// Nothing to validate for Trillian gRPC

‎trillian/ctfe/configpb/config.pb.go

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎trillian/ctfe/configpb/config.proto

+3
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ message LogConfig {
139139
// MySQL/MariaDB:
140140
// mysql://[username[:password]@][protocol[(address)]]/dbname[?param1=value1&...&paramN=valueN]
141141
//
142+
// PostgreSQL:
143+
// postgresql://[username[:password]@][host][:port][/dbname][?param1=value1&...&paramN=valueN]
144+
//
142145
// This is required when the issuance chain storage backend is CTFE.
143146
//
144147
// Warning: CT log operators are advised not to re-use the same connection
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright 2024 Google LLC
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 postgresql defines the IssuanceChainStorage type, which implements IssuanceChainStorage interface with FindByKey and Add methods.
16+
package postgresql
17+
18+
import (
19+
"context"
20+
"database/sql"
21+
"errors"
22+
"fmt"
23+
"strings"
24+
25+
"github.com/jackc/pgerrcode"
26+
"github.com/jackc/pgx/v5/pgconn"
27+
_ "github.com/jackc/pgx/v5/stdlib"
28+
29+
"k8s.io/klog/v2"
30+
)
31+
32+
const (
33+
selectIssuanceChainByKeySQL = "SELECT c.ChainValue FROM IssuanceChain AS c WHERE c.IdentityHash = $1"
34+
insertIssuanceChainSQL = "INSERT INTO IssuanceChain(IdentityHash, ChainValue) VALUES ($1, $2)"
35+
)
36+
37+
// IssuanceChainStorage is a PostgreSQL implementation of the IssuanceChainStorage interface.
38+
type IssuanceChainStorage struct {
39+
db *sql.DB
40+
}
41+
42+
// NewIssuanceChainStorage takes the database connection string as the input and return the IssuanceChainStorage.
43+
func NewIssuanceChainStorage(_ context.Context, dbConn string) *IssuanceChainStorage {
44+
db, err := open(dbConn)
45+
if err != nil {
46+
klog.Exitf(fmt.Sprintf("failed to open database: %v", err))
47+
}
48+
49+
return &IssuanceChainStorage{
50+
db: db,
51+
}
52+
}
53+
54+
// FindByKey returns the key-value pair of issuance chain by the key.
55+
func (s *IssuanceChainStorage) FindByKey(ctx context.Context, key []byte) ([]byte, error) {
56+
row := s.db.QueryRowContext(ctx, selectIssuanceChainByKeySQL, key)
57+
if err := row.Err(); err != nil {
58+
return nil, err
59+
}
60+
61+
var chain []byte
62+
if err := row.Scan(&chain); err != nil {
63+
return nil, err
64+
}
65+
66+
return chain, nil
67+
}
68+
69+
// Add inserts the key-value pair of issuance chain.
70+
func (s *IssuanceChainStorage) Add(ctx context.Context, key []byte, chain []byte) error {
71+
_, err := s.db.ExecContext(ctx, insertIssuanceChainSQL, key, chain)
72+
if err != nil {
73+
// Ignore duplicated key error.
74+
var postgresqlErr *pgconn.PgError
75+
if errors.As(err, &postgresqlErr) && postgresqlErr.Code == pgerrcode.UniqueViolation {
76+
return nil
77+
}
78+
return err
79+
}
80+
81+
return nil
82+
}
83+
84+
// open takes the data source name and returns the sql.DB object.
85+
func open(dataSourceName string) (*sql.DB, error) {
86+
// Verify data source name format.
87+
conn := strings.Split(dataSourceName, "://")
88+
if len(conn) != 2 {
89+
return nil, errors.New("could not parse PostgreSQL data source name")
90+
}
91+
if conn[0] != "postgresql" && conn[0] != "postgres" {
92+
return nil, errors.New("expect data source name to start with postgresql or postgres")
93+
}
94+
95+
db, err := sql.Open("pgx", dataSourceName)
96+
if err != nil {
97+
// Don't log data source name as it could contain credentials.
98+
klog.Errorf("could not open PostgreSQL database, check config: %s", err)
99+
return nil, err
100+
}
101+
102+
return db, nil
103+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// Copyright 2024 Google LLC
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 postgresql
16+
17+
import (
18+
"bytes"
19+
"context"
20+
"crypto/sha256"
21+
"database/sql"
22+
"os"
23+
"strings"
24+
"testing"
25+
26+
"github.com/DATA-DOG/go-sqlmock"
27+
)
28+
29+
func TestIssuanceChainFindByKeySuccess(t *testing.T) {
30+
db, mock, err := sqlmock.New()
31+
if err != nil {
32+
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
33+
}
34+
defer func() {
35+
mock.ExpectClose()
36+
if err := db.Close(); err != nil {
37+
t.Error(err)
38+
}
39+
}()
40+
41+
testVal := readTestData(t, "leaf00.chain")
42+
testKey := sha256.Sum256(testVal)
43+
44+
issuanceChainMockRows := sqlmock.NewRows([]string{"ChainValue"}).AddRow(testVal)
45+
mock.ExpectQuery(strings.ReplaceAll(selectIssuanceChainByKeySQL, "$", "\\$")).WillReturnRows(issuanceChainMockRows)
46+
47+
storage := mockIssuanceChainStorage(db)
48+
got, err := storage.FindByKey(context.Background(), testKey[:])
49+
if err != nil {
50+
t.Errorf("issuanceChainStorage.FindByKey: %v", err)
51+
}
52+
if !bytes.Equal(got, testVal) {
53+
t.Errorf("got: %v, want: %v", got, testVal)
54+
}
55+
56+
if err := mock.ExpectationsWereMet(); err != nil {
57+
t.Errorf("there were unfulfilled expectations: %s", err)
58+
}
59+
}
60+
61+
func TestIssuanceChainAddSuccess(t *testing.T) {
62+
db, mock, err := sqlmock.New()
63+
if err != nil {
64+
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
65+
}
66+
defer func() {
67+
mock.ExpectClose()
68+
if err := db.Close(); err != nil {
69+
t.Error(err)
70+
}
71+
}()
72+
73+
tests := setupTestData(t,
74+
"leaf00.chain",
75+
"leaf01.chain",
76+
"leaf02.chain",
77+
)
78+
79+
storage := mockIssuanceChainStorage(db)
80+
for k, v := range tests {
81+
mock.ExpectExec("INSERT INTO IssuanceChain").WithArgs([]byte(k), v).WillReturnResult(sqlmock.NewResult(1, 1))
82+
if err := storage.Add(context.Background(), []byte(k), v); err != nil {
83+
t.Errorf("issuanceChainStorage.Add: %v", err)
84+
}
85+
}
86+
87+
if err := mock.ExpectationsWereMet(); err != nil {
88+
t.Errorf("there were unfulfilled expectations: %s", err)
89+
}
90+
}
91+
92+
func readTestData(t *testing.T, filename string) []byte {
93+
t.Helper()
94+
95+
data, err := os.ReadFile("../../../testdata/" + filename)
96+
if err != nil {
97+
t.Fatal(err)
98+
}
99+
100+
return data
101+
}
102+
103+
func setupTestData(t *testing.T, filenames ...string) map[string][]byte {
104+
t.Helper()
105+
106+
data := make(map[string][]byte, len(filenames))
107+
108+
for _, filename := range filenames {
109+
val := readTestData(t, filename)
110+
key := sha256.Sum256(val)
111+
data[string(key[:])] = val
112+
}
113+
114+
return data
115+
}
116+
117+
func mockIssuanceChainStorage(db *sql.DB) *IssuanceChainStorage {
118+
return &IssuanceChainStorage{
119+
db: db,
120+
}
121+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
-- Copyright 2024 Google LLC
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+
-- PostgreSQL version of the CTFE database schema
16+
17+
-- "IssuanceChain" table contains the hash and value pairs of the issuance chain.
18+
CREATE TABLE IF NOT EXISTS IssuanceChain (
19+
-- Fixed-length hash of the chain of intermediate certificates and root certificates.
20+
IdentityHash bytea NOT NULL,
21+
-- Chain data of intermediate certificates and root certificates.
22+
ChainValue bytea NOT NULL,
23+
PRIMARY KEY (IdentityHash)
24+
);

‎trillian/ctfe/storage/storage.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222

2323
"github.com/google/certificate-transparency-go/trillian/ctfe/configpb"
2424
"github.com/google/certificate-transparency-go/trillian/ctfe/storage/mysql"
25+
"github.com/google/certificate-transparency-go/trillian/ctfe/storage/postgresql"
2526
)
2627

2728
// IssuanceChainStorage is an interface which allows CTFE binaries to use different storage implementations for issuance chains.
@@ -33,14 +34,17 @@ type IssuanceChainStorage interface {
3334
Add(ctx context.Context, key []byte, chain []byte) error
3435
}
3536

36-
// NewIssuanceChainStorage returns nil for Trillian gRPC or mysql.IssuanceChainStorage when MySQL is the prefix in database connection string.
37+
// NewIssuanceChainStorage returns nil for Trillian gRPC, or mysql.IssuanceChainStorage or postgresql.IssuanceChainStorage
38+
// when mysql or postgres is the prefix in database connection string.
3739
func NewIssuanceChainStorage(ctx context.Context, backend configpb.LogConfig_IssuanceChainStorageBackend, dbConn string) (IssuanceChainStorage, error) {
3840
switch backend {
3941
case configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC:
4042
return nil, nil
4143
case configpb.LogConfig_ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE:
4244
if strings.HasPrefix(dbConn, "mysql") {
4345
return mysql.NewIssuanceChainStorage(ctx, dbConn), nil
46+
} else if strings.HasPrefix(dbConn, "postgres") {
47+
return postgresql.NewIssuanceChainStorage(ctx, dbConn), nil
4448
}
4549

4650
return nil, errors.New("failed to initialise IssuanceChainService due to unsupported driver in CTFE storage connection string")

‎trillian/integration/ct_functions.sh

+2-2
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,10 @@ ct_provision() {
8282

8383
# Build config files with absolute paths
8484
CT_CFG=$(mktemp ${TMPDIR}/ct-XXXXXX)
85-
sed "s!@TESTDATA@!${CT_GO_PATH}/trillian/testdata!" ${CT_GO_PATH}/trillian/integration/ct_integration_test.cfg > "${CT_CFG}"
85+
sed "s!@TESTDATA@!${CT_GO_PATH}/trillian/testdata!" ${CT_GO_PATH}/trillian/integration${CONFIG_SUBDIR}/ct_integration_test.cfg > "${CT_CFG}"
8686

8787
CT_LIFECYCLE_CFG=$(mktemp ${TMPDIR}/ct-XXXXXX)
88-
sed "s!@TESTDATA@!${CT_GO_PATH}/trillian/testdata!" ${CT_GO_PATH}/trillian/integration/ct_lifecycle_test.cfg > "${CT_LIFECYCLE_CFG}"
88+
sed "s!@TESTDATA@!${CT_GO_PATH}/trillian/testdata!" ${CT_GO_PATH}/trillian/integration${CONFIG_SUBDIR}/ct_lifecycle_test.cfg > "${CT_LIFECYCLE_CFG}"
8989

9090
echo 'Building createtree'
9191
go build github.com/google/trillian/cmd/createtree/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
config {
2+
log_id: @TREE_ID@
3+
prefix: "athos"
4+
roots_pem_file: "@TESTDATA@/fake-ca.cert"
5+
roots_pem_file: "@TESTDATA@/../../testdata/gossip-root.cert"
6+
public_key: {
7+
der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x2d\x6c\xdc\x30\xf8\x03\x5e\x7f\x0f\x90\x69\xd3\xdf\xcd\xd3\xd3\x82\x45\x7b\x0e\xa2\xcb\xa9\x48\x4c\x97\xad\x3c\xc0\x88\x6f\xdb\xc2\x95\x28\xb6\x62\xa0\x2f\x81\x89\x32\x6e\xc7\xd4\x88\xc1\xf3\xd0\x5c\x54\x64\x74\xdc\x26\xb1\xcf\x74\xc5\x25\xa6\xa1\xeb\x0f"
8+
}
9+
private_key: {
10+
[type.googleapis.com/keyspb.PrivateKey] {
11+
der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\xc4\x2d\x99\xc7\x9e\x31\x77\x99\xd7\xda\x4c\xab\xdb\xb9\x37\xeb\x95\xde\x6a\x72\x1b\x84\xbd\x0b\xfe\xb3\x4b\x1e\xce\xa8\xbb\x2f\xa1\x44\x03\x42\x00\x04\x2d\x6c\xdc\x30\xf8\x03\x5e\x7f\x0f\x90\x69\xd3\xdf\xcd\xd3\xd3\x82\x45\x7b\x0e\xa2\xcb\xa9\x48\x4c\x97\xad\x3c\xc0\x88\x6f\xdb\xc2\x95\x28\xb6\x62\xa0\x2f\x81\x89\x32\x6e\xc7\xd4\x88\xc1\xf3\xd0\x5c\x54\x64\x74\xdc\x26\xb1\xcf\x74\xc5\x25\xa6\xa1\xeb\x0f"
12+
}
13+
}
14+
max_merge_delay_sec: 86400
15+
expected_merge_delay_sec: 120 # Note: 120s is unrealistically fast for a real log, but OK for testing.
16+
}
17+
config {
18+
log_id: @TREE_ID@
19+
prefix: "porthos"
20+
roots_pem_file: "@TESTDATA@/fake-ca.cert"
21+
public_key: {
22+
der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x44\x6d\x69\x2c\x00\xec\xf3\xc7\xbb\x87\x7e\x57\xea\x04\xc3\x4b\x49\x01\xc4\x9a\x19\xf2\x49\x9b\x4c\x44\x1c\xac\xe0\xff\x27\x11\xce\x94\xa8\x85\xd9\xed\x42\x22\x5c\x54\xf6\x33\x73\xa3\x3d\x8b\xe8\x53\x48\xf5\x57\x50\x61\x96\x30\x5b\xc4\x9b\xa3\x04\xc3\x4b"
23+
}
24+
private_key: {
25+
[type.googleapis.com/keyspb.PrivateKey] {
26+
der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\xd8\x8a\x49\xa2\x15\x3c\xbe\xb5\xb7\x6c\x63\xdc\xfd\xc0\x36\x64\x24\x88\xc3\x57\x9d\xfa\xd4\xa8\x70\x78\x32\x72\x29\x1a\xb1\x6f\xa1\x44\x03\x42\x00\x04\x44\x6d\x69\x2c\x00\xec\xf3\xc7\xbb\x87\x7e\x57\xea\x04\xc3\x4b\x49\x01\xc4\x9a\x19\xf2\x49\x9b\x4c\x44\x1c\xac\xe0\xff\x27\x11\xce\x94\xa8\x85\xd9\xed\x42\x22\x5c\x54\xf6\x33\x73\xa3\x3d\x8b\xe8\x53\x48\xf5\x57\x50\x61\x96\x30\x5b\xc4\x9b\xa3\x04\xc3\x4b"
27+
}
28+
}
29+
max_merge_delay_sec: 86400
30+
expected_merge_delay_sec: 120
31+
extra_data_issuance_chain_storage_backend: ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC
32+
}
33+
config {
34+
log_id: @TREE_ID@
35+
prefix: "aramis"
36+
roots_pem_file: "@TESTDATA@/fake-ca.cert"
37+
public_key: {
38+
der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\xd6\xaf\x18\x80\x8c\x66\xc2\xcc\xb3\xb8\xd1\x84\x2a\xa7\xd3\x62\xae\x4f\xe3\xa5\x94\x41\x3d\x64\x65\x1c\x86\x63\x57\xc2\x06\x85\x1e\xa6\x3d\xa1\x27\x63\xc6\xcd\xe5\x9f\x41\xd6\x98\x87\x56\x19\x16\x15\x6c\xf8\x15\x35\x53\x1b\x7f\x39\x9a\x99\x38\x50\xba\x7e"
39+
}
40+
private_key: {
41+
[type.googleapis.com/keyspb.PrivateKey] {
42+
der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\x97\x94\x1f\x33\xa7\x36\xac\x0b\xcb\x11\x09\x23\x8a\xfb\x73\xc1\x17\xc5\xc5\x23\x5d\xdb\xa8\x8f\x32\x94\xc5\xdd\x67\x4b\xff\x5e\xa1\x44\x03\x42\x00\x04\xd6\xaf\x18\x80\x8c\x66\xc2\xcc\xb3\xb8\xd1\x84\x2a\xa7\xd3\x62\xae\x4f\xe3\xa5\x94\x41\x3d\x64\x65\x1c\x86\x63\x57\xc2\x06\x85\x1e\xa6\x3d\xa1\x27\x63\xc6\xcd\xe5\x9f\x41\xd6\x98\x87\x56\x19\x16\x15\x6c\xf8\x15\x35\x53\x1b\x7f\x39\x9a\x99\x38\x50\xba\x7e"
43+
}
44+
}
45+
max_merge_delay_sec: 86400
46+
expected_merge_delay_sec: 120
47+
ctfe_storage_connection_string: "postgresql://postgresql:5432/defaultctdb?user=cttest&password=beeblebrox"
48+
extra_data_issuance_chain_storage_backend: ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
config {
2+
log_id: @TREE_ID@
3+
prefix: "alpha"
4+
roots_pem_file: "@TESTDATA@/fake-ca.cert"
5+
public_key: {
6+
der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x78\xf4\xe5\xd4\x49\x4e\xf9\xe1\x7e\x28\x5e\x88\xf5\x58\x2d\x6c\xf0\x92\xaf\xd7\xb4\x22\x75\x7b\xc6\xb4\x15\x17\xeb\x59\xad\xd4\x7e\x91\x8c\x92\xbb\x07\xa1\xba\x25\x69\xc7\x38\x04\x9f\x00\x4f\x26\xad\xc8\x54\x3a\x35\x1a\xfe\x67\xf9\x8a\xba\x2a\xdb\x77\x15"
7+
}
8+
private_key: {
9+
[type.googleapis.com/keyspb.PrivateKey] {
10+
der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\x6f\x67\x62\x64\x1e\x9e\x4d\xe7\x91\xbe\x2d\xd6\x0c\x9e\xb2\x6d\xc3\x46\xc0\x23\x5b\x4b\x77\x6e\x6e\xa3\xac\x70\x01\xf2\x71\xd2\xa1\x44\x03\x42\x00\x04\x78\xf4\xe5\xd4\x49\x4e\xf9\xe1\x7e\x28\x5e\x88\xf5\x58\x2d\x6c\xf0\x92\xaf\xd7\xb4\x22\x75\x7b\xc6\xb4\x15\x17\xeb\x59\xad\xd4\x7e\x91\x8c\x92\xbb\x07\xa1\xba\x25\x69\xc7\x38\x04\x9f\x00\x4f\x26\xad\xc8\x54\x3a\x35\x1a\xfe\x67\xf9\x8a\xba\x2a\xdb\x77\x15"
11+
}
12+
}
13+
max_merge_delay_sec: 86400
14+
expected_merge_delay_sec: 120 # Note: 120s is unrealistically fast for a real log, but OK for testing.
15+
}
16+
config {
17+
log_id: @TREE_ID@
18+
prefix: "beta"
19+
roots_pem_file: "@TESTDATA@/fake-ca.cert"
20+
public_key: {
21+
der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x75\x79\x7c\x29\x9e\xbb\x39\x5b\x35\x24\x53\xd9\xfb\x58\x5d\x7f\x55\x02\x29\x7b\x3c\x9e\x7c\x72\x51\xfc\xc4\xe4\x01\x22\x00\xd3\xbc\xa9\x5a\xff\x06\x99\x5e\x55\xc8\xa9\xf9\xf2\x13\x9c\x80\xc3\xf1\x26\x1f\xe9\x55\x53\x2d\x46\xbb\x2f\x10\x85\xf9\x17\xe2\xe8"
22+
}
23+
private_key: {
24+
[type.googleapis.com/keyspb.PrivateKey] {
25+
der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\x6b\x0d\xda\x1d\x9f\x23\x43\x94\xea\xa8\xce\x8e\x3b\x05\x71\x6c\xf1\xff\xd5\x0a\x14\xb4\xad\x9a\x9c\x9c\x0a\x64\x29\xb6\xa1\x1d\xa1\x44\x03\x42\x00\x04\x75\x79\x7c\x29\x9e\xbb\x39\x5b\x35\x24\x53\xd9\xfb\x58\x5d\x7f\x55\x02\x29\x7b\x3c\x9e\x7c\x72\x51\xfc\xc4\xe4\x01\x22\x00\xd3\xbc\xa9\x5a\xff\x06\x99\x5e\x55\xc8\xa9\xf9\xf2\x13\x9c\x80\xc3\xf1\x26\x1f\xe9\x55\x53\x2d\x46\xbb\x2f\x10\x85\xf9\x17\xe2\xe8"
26+
}
27+
}
28+
max_merge_delay_sec: 86400
29+
expected_merge_delay_sec: 120
30+
extra_data_issuance_chain_storage_backend: ISSUANCE_CHAIN_STORAGE_BACKEND_TRILLIAN_GRPC
31+
}
32+
config {
33+
log_id: @TREE_ID@
34+
prefix: "gamma"
35+
roots_pem_file: "@TESTDATA@/fake-ca.cert"
36+
public_key: {
37+
der: "\x30\x59\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x03\x42\x00\x04\x55\x32\x88\x34\xe9\x87\x81\x16\x6f\x41\xb3\xd5\x9d\x64\xae\x6c\x24\xbc\x9c\x6a\x21\x41\x0b\xb8\xd6\x0a\xf7\x8f\xc0\x7a\x0a\xc4\x10\xcf\x88\x0e\xa6\x78\xfd\xba\xde\x4f\x1f\x2b\xc7\x06\xec\x71\xed\x77\x34\xb1\xc7\x7d\xe5\x43\xd3\xdc\x15\x6f\x69\x7b\xf0\x56"
38+
}
39+
private_key: {
40+
[type.googleapis.com/keyspb.PrivateKey] {
41+
der: "\x30\x81\x87\x02\x01\x00\x30\x13\x06\x07\x2a\x86\x48\xce\x3d\x02\x01\x06\x08\x2a\x86\x48\xce\x3d\x03\x01\x07\x04\x6d\x30\x6b\x02\x01\x01\x04\x20\xff\x81\x10\xd0\xb3\x06\x48\xf6\x75\x68\x77\x16\x95\xdd\x34\x80\x4c\x3e\x0f\x60\xc9\x2c\x5a\xf4\xe4\xcf\x07\xc7\x06\x68\xb3\x73\xa1\x44\x03\x42\x00\x04\x55\x32\x88\x34\xe9\x87\x81\x16\x6f\x41\xb3\xd5\x9d\x64\xae\x6c\x24\xbc\x9c\x6a\x21\x41\x0b\xb8\xd6\x0a\xf7\x8f\xc0\x7a\x0a\xc4\x10\xcf\x88\x0e\xa6\x78\xfd\xba\xde\x4f\x1f\x2b\xc7\x06\xec\x71\xed\x77\x34\xb1\xc7\x7d\xe5\x43\xd3\xdc\x15\x6f\x69\x7b\xf0\x56"
42+
}
43+
}
44+
max_merge_delay_sec: 86400
45+
expected_merge_delay_sec: 120
46+
ctfe_storage_connection_string: "postgresql://postgresql:5432/defaultctdb?user=cttest&password=beeblebrox"
47+
extra_data_issuance_chain_storage_backend: ISSUANCE_CHAIN_STORAGE_BACKEND_CTFE
48+
}

0 commit comments

Comments
 (0)
Please sign in to comment.