route/internal/database/boltdb.go

250 lines
5.6 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
}
// 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),
}
return Storage(b), nil
}
// interface compliance
var (
_ Storage = &BoltDBStorage{}
)
// GetRoute gets a single route out of the database.
func (b *BoltDBStorage) GetRoute(ctx context.Context, host string) (Route, error) {
r := Route{}
err := b.db.One("Hostname", host, &r)
if err != nil {
ln.Error(err, ln.F{"err": err, "action": "route_get_route"})
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
}
// GetAllRoutes gets all routes out of the database for a given user by username.
func (b *BoltDBStorage) GetAllRoutes(ctx context.Context, user string) ([]Route, error) {
rs := []Route{}
err := b.db.All(&rs)
return rs, err
}
// PutRoute creates a new route in the database.
func (b *BoltDBStorage) PutRoute(ctx context.Context, domain, creator string) (Route, error) {
r := Route{
ID: uuid.New(),
Creator: creator,
Hostname: domain,
}
err := b.db.Save(&r)
if err != nil {
return Route{}, err
}
defer b.db.Commit()
return r, err
}
// DeleteRoute removes a route from the database.
func (b *BoltDBStorage) DeleteRoute(ctx context.Context, id string) error {
r := Route{}
err := b.db.One("ID", id, &r)
if err != nil {
return err
}
defer b.db.Commit()
return b.db.DeleteStruct(&r)
}
// GetToken fetches a token from the database. This is mainly used in validation
// of tokens.
func (b *BoltDBStorage) GetToken(ctx context.Context, token string) (Token, error) {
t := Token{}
err := b.db.One("Body", token, &t)
return t, err
}
// GetTokenID fetches a token by a given token ID.
func (b *BoltDBStorage) GetTokenID(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
}
// GetTokensForOwner fetches all of the tokens owned by a given owner.
func (b *BoltDBStorage) GetTokensForOwner(ctx context.Context, owner string) ([]Token, error) {
ts := []Token{}
err := b.db.Find("Owner", owner, &ts)
return ts, err
}
// PutToken adds a new token to the database.
func (b *BoltDBStorage) PutToken(ctx context.Context, token, owner string, scopes []string) (Token, error) {
t := Token{
ID: uuid.New(),
Body: token,
Owner: owner,
Scopes: scopes,
CreatedAt: time.Now(),
Active: true,
}
err := b.db.Save(&t)
if err != nil {
return Token{}, err
}
defer b.db.Commit()
return t, nil
}
// DeleteToken removes a token from the database.
func (b *BoltDBStorage) DeleteToken(ctx context.Context, id string) error {
t := Token{}
err := b.db.One("ID", id, &t)
if err != nil {
return err
}
return b.db.DeleteStruct(&t)
}
// DeactivateToken de-activates a token in the database. This should be used
// instead of deletion in many cases.
func (b *BoltDBStorage) DeactivateToken(ctx context.Context, id string) error {
t := Token{}
err := b.db.One("ID", id, &t)
if err != nil {
return err
}
defer b.db.Commit()
t.Active = false
return b.db.Save(&t)
}
// GetCert fetches a TLS certificate from the database.
func (b *BoltDBStorage) GetCert(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
switch cc.CryptoLevel {
case CryptoLevelNone:
body = cc.Body
case CryptoLevelSecretbox:
if b.sb == nil {
return nil, ErrCantDecryptCert
}
body, err = b.sb.Decrypt(cc.Body)
if err != nil {
return nil, err
}
}
return body, nil
}
// PutCert adds a new TLS certificate to the database.
func (b *BoltDBStorage) PutCert(ctx context.Context, key string, data []byte) error {
cc := CachedCert{
Key: key,
CryptoLevel: CryptoLevelNone,
Body: data,
}
if b.sb != nil {
cc.CryptoLevel = CryptoLevelSecretbox
cc.Body = b.sb.Encrypt(data)
}
defer b.db.Commit()
return b.db.Save(&cc)
}
// DeleteCert removes a certificate from the database.
func (b *BoltDBStorage) DeleteCert(ct context.Context, key string) error {
cc := CachedCert{}
err := b.db.One("Key", key, &cc)
if err != nil {
return err
}
defer b.db.Commit()
return b.db.DeleteStruct(&cc)
}
// Close ...
func (b *BoltDBStorage) Close() error {
return b.db.Close()
}