Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cache locally whether issuers have been persisted or not already. #169

Merged
merged 15 commits into from
Aug 23, 2024
80 changes: 79 additions & 1 deletion personalities/sctfe/storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,19 @@ import (
"crypto/sha256"
"encoding/hex"
"fmt"
"sync"

"github.com/google/certificate-transparency-go/x509"
tessera "github.com/transparency-dev/trillian-tessera"
"github.com/transparency-dev/trillian-tessera/ctonly"
"k8s.io/klog/v2"
)

const (
// Each key is 64 bytes long, so this will take up to 64MB.
// A CT log references ~15k unique issuer certifiates in 2024, so this gives plenty of space
// if we ever run into this limit, we should re-think how it works.
maxCachedIssuerKeys = 1 << 20
)

// Storage provides all the storage primitives necessary to write to a ct-static-api log.
Expand All @@ -38,6 +47,7 @@ type KV struct {
V []byte
}

// TODO(phboneff): replace with AddIssuersIfNotExist
type IssuerStorage interface {
Exists(ctx context.Context, key []byte) (bool, error)
AddIssuers(ctx context.Context, kv []KV) error
Expand All @@ -53,7 +63,7 @@ type CTStorage struct {
func NewCTSTorage(logStorage tessera.Storage, issuerStorage IssuerStorage) (*CTStorage, error) {
ctStorage := &CTStorage{
storeData: tessera.NewCertificateTransparencySequencedWriter(logStorage),
issuers: issuerStorage,
issuers: NewCachedIssuerStorage(issuerStorage),
}
return ctStorage, nil
}
Expand All @@ -65,6 +75,7 @@ func (cts *CTStorage) Add(ctx context.Context, entry *ctonly.Entry) (uint64, err
}

// AddIssuerChain stores every chain certificate under its sha256.
//
// If an object is already stored under this hash, continues.
func (cts *CTStorage) AddIssuerChain(ctx context.Context, chain []*x509.Certificate) error {
kvs := []KV{}
Expand All @@ -78,3 +89,70 @@ func (cts *CTStorage) AddIssuerChain(ctx context.Context, chain []*x509.Certific
}
return nil
}

// cachedIssuerStorage wraps an IssuerStorage, and keeps a copy the sha256 of certs it contains.
//
// This is intended to make querying faster. It does not keep a copy of the certs, only sha256.
// Only up to N keys will be stored locally.
// TODO(phboneff): add monitoring for the number of keys
type cachedIssuerStorage struct {
sync.RWMutex
m map[string]struct{}
N int // maximum number of entries allowed in m
s IssuerStorage
}

// Exists checks if the key exists in the local cache, if not checks in the underlying storage.
// If it finds it there, caches the key locally.
func (c *cachedIssuerStorage) Exists(ctx context.Context, key []byte) (bool, error) {
phbnf marked this conversation as resolved.
Show resolved Hide resolved
c.RLock()
_, ok := c.m[string(key)]
c.RLock()
phbnf marked this conversation as resolved.
Show resolved Hide resolved
if ok {
klog.V(2).Infof("Exists: found %q in local key cache", key)
return true, nil
}
ok, err := c.s.Exists(ctx, key)
if err != nil {
return false, fmt.Errorf("error checking if issuer %q exists in the underlying IssuerStorage: %s", key, err)
}
if ok {
c.Lock()
c.m[string(key)] = struct{}{}
c.Unlock()
}
return ok, nil
}

// AddIssuers first adds the issuers to the underlying storage, then caches their sha256 locally.
//
// Only up to c.N issuer sha256 will be cached.
func (c *cachedIssuerStorage) AddIssuers(ctx context.Context, kv []KV) error {
req := []KV{}
for _, kv := range kv {
b, err := c.Exists(ctx, kv.K)
if err != nil {
return fmt.Errorf("error checking if issuer %q has been sotred previously: %v", string(kv.K), err)
}
if !b {
req = append(req, kv)
}
}
if err := c.s.AddIssuers(ctx, req); err != nil {
return fmt.Errorf("AddIssuers: error storing issuer data for in the underlying IssuerStorage: %v", err)
}
for _, kv := range req {
if len(c.m) >= c.N {
klog.V(2).Infof("Add: local issuer cache full, will stop caching issuers.")
return nil
}
c.Lock()
c.m[string(kv.K)] = struct{}{}
c.Unlock()
}
return nil
}

func NewCachedIssuerStorage(s IssuerStorage) *cachedIssuerStorage {
return &cachedIssuerStorage{s: s, N: maxCachedIssuerKeys, m: make(map[string]struct{})}
}
Loading