2018-04-27 00:06:00 +03:00
|
|
|
package macaroons_test
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"encoding/hex"
|
2018-07-31 10:17:17 +03:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path"
|
|
|
|
"testing"
|
2018-04-27 00:06:00 +03:00
|
|
|
|
|
|
|
"github.com/coreos/bbolt"
|
|
|
|
"github.com/lightningnetwork/lnd/macaroons"
|
|
|
|
"google.golang.org/grpc/metadata"
|
2018-07-31 10:17:17 +03:00
|
|
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
|
|
|
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
|
2018-04-27 00:06:00 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
var (
|
|
|
|
testOperation = bakery.Op{
|
|
|
|
Entity: "testEntity",
|
|
|
|
Action: "read",
|
|
|
|
}
|
|
|
|
defaultPw = []byte("hello")
|
|
|
|
)
|
|
|
|
|
|
|
|
// setupTestRootKeyStorage creates a dummy root key storage by
|
|
|
|
// creating a temporary macaroons.db and initializing it with the
|
|
|
|
// default password of 'hello'. Only the path to the temporary
|
|
|
|
// DB file is returned, because the service will open the file
|
|
|
|
// and read the store on its own.
|
|
|
|
func setupTestRootKeyStorage(t *testing.T) string {
|
|
|
|
tempDir, err := ioutil.TempDir("", "macaroonstore-")
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error creating temp dir: %v", err)
|
|
|
|
}
|
2018-11-30 07:04:21 +03:00
|
|
|
db, err := bbolt.Open(path.Join(tempDir, "macaroons.db"), 0600,
|
|
|
|
bbolt.DefaultOptions)
|
2018-04-27 00:06:00 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error opening store DB: %v", err)
|
|
|
|
}
|
|
|
|
store, err := macaroons.NewRootKeyStorage(db)
|
|
|
|
if err != nil {
|
|
|
|
db.Close()
|
|
|
|
t.Fatalf("Error creating root key store: %v", err)
|
|
|
|
}
|
|
|
|
defer store.Close()
|
|
|
|
err = store.CreateUnlock(&defaultPw)
|
2019-09-13 05:59:07 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("error creating unlock: %v", err)
|
|
|
|
}
|
2018-04-27 00:06:00 +03:00
|
|
|
return tempDir
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestNewService tests the creation of the macaroon service.
|
|
|
|
func TestNewService(t *testing.T) {
|
|
|
|
// First, initialize a dummy DB file with a store that the service
|
|
|
|
// can read from. Make sure the file is removed in the end.
|
|
|
|
tempDir := setupTestRootKeyStorage(t)
|
|
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
|
|
|
|
// Second, create the new service instance, unlock it and pass in a
|
|
|
|
// checker that we expect it to add to the bakery.
|
|
|
|
service, err := macaroons.NewService(tempDir, macaroons.IPLockChecker)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error creating new service: %v", err)
|
|
|
|
}
|
2019-09-23 17:34:58 +03:00
|
|
|
defer service.Close()
|
2018-04-27 00:06:00 +03:00
|
|
|
err = service.CreateUnlock(&defaultPw)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error unlocking root key storage: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Third, check if the created service can bake macaroons.
|
2019-09-23 17:34:58 +03:00
|
|
|
macaroon, err := service.Oven.NewMacaroon(
|
|
|
|
context.TODO(), bakery.LatestVersion, nil, testOperation,
|
|
|
|
)
|
2018-04-27 00:06:00 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error creating macaroon from service: %v", err)
|
|
|
|
}
|
|
|
|
if macaroon.Namespace().String() != "std:" {
|
|
|
|
t.Fatalf("The created macaroon has an invalid namespace: %s",
|
|
|
|
macaroon.Namespace().String())
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finally, check if the service has been initialized correctly and
|
|
|
|
// the checker has been added.
|
|
|
|
var checkerFound = false
|
|
|
|
checker := service.Checker.FirstPartyCaveatChecker.(*checkers.Checker)
|
|
|
|
for _, info := range checker.Info() {
|
|
|
|
if info.Name == "ipaddr" &&
|
|
|
|
info.Prefix == "" &&
|
|
|
|
info.Namespace == "std" {
|
|
|
|
checkerFound = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if !checkerFound {
|
|
|
|
t.Fatalf("Checker '%s' not found in service.", "ipaddr")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// TestValidateMacaroon tests the validation of a macaroon that is in an
|
|
|
|
// incoming context.
|
|
|
|
func TestValidateMacaroon(t *testing.T) {
|
2018-04-27 00:06:43 +03:00
|
|
|
// First, initialize the service and unlock it.
|
2018-04-27 00:06:00 +03:00
|
|
|
tempDir := setupTestRootKeyStorage(t)
|
|
|
|
defer os.RemoveAll(tempDir)
|
|
|
|
service, err := macaroons.NewService(tempDir, macaroons.IPLockChecker)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error creating new service: %v", err)
|
|
|
|
}
|
2019-09-23 17:34:58 +03:00
|
|
|
defer service.Close()
|
|
|
|
|
2018-04-27 00:06:00 +03:00
|
|
|
err = service.CreateUnlock(&defaultPw)
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error unlocking root key storage: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Then, create a new macaroon that we can serialize.
|
2019-09-23 17:34:58 +03:00
|
|
|
macaroon, err := service.Oven.NewMacaroon(
|
|
|
|
context.TODO(), bakery.LatestVersion, nil, testOperation,
|
|
|
|
)
|
2018-04-27 00:06:00 +03:00
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error creating macaroon from service: %v", err)
|
|
|
|
}
|
|
|
|
macaroonBinary, err := macaroon.M().MarshalBinary()
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error serializing macaroon: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Because the macaroons are always passed in a context, we need to
|
|
|
|
// mock one that has just the serialized macaroon as a value.
|
2018-04-27 00:06:43 +03:00
|
|
|
md := metadata.New(map[string]string{
|
|
|
|
"macaroon": hex.EncodeToString(macaroonBinary),
|
|
|
|
})
|
2018-04-27 00:06:00 +03:00
|
|
|
mockContext := metadata.NewIncomingContext(context.Background(), md)
|
|
|
|
|
|
|
|
// Finally, validate the macaroon against the required permissions.
|
|
|
|
err = service.ValidateMacaroon(mockContext, []bakery.Op{testOperation})
|
|
|
|
if err != nil {
|
|
|
|
t.Fatalf("Error validating the macaroon: %v", err)
|
|
|
|
}
|
|
|
|
}
|