database: add boltdb backend, Storage interface
This commit is contained in:
parent
115786ae6e
commit
7fe1f5ed15
|
@ -0,0 +1,209 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
|
||||
"github.com/Xe/uuid"
|
||||
"github.com/asdine/storm"
|
||||
"github.com/brandur/simplebox"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Database errors
|
||||
var (
|
||||
ErrNotImplemented = errors.New("database: not implemented")
|
||||
ErrInvalidKind = errors.New("database: invalid route kind")
|
||||
ErrNoSuchToken = errors.New("database: no such token")
|
||||
ErrCantDecryptCert = errors.New("database: can't decrypt cert")
|
||||
ErrUnknownCryptMethod = errors.New("database: unknown encryption method")
|
||||
)
|
||||
|
||||
// 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)
|
||||
return r, err
|
||||
}
|
||||
|
||||
// GetAllRoutes gets all routes out of the database.
|
||||
func (b *BoltDBStorage) GetAllRoutes(ctx context.Context) ([]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, kind string) (Route, error) {
|
||||
switch kind {
|
||||
case "tcp", "http":
|
||||
// empty case, do nothing
|
||||
default:
|
||||
return Route{}, ErrInvalidKind
|
||||
}
|
||||
|
||||
r := Route{
|
||||
ID: uuid.New(),
|
||||
Creator: ctx.Value("creator").(string),
|
||||
Hostname: domain,
|
||||
}
|
||||
|
||||
err := b.db.Save(&r)
|
||||
if err != nil {
|
||||
return Route{}, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
return b.db.DeleteStruct(&cc)
|
||||
}
|
|
@ -32,7 +32,7 @@ const (
|
|||
|
||||
// CachedCert is an individual cached certificate in the database.
|
||||
type CachedCert struct {
|
||||
Key string `gorethink:"id"`
|
||||
Key string `gorethink:"id" storm:"id"`
|
||||
CryptoLevel CryptoLevel `gorethink:"cryptoLevel"`
|
||||
// PEM-encoded bytes with the above crypto level as a filter.
|
||||
Body []byte `gorethink:"body"`
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"crypto/rsa"
|
||||
|
||||
"git.xeserv.us/xena/route/routerpc"
|
||||
"git.xeserv.us/xena/route/utils"
|
||||
r "github.com/GoRethink/gorethink"
|
||||
)
|
||||
|
||||
|
@ -35,24 +32,20 @@ var tables = []string{
|
|||
"routes",
|
||||
}
|
||||
|
||||
// Route is a single route object serialized to rethinkdb.
|
||||
// Route is a single HTTP route.
|
||||
type Route struct {
|
||||
ID string `gorethink:"id,omitempty"`
|
||||
Hostname string `gorethink:"hostname"`
|
||||
OnionHostname string `gorethink:"onionhostname"`
|
||||
Token string `gorethink:"token"`
|
||||
OnionKey []byte `gorethink:"onionKey"` // PEM-encoded
|
||||
ID string `gorethink:"id,omitempty" storm:"id"`
|
||||
Creator string
|
||||
Hostname string `gorethink:"hostname" storm:"index"`
|
||||
|
||||
Token string `gorethink:"token" storm:"-"` // deprecated
|
||||
}
|
||||
|
||||
// SaveRoute adds the route to the database.
|
||||
func (db *DB) SaveRoute(resp *routerpc.AddHostResponse) error {
|
||||
bytes := utils.RSAPrivateKeyToPem(resp.PrivKey.(*rsa.PrivateKey))
|
||||
|
||||
rt := &Route{
|
||||
Hostname: resp.Hostname,
|
||||
OnionHostname: resp.OnionHostname,
|
||||
Token: resp.Token,
|
||||
OnionKey: bytes,
|
||||
Hostname: resp.Hostname,
|
||||
Token: resp.Token,
|
||||
}
|
||||
|
||||
// TODO: check if OnionHostname or Hostname actually exists in
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"golang.org/x/crypto/acme/autocert"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Storage is the parent interface for the database backends of route.
|
||||
type Storage interface {
|
||||
// routes
|
||||
GetRoute(ctx context.Context, host string) (Route, error)
|
||||
GetAllRoutes(ctx context.Context) ([]Route, error)
|
||||
PutRoute(ctx context.Context, domain, kind string) (Route, error)
|
||||
DeleteRoute(ctx context.Context, id string) error
|
||||
|
||||
// tokens
|
||||
GetToken(ctx context.Context, token string) (Token, error)
|
||||
GetTokensForOwner(ctx context.Context, owner string) ([]Token, error)
|
||||
PutToken(ctx context.Context, token, owner string, scopes []string) (Token, error)
|
||||
DeleteToken(ctx context.Context, id string) error
|
||||
DeactivateToken(ctx context.Context, id string) error
|
||||
|
||||
// certificates
|
||||
GetCert(ctx context.Context, key string) ([]byte, error)
|
||||
PutCert(ctx context.Context, key string, data []byte) error
|
||||
DeleteCert(ctx context.Context, key string) error
|
||||
}
|
||||
|
||||
type storageManager struct {
|
||||
Storage
|
||||
}
|
||||
|
||||
func (s *storageManager) Get(ctx context.Context, key string) ([]byte, error) {
|
||||
return s.GetCert(ctx, key)
|
||||
}
|
||||
|
||||
func (s *storageManager) Put(ctx context.Context, key string, data []byte) error {
|
||||
return s.PutCert(ctx, key, data)
|
||||
}
|
||||
|
||||
func (s *storageManager) Delete(ctx context.Context, key string) error {
|
||||
return s.DeleteCert(ctx, key)
|
||||
}
|
||||
|
||||
// Cache creates an autocert.Cache from a Storage instance.
|
||||
func Cache(s Storage) autocert.Cache {
|
||||
return autocert.Cache(&storageManager{
|
||||
Storage: s,
|
||||
})
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
package database
|
||||
|
||||
import "time"
|
||||
|
||||
// Token is a single authorization token.
|
||||
type Token struct {
|
||||
ID string `storm:"id"`
|
||||
Body string `storm:"unique"`
|
||||
Owner string `storm:"index"`
|
||||
Scopes []string
|
||||
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
Active bool `json:"active"`
|
||||
}
|
Loading…
Reference in New Issue