macaroons: add macaroons package and update glide

This commit is contained in:
Alex 2017-08-17 19:50:15 -06:00 committed by Olaoluwa Osuntokun
parent 5ef077e5c8
commit 662731e719
5 changed files with 282 additions and 2 deletions

14
glide.lock generated

@ -1,5 +1,5 @@
hash: d40636a5875b6dedd06b34514ea3430717c02153ecd16f32aa5bcedeedd6a833 hash: 6569f51a7172f16b26c374cdcf3cae0abea234207ac99bfa6dc4712406bfc4d2
updated: 2017-08-02T21:19:34.129016558-07:00 updated: 2017-08-09T14:51:02.638659953-06:00
imports: imports:
- name: github.com/aead/chacha20 - name: github.com/aead/chacha20
version: d31a916ded42d1640b9d89a26f8abd53cc96790c version: d31a916ded42d1640b9d89a26f8abd53cc96790c
@ -180,4 +180,14 @@ imports:
- stats - stats
- tap - tap
- transport - transport
- name: gopkg.in/macaroon.v1
version: d8fd13e6951f2ce46f0964a58149cf2f103cac9a
- name: gopkg.in/macaroon-bakery.v1
version: 8e14f8b0f5e286ad100ca8d6ce5bde0f0dfb8b21
- name: github.com/juju/loggo
version: 8232ab8918d91c72af1a9fb94d3edbe31d88b790
- name: github.com/rogpeppe/fastuuid
version: 6724a57986aff9bff1a1770e9347036def7c89f6
- name: gopkg.in/errgo.v1
version: 442357a80af5c6bf9b6d51ae791a39c3421004f3
testImports: [] testImports: []

@ -71,3 +71,8 @@ import:
- chaincfg - chaincfg
- package: github.com/lightninglabs/neutrino - package: github.com/lightninglabs/neutrino
version: 807d3e267a2863654e99dda39ac4daac78943f7e version: 807d3e267a2863654e99dda39ac4daac78943f7e
- package: gopkg.in/macaroon.v1
- package: gopkg.in/macaroon-bakery.v1
- package: github.com/juju/loggo
- package: github.com/rogpeppe/fastuuid
- package: gopkg.in/errgo.v1

76
macaroons/auth.go Normal file

@ -0,0 +1,76 @@
package macaroons
import (
"encoding/hex"
"fmt"
"golang.org/x/net/context"
"google.golang.org/grpc/metadata"
"gopkg.in/macaroon-bakery.v1/bakery"
"gopkg.in/macaroon-bakery.v1/bakery/checkers"
macaroon "gopkg.in/macaroon.v1"
)
// MacaroonCredential wraps a macaroon to implement the
// credentials.PerRPCCredentials interface.
type MacaroonCredential struct {
*macaroon.Macaroon
}
// RequireTransportSecurity implements the PerRPCCredentials interface.
func (m MacaroonCredential) RequireTransportSecurity() bool {
return true
}
// GetRequestMetadata implements the PerRPCCredentials interface.
func (m MacaroonCredential) GetRequestMetadata(ctx context.Context,
uri ...string) (map[string]string, error) {
macBytes, err := m.MarshalBinary()
if err != nil {
return nil, err
}
md := make(map[string]string)
md["macaroon"] = hex.EncodeToString(macBytes)
return md, nil
}
// NewMacaroonCredential returns a copy of the passed macaroon wrapped in a
// MacaroonCredential struct which implements PerRPCCredentials.
func NewMacaroonCredential(m *macaroon.Macaroon) MacaroonCredential {
ms := MacaroonCredential{}
ms.Macaroon = m.Clone()
return ms
}
// ValidateMacaroon validates auth given a bakery service, context, and uri.
func ValidateMacaroon(ctx context.Context, method string,
svc *bakery.Service) error {
// Get macaroon bytes from context and unmarshal into macaroon.
// TODO(aakselrod): use FromIncomingContext after grpc update in glide.
md, ok := metadata.FromContext(ctx)
if !ok {
return fmt.Errorf("unable to get metadata from context")
}
if len(md["macaroon"]) != 1 {
return fmt.Errorf("expected 1 macaroon, got %d",
len(md["macaroon"]))
}
macBytes, err := hex.DecodeString(md["macaroon"][0])
if err != nil {
return err
}
mac := &macaroon.Macaroon{}
err = mac.UnmarshalBinary(macBytes)
if err != nil {
return err
}
// Check the method being called against the permitted operation and the
// expiration time and return the result.
// TODO(aakselrod): Add more checks as required.
return svc.Check(macaroon.Slice{mac}, checkers.New(
checkers.OperationChecker(method),
checkers.TimeBefore,
))
}

44
macaroons/service.go Normal file

@ -0,0 +1,44 @@
package macaroons
import (
"path"
"gopkg.in/macaroon-bakery.v1/bakery"
"github.com/boltdb/bolt"
)
var (
// dbFileName is the filename within the data directory which contains
// the macaroon stores.
dbFilename = "macaroons.db"
)
// NewService returns a service backed by the macaroon Bolt DB stored in the
// passed directory.
func NewService(dir string) (*bakery.Service, error) {
// Open the database.
macaroonDB, err := bolt.Open(path.Join(dir, dbFilename), 0600,
bolt.DefaultOptions)
if err != nil {
return nil, err
}
rootKeyStore, err := NewRootKeyStorage(macaroonDB)
if err != nil {
return nil, err
}
macaroonStore, err := NewStorage(macaroonDB)
if err != nil {
return nil, err
}
macaroonParams := bakery.NewServiceParams{
Location: "lnd",
Store: macaroonStore,
RootKeyStore: rootKeyStore,
// No third-party caveat support for now.
// TODO(aakselrod): Add third-party caveat support.
Locator: nil,
Key: nil,
}
return bakery.NewService(macaroonParams)
}

145
macaroons/store.go Normal file

@ -0,0 +1,145 @@
package macaroons
import (
"crypto/rand"
"fmt"
"io"
"github.com/boltdb/bolt"
)
const (
// RootKeyLen is the length of a root key.
RootKeyLen = 32
)
var (
// rootKeyBucketName is the name of the root key store bucket.
rootKeyBucketName = []byte("macrootkeys")
// defaultRootKeyID is the ID of the default root key. The first is
// just 0, to emulate the memory storage that comes with bakery.
// TODO(aakselrod): Add support for key rotation.
defaultRootKeyID = "0"
// macaroonBucketName is the name of the macaroon store bucket.
macaroonBucketName = []byte("macaroons")
)
// RootKeyStorage implements the bakery.RootKeyStorage interface.
type RootKeyStorage struct {
*bolt.DB
}
// NewRootKeyStorage creates a RootKeyStorage instance.
// TODO(aakselrod): Add support for encryption of data with passphrase.
func NewRootKeyStorage(db *bolt.DB) (*RootKeyStorage, error) {
// If the store's bucket doesn't exist, create it.
err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(rootKeyBucketName)
return err
})
if err != nil {
return nil, err
}
// Return the DB wrapped in a RootKeyStorage object.
return &RootKeyStorage{db}, nil
}
// Get implements the Get method for the bakery.RootKeyStorage interface.
func (r *RootKeyStorage) Get(id string) ([]byte, error) {
var rootKey []byte
err := r.View(func(tx *bolt.Tx) error {
rootKey = tx.Bucket(rootKeyBucketName).Get([]byte(id))
if len(rootKey) == 0 {
return fmt.Errorf("root key with id %s doesn't exist",
id)
}
return nil
})
if err != nil {
return nil, err
}
return rootKey, nil
}
// RootKey implements the RootKey method for the bakery.RootKeyStorage
// interface.
// TODO(aakselrod): Add support for key rotation.
func (r *RootKeyStorage) RootKey() ([]byte, string, error) {
var rootKey []byte
id := defaultRootKeyID
err := r.Update(func(tx *bolt.Tx) error {
ns := tx.Bucket(rootKeyBucketName)
rootKey = ns.Get([]byte(id))
// If there's no root key stored in the bucket yet, create one.
if len(rootKey) != 0 {
return nil
}
// Create a RootKeyLen-byte root key.
rootKey = make([]byte, RootKeyLen)
if _, err := io.ReadFull(rand.Reader, rootKey[:]); err != nil {
return err
}
return ns.Put([]byte(id), rootKey)
})
if err != nil {
return nil, "", err
}
return rootKey, id, nil
}
// Storage implements the bakery.Storage interface.
type Storage struct {
*bolt.DB
}
// NewStorage creates a Storage instance.
// TODO(aakselrod): Add support for encryption of data with passphrase.
func NewStorage(db *bolt.DB) (*Storage, error) {
// If the store's bucket doesn't exist, create it.
err := db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(macaroonBucketName)
return err
})
if err != nil {
return nil, err
}
// Return the DB wrapped in a Storage object.
return &Storage{db}, nil
}
// Put implements the Put method for the bakery.Storage interface.
func (s *Storage) Put(location string, item string) error {
return s.Update(func(tx *bolt.Tx) error {
return tx.Bucket(macaroonBucketName).Put([]byte(location),
[]byte(item))
})
}
// Get implements the Get method for the bakery.Storage interface.
func (s *Storage) Get(location string) (string, error) {
var item string
err := s.View(func(tx *bolt.Tx) error {
itemBytes := tx.Bucket(macaroonBucketName).Get([]byte(location))
if len(itemBytes) == 0 {
return fmt.Errorf("couldn't get item for location %s",
location)
}
item = string(itemBytes)
return nil
})
if err != nil {
return "", err
}
return item, nil
}
// Del implements the Del method for the bakery.Storage interface.
func (s *Storage) Del(location string) error {
return s.Update(func(tx *bolt.Tx) error {
return tx.Bucket(macaroonBucketName).Delete([]byte(location))
})
}