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) }