mirror of
https://github.com/coder/coder.git
synced 2026-06-04 05:28:20 +00:00
b522c9471a
## Description Implements in-memory certificate caching for the AI Bridge MITM proxy. Certificate generation is expensive (RSA key generation + signing), so caching avoids repeated generation for the same hostname. ## Changes * Add `certCache` struct implementing `goproxy.CertStorage` with thread-safe double-check locking * Wire certificate cache into the proxy via `proxy.CertStore` * Add unit tests for cache behavior (hit, miss, errors, concurrency) * Add integration test to verify caching works end-to-end through the proxy Closes https://github.com/coder/internal/issues/1183
72 lines
2.0 KiB
Go
72 lines
2.0 KiB
Go
package aibridgeproxyd
|
|
|
|
import (
|
|
"crypto/tls"
|
|
"sync"
|
|
|
|
"golang.org/x/xerrors"
|
|
"tailscale.com/util/singleflight"
|
|
)
|
|
|
|
// CertCache implements goproxy.CertStorage to cache generated leaf certificates
|
|
// in memory. Certificate generation is expensive (RSA key generation + signing),
|
|
// so caching avoids repeated generation for the same hostname during MITM.
|
|
type CertCache struct {
|
|
mu sync.RWMutex
|
|
certs map[string]*tls.Certificate
|
|
singleFlight singleflight.Group[string, *tls.Certificate]
|
|
}
|
|
|
|
// NewCertCache creates a new certificate cache that maps hostnames to their
|
|
// generated TLS certificates.
|
|
func NewCertCache() *CertCache {
|
|
return &CertCache{
|
|
certs: make(map[string]*tls.Certificate),
|
|
}
|
|
}
|
|
|
|
// Fetch retrieves a cached certificate for the given hostname, or generates
|
|
// and caches a new one using the provided generator function.
|
|
//
|
|
// Uses singleflight to ensure concurrent requests for the same hostname share
|
|
// a single in-flight generation rather than waiting on a mutex. This means only
|
|
// one goroutine generates the certificate while others wait on the result directly.
|
|
func (c *CertCache) Fetch(hostname string, genFunc func() (*tls.Certificate, error)) (*tls.Certificate, error) {
|
|
// Cache hit: check cache with read lock.
|
|
c.mu.RLock()
|
|
cert, ok := c.certs[hostname]
|
|
c.mu.RUnlock()
|
|
if ok {
|
|
return cert, nil
|
|
}
|
|
|
|
// Cache miss: use singleflight to ensure only one goroutine generates
|
|
// the certificate for a given hostname, even under concurrent requests.
|
|
cert, err, _ := c.singleFlight.Do(hostname, func() (*tls.Certificate, error) {
|
|
// Double-check cache inside singleflight in case another call
|
|
// already populated it.
|
|
c.mu.RLock()
|
|
if cert, ok := c.certs[hostname]; ok {
|
|
c.mu.RUnlock()
|
|
return cert, nil
|
|
}
|
|
c.mu.RUnlock()
|
|
|
|
cert, err := genFunc()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if cert == nil {
|
|
return nil, xerrors.New("generator function returned nil certificate")
|
|
}
|
|
|
|
c.mu.Lock()
|
|
c.certs[hostname] = cert
|
|
c.mu.Unlock()
|
|
|
|
return cert, nil
|
|
})
|
|
|
|
return cert, err
|
|
}
|