Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions cmd/omniwitness/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,20 @@ uniquely identifies the witness, and be under a domain you control.

A keypair can be generated using the `cmd/generate_keys` tool in this repo.

### Witness Key Rotation

The omniwitness allows for multiple signatures to be added to checkpoints, this
is accomplished by providing multiple instances of the `--private_key_path` flag.

Note that ordering is important - the witness uses the verifier from the _first_
provided flag to authenticate previously witnesses checkpoints which are stored locally.

In order to rotate the key used by the witness, you should use several steps:
1. Witness signing with key `A`: `--private_key_path=.../A.sec`
1. Update to sign with keys `A` and `B`: `--private_key_path=.../A.sec --private_key_path=.../B.sec` (note ordering - `A.sec` _before_ `B.sec`)
1. Wait sufficient time for all clients to become aware of, and switch over to, the new 'B' key.
1. Stop signing with key 'A': `--private_key_path=.../B.sec`.

### Public witness network support

The omniwitness supports automatic provisioning of logs via the public witness network.
Expand Down
43 changes: 27 additions & 16 deletions cmd/omniwitness/monolith.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import (

func init() {
flag.Var(&publicWitnessConfigs, "public_witness_config_url", "URL of a public witness network config file. May be specified multiple times to configure the union of multiple files.")
flag.Var(&signingKeyPaths, "private_key_path", "Path to a file containing a note-compatible Ed25519 or MLDSA signing key to use. May be specified multiple times to configure multiple signing keys to use. See README.md for details.")
}

var (
Expand All @@ -54,7 +55,7 @@ var (
dbMaxConns = flag.Int("db_max_conns", 1000, "Maximum number of connections to sqlite3 database")

signingKey = flag.String("private_key", "", "The note-compatible signing key to use. DEPRECATED: please use --private_key_path")
signingKeyPath = flag.String("private_key_path", "", "Path to a file containing a note-compatible Ed25519 signing key to use")
signingKeyPaths multiStringFlag
restDistributorBaseURL = flag.String("rest_distro_url", "", "Optional base URL to a distributor that takes witnessed checkpoints via a PUT request")
bastionAddr = flag.String("bastion_addr", "", "host:port of the bastion to connect to, or empty to not connect to a bastion")
bastionKeyPath = flag.String("bastion_key_path", "", "Path to a file containing an ed25519 private key in PKCS8 PEM format")
Expand Down Expand Up @@ -120,7 +121,7 @@ func main() {
}
}

signerCosigV1 := signerFromFlags()
witnessSigners, witnessVerifier := signerFromFlags()

l, err := omniwitness.NewStaticLogConfig(omniwitness.DefaultConfigLogs)
if err != nil {
Expand Down Expand Up @@ -173,8 +174,8 @@ func main() {
}

opConfig := omniwitness.OperatorConfig{
WitnessKeys: []note.Signer{signerCosigV1},
WitnessVerifier: signerCosigV1.Verifier(),
WitnessKeys: witnessSigners,
WitnessVerifier: witnessVerifier,
RestDistributorBaseURL: *restDistributorBaseURL,
BastionAddr: *bastionAddr,
BastionKey: bastionKey,
Expand All @@ -192,26 +193,36 @@ func main() {
}
}

func signerFromFlags() *f_note.Signer {
var k string
func signerFromFlags() ([]note.Signer, note.Verifier) {
var ks []string
switch {
case *signingKey != "":
klog.Warningf("The --private_key flag is deprecated, please use --private_key_path flag instead. This will become a fatal error shortly.")
k = *signingKey
case *signingKeyPath != "":
r, err := os.ReadFile(*signingKeyPath)
if err != nil {
klog.Exitf("Failed to read private key from %q: %v", *signingKeyPath, err)
ks = append(ks, *signingKey)
case len(signingKeyPaths) > 0:
for _, p := range signingKeyPaths {
r, err := os.ReadFile(p)
if err != nil {
klog.Exitf("Failed to read private key from %q: %v", p, err)
}
ks = append(ks, string(r))
}
k = string(r)
default:
klog.Exitf("Please provide a --private_key_path flag.")
}
signerCosigV1, err := f_note.NewSignerForCosignatureV1(k)
if err != nil {
klog.Exitf("Failed to init signer v1: %v", err)
signers := []note.Signer{}
var verifier note.Verifier
for _, k := range ks {
signerCosigV1, err := f_note.NewSignerForCosignatureV1(k)
if err != nil {
klog.Exitf("Failed to init signer v1: %v", err)
}
signers = append(signers, signerCosigV1)
if verifier == nil {
verifier = signerCosigV1.Verifier()
}
}
return signerCosigV1
return signers, verifier
}

func readPrivateKey(f string) (ed25519.PrivateKey, error) {
Expand Down
17 changes: 16 additions & 1 deletion cmd/omniwitness_gcp/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ Take note of the resource name for this database, it'll be of the format `projec

There must already be a Secret Manager secret created which contains a note-formatted Ed25519 signing key.

You'll need the resource name for the initial version of this secret , it'be of the format `projects/{project_id}/secrets/{secret_name}/versions/1`
You'll need the resource name for the initial version of this secret , it should be of the format `projects/{project_id}/secrets/{secret_name}/versions/1`

The `cmd/generate_keys_gcp` tool in this repo will generate a new signing & verification key pair and store them directly in Secret Manager.

Expand All @@ -59,6 +59,21 @@ go run github.com/transparency-dev/witness/cmd/generate_keys_gcp@HEAD \
Note that while we *only* need the signing key to start the witness (`omniwitness_gcp` is able to derive the corresponding public key at runtime
from the secret key), having the public key stored somewhere is likely to be useful when you wish to publicise/share your witness' identity.

### Witness Key Rotation

OmniGCP allows for multiple signatures to be added to checkpoints, this
is accomplished by providing multiple instances of the `--signer_private_key_secret_name` flag.

Note that ordering is important - the witness uses the verifier from the _first_
provided flag to authenticate previously witnesses checkpoints which are stored locally.

In order to rotate the key used by the witness, you should use several steps:
1. Witness signing with key `A`: `--signer_private_key_secret_name=.../secrets/A/...`
1. Update to sign with keys `A` and `B`: `--signer_private_key_secret_name=.../secrets/A/... --signer_private_key_secret_name=.../secrets/B/...` (note ordering - `A` _before_ `B`)
1. Wait sufficient time for all clients to become aware of, and switch over to, the new 'B' key.
1. Stop signing with key 'A': `--signer_private_key_secret_name=.../secrets/B/...`.


### Starting the witness

You can run this binary directly on a GCP VM like so:
Expand Down
28 changes: 21 additions & 7 deletions cmd/omniwitness_gcp/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,15 @@ import (

func init() {
flag.Var(&publicWitnessConfigs, "public_witness_config_url", "URL of a public witness network config file. May be specified multiple times to configure the union of multiple files.")
flag.Var(&signerPrivateKeySecretNames, "signer_private_key_secret_name", "Private key secret name for witness signatures. Format: projects/{projectId}/secrets/{secretName}/versions/{secretVersion}. May be specified multiple times to configure multiple signing keys to use, see README.md for details.")
}

var (
addr = flag.String("listen", ":8080", "Address to listen on")
spannerURI = flag.String("spanner", "", "Spanner resource URI. Format: projects/{projectName}/instances/{spannerInstance}/databases/{databaseName}.")

signerPrivateKeySecretName = flag.String("signer_private_key_secret_name", "", "Private key secret name for witnes signatures. Format: projects/{projectId}/secrets/{secretName}/versions/{secretVersion}.")
httpTimeout = flag.Duration("http_timeout", 10*time.Second, "HTTP timeout for outbound requests.")
signerPrivateKeySecretNames multiStringFlag
httpTimeout = flag.Duration("http_timeout", 10*time.Second, "HTTP timeout for outbound requests.")

additionalLogYaml = flag.String("additional_logs", "", "The path to an optional addition logs YAML file. Entries in this file will be *added* to the logs configured by default")
publicWitnessConfigs multiStringFlag
Expand Down Expand Up @@ -71,9 +72,22 @@ func main() {
Timeout: *httpTimeout,
}

signer, err := NewSecretManagerSigner(ctx, *signerPrivateKeySecretName)
if err != nil {
klog.Exitf("Failed to init signer v1: %v", err)
if len(signerPrivateKeySecretNames) == 0 {
klog.Exit("Must provide at least one --signer_private_key_secret_name flag")
}

signers := []note.Signer{}
var verifier note.Verifier

for _, s := range signerPrivateKeySecretNames {
signer, err := NewSecretManagerSigner(ctx, s)
if err != nil {
klog.Exitf("Failed to init signer %q: %v", s, err)
}
signers = append(signers, signer)
if verifier == nil {
verifier = signer.Verifier()
}
}

p, shutdown, err := newSpannerPersistence(ctx, *spannerURI)
Expand All @@ -96,8 +110,8 @@ func main() {
}

opConfig := omniwitness.OperatorConfig{
WitnessKeys: []note.Signer{signer},
WitnessVerifier: signer.Verifier(),
WitnessKeys: signers,
WitnessVerifier: verifier,
ServeMux: mux,
Logs: p,
WitnessNetworkConfigURLs: publicWitnessConfigs,
Expand Down
Loading