macaroons: add BakeFromRootKey function

This commit is contained in:
Oliver Gugger 2024-10-09 08:54:16 +02:00
parent fd37c6f90a
commit 6e932c6bc8
No known key found for this signature in database
GPG Key ID: 8E4256593F177720
2 changed files with 126 additions and 0 deletions

68
macaroons/bake.go Normal file
View File

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

58
macaroons/bake_test.go Normal file
View File

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