package store import ( "context" "crypto/tls" "fmt" "io" "github.com/asdine/storm" "github.com/coreos/bbolt" ) type Interface interface { Domain() string TLS() tls.Certificate TLSConfig() *tls.Config SetDomain(string) error SetTLS([]byte, []byte) error io.Closer } func New(ctx context.Context, filename string) (Interface, error) { db, err := storm.Open(filename, storm.BoltOptions(0600, &bolt.Options{})) if err != nil { return nil, err } out := &concrete{ filename: filename, storm: db, } if err := out.setup(); err != nil { return nil, err } return out, nil } func Init(filename, domain string, certPEM, keyPEM []byte) error { db, err := storm.Open(filename, storm.BoltOptions(0600, &bolt.Options{})) if err != nil { return err } builder := &concrete{ filename: filename, storm: db, } defer builder.Close() if err := builder.SetDomain(domain); err != nil { return fmt.Errorf("Couldn't set domain: %v", err) } if err := builder.SetTLS(certPEM, keyPEM); err != nil { return fmt.Errorf("Couldn't set TLS: %v", err) } return nil } type concrete struct { filename string storm *storm.DB domainBucket storm.Node // These are persisted in BoltDB, but we // Might as well keep them in memory for the duration, though. domain string cert tls.Certificate } func (c *concrete) Domain() string { return c.domain } func (c *concrete) TLS() tls.Certificate { return c.cert } func (c *concrete) TLSConfig() *tls.Config { return &tls.Config{ GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) { certCopy := c.TLS() return &certCopy, nil }, ServerName: c.Domain(), } } func (c *concrete) SetDomain(domain string) error { if err := c.storm.Set("config", "domain", domain); err != nil { return err } c.domain = domain return nil } func (c *concrete) SetTLS(certPEM, keyPEM []byte) error { cert, err := tls.X509KeyPair(certPEM, keyPEM) if err != nil { return fmt.Errorf("Couldn't parse data as TLS keypair: %v", err) } if cert.PrivateKey == nil { return fmt.Errorf("No private key for TLS certificate") } domainBucket := c.storm.From("domains").From(c.Domain()) tx, err := domainBucket.Begin(true) if err != nil { return err } if err := tx.Set("config", "cert", certPEM); err != nil { return err } if err := tx.Set("config", "key", keyPEM); err != nil { return err } if err := tx.Commit(); err != nil { return err } c.cert = cert return nil } func (c *concrete) Close() error { return c.storm.Close() } // Bootstraps all cached values from storm func (c *concrete) setup() error { var domain string var keyPEM []byte var certPEM []byte if err := c.storm.Get("config", "domain", &domain); err != nil { return fmt.Errorf("Couldn't read config/domain: %v", err) } domainBucket := c.storm.From("domains").From(domain) if err := domainBucket.Get("config", "cert", &certPEM); err != nil { return fmt.Errorf("Couldn't read domains/%s/config/cert: %v", domain, err) } if err := domainBucket.Get("config", "key", &keyPEM); err != nil { return fmt.Errorf("Couldn't read domains/%s/config/key: %v", domain, err) } cert, err := tls.X509KeyPair(certPEM, keyPEM) if err != nil { return fmt.Errorf("Couldn't parse key and certificate: %v", err) } c.domainBucket = domainBucket c.domain = domain c.cert = cert return nil }