305 lines
7.1 KiB
Go
305 lines
7.1 KiB
Go
package database
|
|
|
|
import (
|
|
"time"
|
|
|
|
"github.com/Xe/ln"
|
|
"github.com/Xe/uuid"
|
|
"github.com/asdine/storm"
|
|
"github.com/brandur/simplebox"
|
|
"github.com/pkg/errors"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
// Database errors
|
|
var (
|
|
ErrNotImplemented = errors.New("database: not implemented")
|
|
ErrInvalidKind = errors.New("database: invalid route kind")
|
|
ErrRouteAlreadyExists = errors.New("database: route already exists")
|
|
ErrTokenAleradyExists = errors.New("database: token already exists")
|
|
ErrNoSuchRoute = errors.New("database: no such route")
|
|
ErrNoSuchToken = errors.New("database: no such token")
|
|
ErrCantDecryptCert = errors.New("database: can't decrypt cert")
|
|
ErrUnknownCryptMethod = errors.New("database: unknown encryption method")
|
|
ErrUnknown = errors.New("database: unknown error")
|
|
)
|
|
|
|
// BoltDBStorage is a backend that uses https://github.com/boltdb/bolt to store
|
|
// route data.
|
|
type BoltDBStorage struct {
|
|
db *storm.DB
|
|
sb *simplebox.SimpleBox
|
|
|
|
cs *boltCertificateStorage
|
|
rs *boltRouteStorage
|
|
ts *boltTokenStorage
|
|
}
|
|
|
|
type boltCertificateStorage struct {
|
|
*BoltDBStorage
|
|
}
|
|
|
|
type boltRouteStorage struct {
|
|
*BoltDBStorage
|
|
}
|
|
|
|
type boltTokenStorage struct {
|
|
*BoltDBStorage
|
|
}
|
|
|
|
// NewBoltStorage creates a new Storage instance backed by BoltDB + Storm.
|
|
func NewBoltStorage(path string, key *[32]byte) (Storage, error) {
|
|
db, err := storm.Open(path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
b := &BoltDBStorage{
|
|
db: db,
|
|
sb: simplebox.NewFromSecretKey(key),
|
|
}
|
|
|
|
b.cs = &boltCertificateStorage{b}
|
|
b.rs = &boltRouteStorage{b}
|
|
b.ts = &boltTokenStorage{b}
|
|
|
|
return b, nil
|
|
}
|
|
|
|
// Certs gets the certificate storage interface.
|
|
func (b *BoltDBStorage) Certs() Certs { return b.cs }
|
|
|
|
// Routes gets the route storage interface.
|
|
func (b *BoltDBStorage) Routes() Routes { return b.rs }
|
|
|
|
// Tokens gets the token storage interface.
|
|
func (b *BoltDBStorage) Tokens() Tokens { return b.ts }
|
|
|
|
// Close cleans up resources for this Storage.
|
|
func (b *BoltDBStorage) Close() error { return b.db.Close() }
|
|
|
|
// interface compliance
|
|
var (
|
|
_ Storage = &BoltDBStorage{}
|
|
_ Certs = &boltCertificateStorage{}
|
|
_ Routes = &boltRouteStorage{}
|
|
_ Tokens = &boltTokenStorage{}
|
|
)
|
|
|
|
func (b *boltRouteStorage) Get(ctx context.Context, id string) (Route, error) {
|
|
return b.getRouteBy(ctx, "ID", id)
|
|
}
|
|
|
|
func (b *boltRouteStorage) GetHost(ctx context.Context, id string) (Route, error) {
|
|
return b.getRouteBy(ctx, "Hostname", id)
|
|
}
|
|
|
|
// getRouteBy gets a single route out of the database by a given field data.
|
|
func (b *boltRouteStorage) getRouteBy(ctx context.Context, match, val string) (Route, error) {
|
|
r := Route{}
|
|
err := b.db.One(match, val, &r)
|
|
if err != nil {
|
|
ln.Error(ctx, err, ln.Action("get route"), ln.F{"match": match, "val": val})
|
|
|
|
switch err {
|
|
case storm.ErrNotFound:
|
|
return Route{}, errors.Wrapf(err, "%v", ErrNoSuchRoute)
|
|
case storm.ErrAlreadyExists:
|
|
return Route{}, errors.Wrapf(err, "%v", ErrRouteAlreadyExists)
|
|
default:
|
|
return Route{}, errors.Wrapf(err, "%v", ErrUnknown)
|
|
}
|
|
}
|
|
|
|
return r, nil
|
|
}
|
|
|
|
// GetAll gets all routes out of the database for a given user by username.
|
|
func (b *boltRouteStorage) GetAll(ctx context.Context, user string) ([]Route, error) {
|
|
rs := []Route{}
|
|
err := b.db.All(&rs)
|
|
return rs, err
|
|
}
|
|
|
|
// Put creates a new route in the database.
|
|
func (b *boltRouteStorage) Put(ctx context.Context, r Route) (Route, error) {
|
|
if r.ID == "" {
|
|
r.ID = uuid.New()
|
|
}
|
|
|
|
err := b.db.Save(&r)
|
|
if err != nil {
|
|
return Route{}, err
|
|
}
|
|
defer b.db.Commit()
|
|
|
|
ln.Log(ctx, r, ln.Action("new route created in database"))
|
|
|
|
return r, err
|
|
}
|
|
|
|
// Delete removes a route from the database.
|
|
func (b *boltRouteStorage) Delete(ctx context.Context, inp Route) (Route, error) {
|
|
r := Route{}
|
|
err := b.db.One("ID", inp.ID, &r)
|
|
if err != nil {
|
|
return r, err
|
|
}
|
|
defer b.db.Commit()
|
|
|
|
ln.Log(ctx, r, ln.Action("route deleted from database"))
|
|
|
|
return r, b.db.DeleteStruct(&r)
|
|
}
|
|
|
|
// GetBody fetches a token from the database. This is mainly used in validation
|
|
// of tokens.
|
|
func (b *boltTokenStorage) GetBody(ctx context.Context, token string) (Token, error) {
|
|
t := Token{}
|
|
err := b.db.One("Body", token, &t)
|
|
return t, err
|
|
}
|
|
|
|
// Get fetches a token by a given token ID.
|
|
func (b *boltTokenStorage) Get(ctx context.Context, id string) (Token, error) {
|
|
t := Token{}
|
|
err := b.db.One("ID", id, &t)
|
|
if err != nil {
|
|
switch err {
|
|
case storm.ErrNotFound:
|
|
return Token{}, ErrNoSuchToken
|
|
default:
|
|
return Token{}, err
|
|
}
|
|
}
|
|
|
|
return t, nil
|
|
}
|
|
|
|
// GetAll fetches all of the tokens owned by a given owner.
|
|
func (b *boltTokenStorage) GetAll(ctx context.Context, owner string) ([]Token, error) {
|
|
ts := []Token{}
|
|
err := b.db.Find("Owner", owner, &ts)
|
|
return ts, err
|
|
}
|
|
|
|
// Put adds a new token to the database.
|
|
func (b *boltTokenStorage) Put(ctx context.Context, t Token) (Token, error) {
|
|
if t.ID == "" {
|
|
t.ID = uuid.New()
|
|
t.CreatedAt = time.Now()
|
|
t.Active = true
|
|
t.Body = uuid.New()
|
|
}
|
|
|
|
err := b.db.Save(&t)
|
|
if err != nil {
|
|
return Token{}, err
|
|
}
|
|
defer b.db.Commit()
|
|
|
|
ln.Log(ctx, t, ln.Action("new token put into database"))
|
|
|
|
return t, nil
|
|
}
|
|
|
|
// Delete removes a token from the database.
|
|
func (b *boltTokenStorage) Delete(ctx context.Context, id string) (Token, error) {
|
|
t := Token{}
|
|
err := b.db.One("ID", id, &t)
|
|
if err != nil {
|
|
return t, err
|
|
}
|
|
|
|
ln.Log(ctx, t, ln.Action("token deleted from database"))
|
|
|
|
return t, b.db.DeleteStruct(&t)
|
|
}
|
|
|
|
// DeleteExpired deletes all expired tokens.
|
|
func (b *boltTokenStorage) DeleteExpired(ctx context.Context) error {
|
|
return errors.New("not implemented")
|
|
}
|
|
|
|
// GetAll fetches all certificates and returns their DECRYPTED BODIES to the caller.
|
|
// This is intended for usage in migration tools only.
|
|
func (b *boltCertificateStorage) GetAll(ctx context.Context) ([]CachedCert, error) {
|
|
var cc []CachedCert
|
|
err := b.db.All(&cc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var result []CachedCert
|
|
|
|
for _, c := range cc {
|
|
r := CachedCert{
|
|
Key: c.Key,
|
|
}
|
|
|
|
r.Body, err = b.sb.Decrypt(c.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
result = append(result, r)
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// Get fetches a TLS certificate from the database.
|
|
func (b *boltCertificateStorage) Get(ctx context.Context, key string) ([]byte, error) {
|
|
cc := CachedCert{}
|
|
err := b.db.One("Key", key, &cc)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var body []byte
|
|
|
|
if b.sb == nil {
|
|
return nil, ErrCantDecryptCert
|
|
}
|
|
|
|
body, err = b.sb.Decrypt(cc.Body)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return body, nil
|
|
}
|
|
|
|
// Put adds a new TLS certificate to the database.
|
|
func (b *boltCertificateStorage) Put(ctx context.Context, key string, data []byte) error {
|
|
cc := CachedCert{
|
|
Key: key,
|
|
Body: data,
|
|
}
|
|
|
|
if b.sb != nil {
|
|
cc.Body = b.sb.Encrypt(data)
|
|
}
|
|
|
|
defer b.db.Commit()
|
|
|
|
ln.Log(ctx, ln.Action("certificate saved to database"), ln.F{"domain": key})
|
|
|
|
return b.db.Save(&cc)
|
|
}
|
|
|
|
// Delete removes a certificate from the database.
|
|
func (b *boltCertificateStorage) Delete(ctx context.Context, key string) error {
|
|
cc := CachedCert{}
|
|
err := b.db.One("Key", key, &cc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
defer b.db.Commit()
|
|
|
|
ln.Log(ctx, ln.F{"domain": key}, ln.Action("certificate deleted from database"))
|
|
|
|
return b.db.DeleteStruct(&cc)
|
|
}
|