diff --git a/macaroons/bake.go b/macaroons/bake.go new file mode 100644 index 000000000..29904d151 --- /dev/null +++ b/macaroons/bake.go @@ -0,0 +1,68 @@ +package macaroons + +import ( + "bytes" + "fmt" + + "golang.org/x/net/context" + "gopkg.in/macaroon-bakery.v2/bakery" + "gopkg.in/macaroon.v2" +) + +// inMemoryRootKeyStore is a simple implementation of bakery.RootKeyStore that +// stores a single root key in memory. +type inMemoryRootKeyStore struct { + rootKey []byte +} + +// A compile-time check to ensure that inMemoryRootKeyStore implements +// bakery.RootKeyStore. +var _ bakery.RootKeyStore = (*inMemoryRootKeyStore)(nil) + +// Get returns the root key for the given id. If the item is not there, it +// returns ErrNotFound. +func (s *inMemoryRootKeyStore) Get(_ context.Context, id []byte) ([]byte, + error) { + + if !bytes.Equal(id, DefaultRootKeyID) { + return nil, bakery.ErrNotFound + } + + return s.rootKey, nil +} + +// RootKey returns the root key to be used for making a new macaroon, and an id +// that can be used to look it up later with the Get method. +func (s *inMemoryRootKeyStore) RootKey(context.Context) ([]byte, []byte, + error) { + + return s.rootKey, DefaultRootKeyID, nil +} + +// BakeFromRootKey creates a new macaroon that is derived from the given root +// key and permissions. +func BakeFromRootKey(rootKey []byte, + permissions []bakery.Op) (*macaroon.Macaroon, error) { + + if len(rootKey) != RootKeyLen { + return nil, fmt.Errorf("root key must be %d bytes, is %d", + RootKeyLen, len(rootKey)) + } + + rootKeyStore := &inMemoryRootKeyStore{ + rootKey: rootKey, + } + + service, err := NewService(rootKeyStore, "lnd", false) + if err != nil { + return nil, fmt.Errorf("unable to create service: %w", err) + } + + ctx := context.Background() + mac, err := service.NewMacaroon(ctx, DefaultRootKeyID, permissions...) + if err != nil { + return nil, fmt.Errorf("unable to create macaroon: %w", err) + } + + return mac.M(), nil +} diff --git a/macaroons/bake_test.go b/macaroons/bake_test.go new file mode 100644 index 000000000..20c22b647 --- /dev/null +++ b/macaroons/bake_test.go @@ -0,0 +1,58 @@ +package macaroons_test + +import ( + "context" + "encoding/hex" + "testing" + + "github.com/lightningnetwork/lnd/macaroons" + "github.com/stretchr/testify/require" + "google.golang.org/grpc/metadata" + "gopkg.in/macaroon-bakery.v2/bakery" +) + +// TestBakeFromRootKey tests that a macaroon can be baked from a root key +// directly without needing to create a store or service first. +func TestBakeFromRootKey(t *testing.T) { + // Create a test store and unlock it. + _, store := newTestStore(t) + + pw := []byte("weks") + err := store.CreateUnlock(&pw) + require.NoError(t, err) + + // Force the store to create a new random root key. + key, id, err := store.RootKey(defaultRootKeyIDContext) + require.NoError(t, err) + require.Len(t, key, 32) + + tmpKey, err := store.Get(defaultRootKeyIDContext, id) + require.NoError(t, err) + require.Equal(t, key, tmpKey) + + // Create a service that uses the root key store. + service, err := macaroons.NewService(store, "lnd", false) + require.NoError(t, err, "Error creating new service") + defer func() { + require.NoError(t, service.Close()) + }() + + // Call the BakeFromRootKey function that derives a macaroon directly + // from the root key. + perms := []bakery.Op{{Entity: "foo", Action: "bar"}} + mac, err := macaroons.BakeFromRootKey(key, perms) + require.NoError(t, err) + + macaroonBytes, err := mac.MarshalBinary() + require.NoError(t, err) + + md := metadata.New(map[string]string{ + "macaroon": hex.EncodeToString(macaroonBytes), + }) + macCtx := metadata.NewIncomingContext(context.Background(), md) + + // The macaroon should be valid for the service, since the root key was + // the same. + err = service.ValidateMacaroon(macCtx, nil, "baz") + require.NoError(t, err) +}