Skip to content
Draft
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
20 changes: 20 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,26 @@ jobs:
- name: Run e2e tests
run: go test -v -tags=e2e ./tests/

e2e-posix-tests:
name: Run E2E POSIX tests
permissions:
contents: read
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
with:
persist-credentials: false
- name: Run docker compose
run: docker compose -f compose.posix.yml up -d --build --wait --wait-timeout 60
- name: Run e2e tests
run: go test -v -tags=e2e,posix -run TestPOSIXReadWrite ./tests/
- name: docker compose down (persist volume)
run: docker compose down
- name: docker compose up for persistent antispam
run: docker compose -f compose.posix.yml up -d --build --wait --wait-timeout 60
- name: Run persistent antispam test
run: go test -v -tags=e2e,posix -run TestPOSIXPersistentAnitspam ./tests/

sharding:
name: Run freeze log tests
permissions:
Expand Down
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
{
"go.buildTags": "e2e,freeze",
"go.buildTags": "e2e,freeze,posix",
}
67 changes: 57 additions & 10 deletions cmd/rekor-server/app/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import (
"github.com/sigstore/rekor-tiles/pkg/signerverifier"
"github.com/sigstore/rekor-tiles/pkg/tessera"
"github.com/sigstore/sigstore/pkg/signature"
tesseraLib "github.com/transparency-dev/trillian-tessera"
)

var serveCmd = &cobra.Command{
Expand All @@ -49,12 +50,50 @@ var serveCmd = &cobra.Command{
}
slog.Info("starting rekor-server", "version", versionInfoStr)

// currently only the GCP driver is supported for rekor-tiles.
tesseraDriver, err := tessera.NewGCPDriver(ctx, viper.GetString("gcp-bucket"), viper.GetString("gcp-spanner"))
if err != nil {
slog.Error(fmt.Sprintf("failed to initialize GCP driver: %v", err.Error()))
readOnly := viper.GetBool("read-only")
persistentAntispam := viper.GetBool("persistent-antispam")
asMaxBatchSize := viper.GetUint("antispam-max-batch-size")
asPushbackThreshold := viper.GetUint("antispam-pushback-threshold")

gcpBucket := viper.GetString("gcp-bucket")
gcpSpannerDb := viper.GetString("gcp-spanner")
posixStorageDir := viper.GetString("posix-storage-dir")

// GCP or POSIX Tessera drivers currently supported
var tesseraDriver tesseraLib.Driver
var antispamProvider tesseraLib.Antispam
switch {
case gcpBucket != "" && gcpSpannerDb != "":
tesseraDriver, err = tessera.NewGCPDriver(ctx, gcpBucket, gcpSpannerDb)
if err != nil {
slog.Error(fmt.Sprintf("failed to initialize GCP driver: %v", err.Error()))
os.Exit(1)
}
if !readOnly && persistentAntispam {
antispamProvider, err = tessera.NewGCPAntispam(ctx, gcpSpannerDb, asMaxBatchSize, asPushbackThreshold)
if err != nil {
slog.Error(fmt.Sprintf("failed to initialize GCP antispam: %v", err.Error()))
os.Exit(1)
}
}
case posixStorageDir != "":
tesseraDriver, err = tessera.NewPOSIXDriver(ctx, posixStorageDir)
if err != nil {
slog.Error(fmt.Sprintf("failed to initialize POSIX driver: %v", err.Error()))
os.Exit(1)
}
if !readOnly && persistentAntispam {
antispamProvider, err = tessera.NewPOSIXAntispam(ctx, posixStorageDir, asMaxBatchSize, asPushbackThreshold)
if err != nil {
slog.Error(fmt.Sprintf("failed to initialize POSIX antispam: %v", err.Error()))
os.Exit(1)
}
}
default:
slog.Error("no flags provided to initialize Tessera driver")
os.Exit(1)
}

var signerOpts []signerverifier.Option
switch {
case viper.GetString("signer-filepath") != "":
Expand Down Expand Up @@ -83,13 +122,12 @@ var serveCmd = &cobra.Command{
slog.Error(fmt.Sprintf("failed to initialize append options: %v", err))
os.Exit(1)
}
readOnly := viper.GetBool("read-only")
var tesseraStorage tessera.Storage
shutdownFn := func(_ context.Context) error { return nil }
// if in read-only mode, don't start the appender, because we don't want new checkpoints being published.
if !readOnly {
appendOptions = tessera.WithLifecycleOptions(appendOptions, viper.GetUint("batch-max-size"), viper.GetDuration("batch-max-age"), viper.GetDuration("checkpoint-interval"), viper.GetUint("pushback-max-outstanding"))
appendOptions, err = tessera.WithAntispamOptions(ctx, appendOptions, viper.GetBool("persistent-antispam"), viper.GetUint("antispam-max-batch-size"), viper.GetUint("antispam-pushback-threshold"), viper.GetString("gcp-spanner"))
appendOptions = tessera.WithAntispamOptions(appendOptions, antispamProvider)
if err != nil {
slog.Error(fmt.Sprintf("failed to configure antispam append options: %v", err))
os.Exit(1)
Expand All @@ -106,7 +144,12 @@ var serveCmd = &cobra.Command{
os.Exit(1)
}

rekorServer := server.NewServer(tesseraStorage, readOnly, algorithmRegistry)
var rekorServer server.RekorServer
if viper.GetBool("serve-read-paths") {
rekorServer = server.NewReadServer(tesseraStorage, readOnly, algorithmRegistry)
} else {
rekorServer = server.NewServer(tesseraStorage, readOnly, algorithmRegistry)
}

server.Serve(
ctx,
Expand Down Expand Up @@ -141,6 +184,7 @@ func init() {
serveCmd.Flags().String("grpc-address", "127.0.0.1", "GRPC address to bind to")
serveCmd.Flags().Duration("timeout", 60*time.Second, "timeout")
serveCmd.Flags().Int("max-request-body-size", 4*1024*1024, "maximum request body size in bytes")
serveCmd.Flags().Bool("serve-read-paths", false, "whether to serve the read paths /checkpoint and /tile from the filesystem, as an alternative to a standalone read traffic server")

// hostname
hostname, err := os.Hostname()
Expand All @@ -153,6 +197,9 @@ func init() {
serveCmd.Flags().String("gcp-bucket", "", "GCS bucket for tile and checkpoint storage")
serveCmd.Flags().String("gcp-spanner", "", "Spanner database URI")

// posix config
serveCmd.Flags().String("posix-storage-dir", "", "directory for tile and checkpoint storage for a POSIX log")

// checkpoint signing configs
serveCmd.Flags().String("signer-filepath", "", "path to the signing key")
serveCmd.Flags().String("signer-password", "", "password to decrypt the signing key")
Expand All @@ -168,9 +215,9 @@ func init() {
serveCmd.Flags().Uint("pushback-max-outstanding", tessera.DefaultPushbackMaxOutstanding, "the maximum number of 'in-flight' add requests")

// antispam configs
serveCmd.Flags().Bool("persistent-antispam", false, "whether to enable persistent antispam measures; only available for GCP storage backend and not supported by the Spanner storage emulator")
serveCmd.Flags().Uint("antispam-max-batch-size", tessera.DefaultAntispamMaxBatchSize, "maximum batch size for deduplication operations; recommend around 1500 for Spanner instances with 300 or more PU, or around 64 for smaller (e.g. 100 PU) instances")
serveCmd.Flags().Uint("antispam-pushback-threshold", tessera.DefaultAntispamPushbackThreshold, "maximum number of 'in-flight' add requests the antispam operator will allow before pushing back")
serveCmd.Flags().Bool("persistent-antispam", false, "whether to enable persistent antispam measures; available for GCP and POSIX storage backends; not supported by the Spanner storage emulator")
serveCmd.Flags().Uint("antispam-max-batch-size", 0, "maximum batch size for deduplication operations; will default to Tessera recommendation if unset; for Spanner, recommend around 1500 with 300 or more PU, or around 64 for smaller (e.g. 100 PU) instances")
serveCmd.Flags().Uint("antispam-pushback-threshold", 0, "maximum number of 'in-flight' add requests the antispam operator will allow before pushing back; will default to Tessera recommendation if unset")

// allowed entry signing algorithms
keyAlgorithmTypes, err := defaultKeyAlgorithms()
Expand Down
46 changes: 46 additions & 0 deletions compose.posix.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Copyright 2025 The Sigstore Authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

services:
rekor:
build:
context: .
target: deploy
command:
- "rekor-server"
- "serve"
- "--http-address=0.0.0.0"
- "--grpc-address=0.0.0.0"
- "--hostname=rekor-local"
- "--posix-storage-dir=/tmp/posixlog"
- "--serve-read-paths=true"
- "--persistent-antispam=true"
- "--signer-filepath=/pki/ed25519-priv-key.pem"
- "--checkpoint-interval=2s"
ports:
- "3003:3000" # http port
- "3001:3001" # grpc port
- "2114:2112" # metrics port
healthcheck:
test:
- CMD-SHELL
- curl http://localhost:3000/healthz | grep '{"status":"SERVING"}'
timeout: 30s
retries: 10
interval: 3s
volumes:
- ./tests/testdata/pki:/pki
- antispam:/tmp/posixlog
volumes:
antispam: {}
2 changes: 2 additions & 0 deletions compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
services:
spanner:
image: gcr.io/cloud-spanner-emulator/emulator:1.5.33@sha256:b211c813058e95bbdabfcb5dc78de0deb2d8a51e071532b2e90485fc6ec3a877
platform: linux/amd64
gcs:
image: fsouza/fake-gcs-server:1.52.2@sha256:d47b4cf8b87006cab8fbbecfa5f06a2a3c5722e464abddc0d107729663d40ec4
volumes:
Expand Down Expand Up @@ -62,6 +63,7 @@ services:
- "--gcp-spanner=projects/rekor-tiles-e2e/instances/rekor-tiles/databases/sequencer"
- "--signer-filepath=/pki/ed25519-priv-key.pem"
- "--checkpoint-interval=2s"
- "--serve-read-paths=true"
ports:
- "3003:3000" # http port
- "3001:3001" # grpc port
Expand Down
5 changes: 5 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ require (
github.com/cncf/xds/go v0.0.0-20250121191232-2f005788dc42 // indirect
github.com/common-nighthawk/go-figure v0.0.0-20210622060536-734e95fb86be // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgraph-io/badger/v4 v4.7.0 // indirect
github.com/dgraph-io/ristretto/v2 v2.2.0 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
github.com/fatih/color v1.18.0 // indirect
Expand All @@ -94,6 +97,7 @@ require (
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
github.com/google/addlicense v1.1.1 // indirect
github.com/google/flatbuffers v25.2.10+incompatible // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/go-containerregistry v0.20.3 // indirect
github.com/google/s2a-go v0.1.9 // indirect
Expand All @@ -114,6 +118,7 @@ require (
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/jellydator/ttlcache/v3 v3.3.0 // indirect
github.com/jmespath/go-jmespath v0.4.1-0.20220621161143-b0104c826a24 // indirect
github.com/klauspost/compress v1.18.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/letsencrypt/boulder v0.0.0-20240620165639-de9c06129bec // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
Expand Down
10 changes: 10 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -743,10 +743,18 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger/v4 v4.7.0 h1:Q+J8HApYAY7UMpL8d9owqiB+odzEc0zn/aqOD9jhc6Y=
github.com/dgraph-io/badger/v4 v4.7.0/go.mod h1:He7TzG3YBy3j4f5baj5B7Zl2XyfNe5bl4Udl0aPemVA=
github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM=
github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da h1:aIftn67I1fkbMa512G+w+Pxci9hJPB8oMnkcP3iZF38=
github.com/dgryski/go-farm v0.0.0-20240924180020-3414d57e47da/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
Expand Down Expand Up @@ -859,6 +867,8 @@ github.com/google/addlicense v1.1.1/go.mod h1:Sm/DHu7Jk+T5miFHHehdIjbi4M5+dJDRS3
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/flatbuffers v2.0.8+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q=
github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
Expand Down
4 changes: 2 additions & 2 deletions pkg/server/grpc.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ type grpcServer struct {
serverEndpoint string
}

// newGRPCServer starts a new grpc server and registers the services.
func newGRPCServer(config *GRPCConfig, server rekorServer) *grpcServer {
// newGRPCServer starts a new gRPC server and registers the services
func newGRPCServer(config *GRPCConfig, server RekorServer) *grpcServer {
var opts []grpc.ServerOption

grpcPanicRecoveryHandler := func(p any) (err error) {
Expand Down
2 changes: 1 addition & 1 deletion pkg/server/grpc_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func TestServe_grpcSmoke(t *testing.T) {
server.Start(t)
defer server.Stop(t)

// check if we can hit grpc endpoints
// check if we can hit gRPC endpoints
conn, err := grpc.NewClient(
server.gc.GRPCTarget(),
grpc.WithTransportCredentials(insecure.NewCredentials()))
Expand Down
11 changes: 9 additions & 2 deletions pkg/server/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,15 @@ import (
const (
httpStatusCodeHeader = "x-http-code"
httpErrorMessageHeader = "x-http-error-message"
httpCacheControlHeader = "x-cache-control"
)

type httpProxy struct {
*http.Server
serverEndpoint string
}

// newHTTProxy creates a mux for each of the service grpc methods, including the grpc heatlhcheck.
// newHTTProxy creates a mux for each of the service grpc methods, including the gRPC heatlhcheck.
func newHTTPProxy(ctx context.Context, config *HTTPConfig, grpcServer *grpcServer) *httpProxy {
// configure a custom marshaler to fail on unknown fields
strictMarshaler := runtime.HTTPBodyMarshaler{
Expand Down Expand Up @@ -197,7 +198,13 @@ func httpResponseModifier(ctx context.Context, w http.ResponseWriter, _ proto.Me
}
}

// set http status code
// set cache control
if vals := md.HeaderMD.Get(httpCacheControlHeader); len(vals) > 0 {
delete(md.HeaderMD, httpCacheControlHeader)
w.Header().Set("Cache-Control", vals[0])
}

// set HTTP status code
if vals := md.HeaderMD.Get(httpStatusCodeHeader); len(vals) > 0 {
code, err := strconv.Atoi(vals[0])
if err != nil {
Expand Down
4 changes: 2 additions & 2 deletions pkg/server/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import (
"sync"
)

// Serve starts the grpc server and its http proxy.
func Serve(ctx context.Context, hc *HTTPConfig, gc *GRPCConfig, s rekorServer, tesseraShutdownFn func(context.Context) error) {
// Serve starts the gRPC server and HTTP proxy
func Serve(ctx context.Context, hc *HTTPConfig, gc *GRPCConfig, s RekorServer, tesseraShutdownFn func(context.Context) error) {
var wg sync.WaitGroup

if hc.port == 0 || gc.port == 0 {
Expand Down
5 changes: 3 additions & 2 deletions pkg/server/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,13 @@ import (
"google.golang.org/protobuf/types/known/emptypb"
)

// rekorServer is the collection of methods that our grpc server must implement.
type rekorServer interface {
// RekorServer is the collection of methods that the gRPC server must implement
type RekorServer interface {
pb.RekorServer
grpc_health_v1.HealthServer
}

// Server implements the write path and default healthcheck for all storage backends
type Server struct {
pb.UnimplementedRekorServer
grpc_health_v1.UnimplementedHealthServer
Expand Down
Loading