From a2f900ec2dfbccc602c07206c1f9efc078e2df40 Mon Sep 17 00:00:00 2001 From: whythat Date: Fri, 22 Sep 2017 08:51:15 +0300 Subject: [PATCH 1/5] macaroons: add constraints unit tests --- macaroons/constraints_test.go | 94 +++++++++++++++++++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 macaroons/constraints_test.go diff --git a/macaroons/constraints_test.go b/macaroons/constraints_test.go new file mode 100644 index 00000000..9db16617 --- /dev/null +++ b/macaroons/constraints_test.go @@ -0,0 +1,94 @@ +package macaroons + +import ( + "testing" + "time" + + "gopkg.in/macaroon-bakery.v1/bakery" + "gopkg.in/macaroon-bakery.v1/bakery/checkers" + macaroon "gopkg.in/macaroon.v1" +) + +func TestAllowConstraint(t *testing.T) { + macParams := bakery.NewServiceParams{} + svc, err := bakery.NewService(macParams) + if err != nil { + t.Fatalf("Failed to create a new service") + } + mac, err := svc.NewMacaroon("", nil, nil) + if err != nil { + t.Fatalf("Failed to create a new macaroon") + } + + constraint := AllowConstraint("op1", "op2", "op4") + mac, err = AddConstraints(mac, constraint) + if err != nil { + t.Fatalf("Failed to add macaroon constraint") + } + + checker := checkers.New(AllowChecker("op1")) + if err := svc.Check(macaroon.Slice{mac}, checker); err != nil { + t.Fatalf("Allowed operation failed macaroon check") + } + + checker = checkers.New(AllowChecker("op3")) + if err := svc.Check(macaroon.Slice{mac}, checker); err == nil { + t.Fatalf("Disallowed operation passed macaroon check") + } +} + +func TestTimeoutConstraint(t *testing.T) { + macParams := bakery.NewServiceParams{} + svc, err := bakery.NewService(macParams) + if err != nil { + t.Fatalf("Failed to create a new service") + } + mac, err := svc.NewMacaroon("", nil, nil) + if err != nil { + t.Fatalf("Failed to create a new macaroon") + } + + constraint := TimeoutConstraint(1) + mac, err = AddConstraints(mac, constraint) + if err != nil { + t.Fatalf("Failed to add macaroon constraint") + } + + checker := checkers.New(TimeoutChecker()) + if err := svc.Check(macaroon.Slice{mac}, checker); err != nil { + t.Fatalf("Timeout check failed within timeframe") + } + + time.Sleep(time.Second) + if err := svc.Check(macaroon.Slice{mac}, checker); err == nil { + t.Fatalf("Timeout check passed for an expired timeout") + } +} + +func TestIPLockConstraint(t *testing.T) { + macParams := bakery.NewServiceParams{} + svc, err := bakery.NewService(macParams) + if err != nil { + t.Fatalf("Failed to create a new service") + } + mac, err := svc.NewMacaroon("", nil, nil) + if err != nil { + t.Fatalf("Failed to create a new macaroon") + } + + constraint := IPLockConstraint("127.0.0.1") + mac, err = AddConstraints(mac, constraint) + if err != nil { + t.Fatalf("Failed to add macaroon constraint") + } + + checker := checkers.New(IPLockChecker("127.0.0.1")) + if err := svc.Check(macaroon.Slice{mac}, checker); err != nil { + t.Fatalf("IPLock for the same IP failed the test") + } + + checker = checkers.New(IPLockChecker("0.0.0.0")) + if err := svc.Check(macaroon.Slice{mac}, checker); err == nil { + t.Fatalf("IPLock for a different IP passed the test") + } +} From 04f4dbe6399f623cee4f294aab6de317490fef6d Mon Sep 17 00:00:00 2001 From: whythat Date: Fri, 22 Sep 2017 09:35:47 +0300 Subject: [PATCH 2/5] macaroons: add utilities for constraint tests --- macaroons/constraints_test.go | 133 +++++++++++++++++++--------------- 1 file changed, 74 insertions(+), 59 deletions(-) diff --git a/macaroons/constraints_test.go b/macaroons/constraints_test.go index 9db16617..4bd9f552 100644 --- a/macaroons/constraints_test.go +++ b/macaroons/constraints_test.go @@ -1,6 +1,8 @@ package macaroons import ( + "errors" + "fmt" "testing" "time" @@ -9,86 +11,99 @@ import ( macaroon "gopkg.in/macaroon.v1" ) -func TestAllowConstraint(t *testing.T) { +type macError struct { + message string +} + +func (err macError) Error() string { + return err.message +} + +func testConstraint(constraint Constraint, ok checkers.Checker, + failFn func() checkers.Checker) error { macParams := bakery.NewServiceParams{} svc, err := bakery.NewService(macParams) if err != nil { - t.Fatalf("Failed to create a new service") + return errors.New("Failed to create a new service") } mac, err := svc.NewMacaroon("", nil, nil) if err != nil { - t.Fatalf("Failed to create a new macaroon") + return errors.New("Failed to create a new macaroon") } - constraint := AllowConstraint("op1", "op2", "op4") mac, err = AddConstraints(mac, constraint) if err != nil { - t.Fatalf("Failed to add macaroon constraint") + return errors.New("Failed to add macaroon constraint") } - checker := checkers.New(AllowChecker("op1")) - if err := svc.Check(macaroon.Slice{mac}, checker); err != nil { - t.Fatalf("Allowed operation failed macaroon check") + okChecker := checkers.New(ok) + if err := svc.Check(macaroon.Slice{mac}, okChecker); err != nil { + msg := "Correct checker failed: %v" + return macError{fmt.Sprintf(msg, ok)} } - checker = checkers.New(AllowChecker("op3")) - if err := svc.Check(macaroon.Slice{mac}, checker); err == nil { - t.Fatalf("Disallowed operation passed macaroon check") + fail := failFn() + failChecker := checkers.New(fail) + if err := svc.Check(macaroon.Slice{mac}, failChecker); err == nil { + msg := "Incorrect checker succeeded: %v" + return macError{fmt.Sprintf(msg, fail)} + } + return nil +} + +func TestAllowConstraint(t *testing.T) { + if err := testConstraint( + AllowConstraint("op1", "op2", "op4"), + AllowChecker("op1"), + func() checkers.Checker { + return AllowChecker("op3") + }, + ); err != nil { + t.Fatalf(err.Error()) } } func TestTimeoutConstraint(t *testing.T) { - macParams := bakery.NewServiceParams{} - svc, err := bakery.NewService(macParams) - if err != nil { - t.Fatalf("Failed to create a new service") - } - mac, err := svc.NewMacaroon("", nil, nil) - if err != nil { - t.Fatalf("Failed to create a new macaroon") - } - - constraint := TimeoutConstraint(1) - mac, err = AddConstraints(mac, constraint) - if err != nil { - t.Fatalf("Failed to add macaroon constraint") - } - - checker := checkers.New(TimeoutChecker()) - if err := svc.Check(macaroon.Slice{mac}, checker); err != nil { - t.Fatalf("Timeout check failed within timeframe") - } - - time.Sleep(time.Second) - if err := svc.Check(macaroon.Slice{mac}, checker); err == nil { - t.Fatalf("Timeout check passed for an expired timeout") + if err := testConstraint( + TimeoutConstraint(1), + TimeoutChecker(), + func() checkers.Checker { + time.Sleep(time.Second) + return TimeoutChecker() + }, + ); err != nil { + t.Fatalf(err.Error()) } } func TestIPLockConstraint(t *testing.T) { - macParams := bakery.NewServiceParams{} - svc, err := bakery.NewService(macParams) - if err != nil { - t.Fatalf("Failed to create a new service") - } - mac, err := svc.NewMacaroon("", nil, nil) - if err != nil { - t.Fatalf("Failed to create a new macaroon") - } - - constraint := IPLockConstraint("127.0.0.1") - mac, err = AddConstraints(mac, constraint) - if err != nil { - t.Fatalf("Failed to add macaroon constraint") - } - - checker := checkers.New(IPLockChecker("127.0.0.1")) - if err := svc.Check(macaroon.Slice{mac}, checker); err != nil { - t.Fatalf("IPLock for the same IP failed the test") - } - - checker = checkers.New(IPLockChecker("0.0.0.0")) - if err := svc.Check(macaroon.Slice{mac}, checker); err == nil { - t.Fatalf("IPLock for a different IP passed the test") + if err := testConstraint( + IPLockConstraint("127.0.0.1"), + IPLockChecker("127.0.0.1"), + func() checkers.Checker { + return IPLockChecker("0.0.0.0") + }, + ); err != nil { + t.Fatalf(err.Error()) + } +} + +func TestIPLockEmptyIP(t *testing.T) { + if err := testConstraint( + IPLockConstraint(""), + IPLockChecker("127.0.0.1"), + func() checkers.Checker { + return IPLockChecker("0.0.0.0") + }, + ); err != nil { + if _, ok := err.(macError); !ok { + t.Fatalf("IPLock with an empty IP should always pass") + } + } +} + +func TestIPLockBadIP(t *testing.T) { + if err := IPLockConstraint("127.0.0/800"); err == nil { + t.Fatalf("IPLockConstraint with bad IP should fail") } } From 8e4e2bd889e3a3fc16f75808196f6e29a9570a4a Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Fri, 27 Apr 2018 00:06:00 +0300 Subject: [PATCH 3/5] macaroons: add tests for service and constraints --- macaroons/constraints_test.go | 169 +++++++++++++++++----------------- macaroons/service_test.go | 134 +++++++++++++++++++++++++++ 2 files changed, 218 insertions(+), 85 deletions(-) create mode 100644 macaroons/service_test.go diff --git a/macaroons/constraints_test.go b/macaroons/constraints_test.go index 4bd9f552..694d7841 100644 --- a/macaroons/constraints_test.go +++ b/macaroons/constraints_test.go @@ -1,109 +1,108 @@ -package macaroons +package macaroons_test import ( - "errors" - "fmt" "testing" + "github.com/lightningnetwork/lnd/macaroons" + "gopkg.in/macaroon.v2" "time" - - "gopkg.in/macaroon-bakery.v1/bakery" - "gopkg.in/macaroon-bakery.v1/bakery/checkers" - macaroon "gopkg.in/macaroon.v1" + "strings" ) -type macError struct { - message string -} +var ( + testRootKey = []byte("dummyRootKey") + testId = []byte("dummyId") + testLocation = "lnd" + testVersion = macaroon.LatestVersion + expectedTimeCaveatSubstring = "time-before " + string(time.Now().Year()) +) -func (err macError) Error() string { - return err.message -} - -func testConstraint(constraint Constraint, ok checkers.Checker, - failFn func() checkers.Checker) error { - macParams := bakery.NewServiceParams{} - svc, err := bakery.NewService(macParams) +func createDummyMacaroon(t *testing.T) *macaroon.Macaroon { + dummyMacaroon, err := macaroon.New(testRootKey, testId, + testLocation, testVersion) if err != nil { - return errors.New("Failed to create a new service") + t.Fatalf("Error creating initial macaroon: %v", err) } - mac, err := svc.NewMacaroon("", nil, nil) - if err != nil { - return errors.New("Failed to create a new macaroon") - } - - mac, err = AddConstraints(mac, constraint) - if err != nil { - return errors.New("Failed to add macaroon constraint") - } - - okChecker := checkers.New(ok) - if err := svc.Check(macaroon.Slice{mac}, okChecker); err != nil { - msg := "Correct checker failed: %v" - return macError{fmt.Sprintf(msg, ok)} - } - - fail := failFn() - failChecker := checkers.New(fail) - if err := svc.Check(macaroon.Slice{mac}, failChecker); err == nil { - msg := "Incorrect checker succeeded: %v" - return macError{fmt.Sprintf(msg, fail)} - } - return nil + return dummyMacaroon } -func TestAllowConstraint(t *testing.T) { - if err := testConstraint( - AllowConstraint("op1", "op2", "op4"), - AllowChecker("op1"), - func() checkers.Checker { - return AllowChecker("op3") - }, - ); err != nil { - t.Fatalf(err.Error()) +// TestAddConstraints tests that constraints can be added to an existing +// macaroon and therefore tighten its restrictions. +func TestAddConstraints(t *testing.T) { + // We need a dummy macaroon to start with. Create one without + // a bakery, because we mock everything anyway. + initialMac := createDummyMacaroon(t) + + // Now add a constraint and make sure we have a cloned macaroon + // with the constraint applied instead of a mutated initial one. + newMac, err := macaroons.AddConstraints(initialMac, + macaroons.TimeoutConstraint(1)) + if err != nil { + t.Fatalf("Error adding constraint: %v", err) + } + if &newMac == &initialMac { + t.Fatalf("Initial macaroon has been changed, something " + + "went wrong!") + } + + // Finally, test that the constraint has been added. + if len(initialMac.Caveats()) == len(newMac.Caveats()) { + t.Fatalf("No caveat has been added to the macaroon when " + + "constraint was applied") } } +// TestTimeoutConstraint tests that a caveat for the lifetime of +// a macaroon is created. func TestTimeoutConstraint(t *testing.T) { - if err := testConstraint( - TimeoutConstraint(1), - TimeoutChecker(), - func() checkers.Checker { - time.Sleep(time.Second) - return TimeoutChecker() - }, - ); err != nil { - t.Fatalf(err.Error()) + // Get a configured version of the constraint function. + constraintFunc := macaroons.TimeoutConstraint(3) + + // Now we need a dummy macaroon that we can apply the constraint + // function to. + testMacaroon := createDummyMacaroon(t) + err := constraintFunc(testMacaroon) + if err != nil { + t.Fatalf("Error applying timeout constraint: %v", err) + } + + // Finally, check that the created caveat has an + // acceptable value + if strings.HasPrefix(string(testMacaroon.Caveats()[0].Id), + expectedTimeCaveatSubstring) { + t.Fatalf("Added caveat '%s' does not meet the expectations!", + testMacaroon.Caveats()[0].Id) } } -func TestIPLockConstraint(t *testing.T) { - if err := testConstraint( - IPLockConstraint("127.0.0.1"), - IPLockChecker("127.0.0.1"), - func() checkers.Checker { - return IPLockChecker("0.0.0.0") - }, - ); err != nil { - t.Fatalf(err.Error()) - } -} - -func TestIPLockEmptyIP(t *testing.T) { - if err := testConstraint( - IPLockConstraint(""), - IPLockChecker("127.0.0.1"), - func() checkers.Checker { - return IPLockChecker("0.0.0.0") - }, - ); err != nil { - if _, ok := err.(macError); !ok { - t.Fatalf("IPLock with an empty IP should always pass") - } +// TestTimeoutConstraint tests that a caveat for the lifetime of +// a macaroon is created. +func TestIpLockConstraint(t *testing.T) { + // Get a configured version of the constraint function. + constraintFunc := macaroons.IPLockConstraint("127.0.0.1") + + // Now we need a dummy macaroon that we can apply the constraint + // function to. + testMacaroon := createDummyMacaroon(t) + err := constraintFunc(testMacaroon) + if err != nil { + t.Fatalf("Error applying timeout constraint: %v", err) + } + + // Finally, check that the created caveat has an + // acceptable value + if string(testMacaroon.Caveats()[0].Id) != "ipaddr 127.0.0.1" { + t.Fatalf("Added caveat '%s' does not meet the expectations!", + testMacaroon.Caveats()[0].Id) } } +// TestIPLockBadIP tests that an IP constraint cannot be added if the +// provided string is not a valid IP address. func TestIPLockBadIP(t *testing.T) { - if err := IPLockConstraint("127.0.0/800"); err == nil { - t.Fatalf("IPLockConstraint with bad IP should fail") + constraintFunc := macaroons.IPLockConstraint("127.0.0/800"); + testMacaroon := createDummyMacaroon(t) + err := constraintFunc(testMacaroon) + if err == nil { + t.Fatalf("IPLockConstraint with bad IP should fail.") } } diff --git a/macaroons/service_test.go b/macaroons/service_test.go new file mode 100644 index 00000000..23f8b386 --- /dev/null +++ b/macaroons/service_test.go @@ -0,0 +1,134 @@ +package macaroons_test + +import ( + "testing" + "path" + "os" + "context" + "io/ioutil" + "encoding/hex" + + "github.com/coreos/bbolt" + "github.com/lightningnetwork/lnd/macaroons" + "gopkg.in/macaroon-bakery.v2/bakery/checkers" + "gopkg.in/macaroon-bakery.v2/bakery" + "google.golang.org/grpc/metadata" +) + +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) + } + db, err := bolt.Open(path.Join(tempDir, "macaroons.db"), 0600, + bolt.DefaultOptions) + 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) + 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) + defer service.Close() + if err != nil { + t.Fatalf("Error creating new service: %v", err) + } + 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. + macaroon, err := service.Oven.NewMacaroon(nil, bakery.LatestVersion, + nil, testOperation) + 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) { + // First, initialize the service and unlock it + tempDir := setupTestRootKeyStorage(t) + defer os.RemoveAll(tempDir) + service, err := macaroons.NewService(tempDir, macaroons.IPLockChecker) + defer service.Close() + if err != nil { + t.Fatalf("Error creating new service: %v", err) + } + 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. + macaroon, err := service.Oven.NewMacaroon(nil, bakery.LatestVersion, + nil, testOperation) + 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. + md := metadata.New(map[string]string{"macaroon": hex.EncodeToString(macaroonBinary)}) + 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) + } +} From 3eff9804ee812987a36dc8281e63836526563c87 Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Fri, 27 Apr 2018 00:06:29 +0300 Subject: [PATCH 4/5] macaroons: add technical documentation, fix comments --- docs/macaroons.md | 3 +- lnd.go | 5 +-- macaroons/README.md | 80 +++++++++++++++++++++++++++++++++++++++++++++ rpcserver.go | 2 +- 4 files changed, 86 insertions(+), 4 deletions(-) create mode 100644 macaroons/README.md diff --git a/docs/macaroons.md b/docs/macaroons.md index eb5bc507..8b83b81b 100644 --- a/docs/macaroons.md +++ b/docs/macaroons.md @@ -36,7 +36,8 @@ legitimate user. A macaroon is delegated by adding restrictions (called caveats) and an authentication code similar to a signature (technically an HMAC) to it. The technical method of doing this is outside the scope of this overview -documentation, but the macaroon paper linked above describes it quite well. The +documentation, but the [README in the macaroons package](../macaroons/README.md) +or the macaroon paper linked above describe it in more detail. The user must remember several things: * Sharing a macaroon allows anyone in possession of that macaroon to use it to diff --git a/lnd.go b/lnd.go index 0ca432f6..93e4905f 100644 --- a/lnd.go +++ b/lnd.go @@ -791,8 +791,9 @@ func genCertPair(certFile, keyFile string) error { return nil } -// genMacaroons generates a pair of macaroon files; one admin-level and one -// read-only. These can also be used to generate more granular macaroons. +// genMacaroons generates three macaroon files; one admin-level, one +// for invoice access and one read-only. These can also be used +// to generate more granular macaroons. func genMacaroons(ctx context.Context, svc *macaroons.Service, admFile, roFile, invoiceFile string) error { diff --git a/macaroons/README.md b/macaroons/README.md new file mode 100644 index 00000000..bb5df573 --- /dev/null +++ b/macaroons/README.md @@ -0,0 +1,80 @@ +# macaroons + +This is a more detailed, technical description of how macaroons work and how authentication +and authorization is implemented in `lnd`. + +For a more high-level overview see [macaroons.md in the docs](../docs/macaroons.md). + +## Root key + +At startup, if the option `--no-macaroons` is **not** used, a Bolt DB key/value store +named `data/macaroons.db` is created with a bucket named `macrootkeys`. +In this DB the following two key/value pairs are stored: + +* Key `0`: the encrypted root key (32 bytes). + * If the root key does not exist yet, 32 bytes of pseudo-random data is generated and used. +* Key `enckey`: the parameters used to derive a secret encryption key from a passphrase. + * The following parameters are stored: `

` + * `salt`: 32 byte of random data used as salt for the `scrypt` key derivation. + * `digest`: sha256 hashed key derived from the `scrypt` operation. Is used to verify if the + password is correct. + * `N`, `P`, `R`: Parameters used for the `scrypt` operation. + * The root key is symmetrically encrypted with the derived secret key, using the + `secretbox` method of the library [btcsuite/golangcrypto](https://github.com/btcsuite/golangcrypto). + * If the option `--noencryptwallet` is used, then the default passphrase `hello` is used + to encrypt the root key. + +## Generated macaroons + +With the root key set up, `lnd` continues with creating three macaroon files: + +* `invoice.macaroon`: Grants read and write access to all invoice related gRPC + commands (like generating an address or adding an invoice). Can be used for a + web shop application for example. Paying an invoice is not possible, even if + the name might suggest it. The permission `offchain` is needed to pay an + invoice which is currently only granted in the admin macaroon. +* `readonly.macaroon`: Grants read-only access to all gRPC commands. Could be + given to a monitoring application for example. +* `admin.macaroon`: Grants full read and write access to all gRPC commands. + This is used by the `lncli` client. + +These three macaroons all have the location field set to `lnd` and have no conditions/first party caveats +or third party caveats set. + +The access restrictions are implemented with a list of entity/action pairs that is mapped +to the gRPC functions by the `rpcserver.go`. +For example, the permissions for the `invoice.macaroon` looks like this: + +```go + // invoicePermissions is a slice of all the entities that allows a user + // to only access calls that are related to invoices, so: streaming + // RPCs, generating, and listening invoices. + invoicePermissions = []bakery.Op{ + { + Entity: "invoices", + Action: "read", + }, + { + Entity: "invoices", + Action: "write", + }, + { + Entity: "address", + Action: "read", + }, + { + Entity: "address", + Action: "write", + }, + } +``` + +## Constraints / First party caveats + +There are currently two constraints implemented that can be used by `lncli` to restrict the +macaroon it uses to communicate with the gRPC interface. These can be found in `constraints.go`: + +* `TimeoutConstraint`: Set a timeout in seconds after which the macaroon is no longer valid. + This constraint can be set by adding the parameter `--macaroontimeout xy` to the `lncli` command. +* `IPLockConstraint`: Locks the macaroon to a specific IP address. + This constraint can be set by adding the parameter `--macaroonip a.b.c.d` to the `lncli` command. diff --git a/rpcserver.go b/rpcserver.go index f22f091f..6f0d01e1 100644 --- a/rpcserver.go +++ b/rpcserver.go @@ -110,7 +110,7 @@ var ( // invoicePermissions is a slice of all the entities that allows a user // to only access calls that are related to invoices, so: streaming - // RPC's, generating, and listening invoices. + // RPCs, generating, and listening invoices. invoicePermissions = []bakery.Op{ { Entity: "invoices", From f22b0ccdbcfa08e947c6d7181174550c0a9909ed Mon Sep 17 00:00:00 2001 From: Oliver Gugger Date: Fri, 27 Apr 2018 00:06:43 +0300 Subject: [PATCH 5/5] macaroons: fix incorrect comparison in isRegistered, wrap long lines --- macaroons/README.md | 55 +++++++++++++++++++++++---------------- macaroons/service.go | 4 ++- macaroons/service_test.go | 6 +++-- 3 files changed, 39 insertions(+), 26 deletions(-) diff --git a/macaroons/README.md b/macaroons/README.md index bb5df573..87ab93bb 100644 --- a/macaroons/README.md +++ b/macaroons/README.md @@ -1,28 +1,33 @@ # macaroons -This is a more detailed, technical description of how macaroons work and how authentication -and authorization is implemented in `lnd`. +This is a more detailed, technical description of how macaroons work and how +authentication and authorization is implemented in `lnd`. -For a more high-level overview see [macaroons.md in the docs](../docs/macaroons.md). +For a more high-level overview see +[macaroons.md in the docs](../docs/macaroons.md). ## Root key -At startup, if the option `--no-macaroons` is **not** used, a Bolt DB key/value store -named `data/macaroons.db` is created with a bucket named `macrootkeys`. +At startup, if the option `--no-macaroons` is **not** used, a Bolt DB key/value +store named `data/macaroons.db` is created with a bucket named `macrootkeys`. In this DB the following two key/value pairs are stored: * Key `0`: the encrypted root key (32 bytes). - * If the root key does not exist yet, 32 bytes of pseudo-random data is generated and used. -* Key `enckey`: the parameters used to derive a secret encryption key from a passphrase. + * If the root key does not exist yet, 32 bytes of pseudo-random data is + generated and used. +* Key `enckey`: the parameters used to derive a secret encryption key from a + passphrase. * The following parameters are stored: `

` - * `salt`: 32 byte of random data used as salt for the `scrypt` key derivation. - * `digest`: sha256 hashed key derived from the `scrypt` operation. Is used to verify if the - password is correct. + * `salt`: 32 byte of random data used as salt for the `scrypt` key + derivation. + * `digest`: sha256 hashed key derived from the `scrypt` operation. Is used + to verify if the password is correct. * `N`, `P`, `R`: Parameters used for the `scrypt` operation. - * The root key is symmetrically encrypted with the derived secret key, using the - `secretbox` method of the library [btcsuite/golangcrypto](https://github.com/btcsuite/golangcrypto). - * If the option `--noencryptwallet` is used, then the default passphrase `hello` is used - to encrypt the root key. + * The root key is symmetrically encrypted with the derived secret key, using + the `secretbox` method of the library + [btcsuite/golangcrypto](https://github.com/btcsuite/golangcrypto). + * If the option `--noencryptwallet` is used, then the default passphrase + `hello` is used to encrypt the root key. ## Generated macaroons @@ -38,11 +43,11 @@ With the root key set up, `lnd` continues with creating three macaroon files: * `admin.macaroon`: Grants full read and write access to all gRPC commands. This is used by the `lncli` client. -These three macaroons all have the location field set to `lnd` and have no conditions/first party caveats -or third party caveats set. +These three macaroons all have the location field set to `lnd` and have no +conditions/first party caveats or third party caveats set. -The access restrictions are implemented with a list of entity/action pairs that is mapped -to the gRPC functions by the `rpcserver.go`. +The access restrictions are implemented with a list of entity/action pairs that +is mapped to the gRPC functions by the `rpcserver.go`. For example, the permissions for the `invoice.macaroon` looks like this: ```go @@ -71,10 +76,14 @@ For example, the permissions for the `invoice.macaroon` looks like this: ## Constraints / First party caveats -There are currently two constraints implemented that can be used by `lncli` to restrict the -macaroon it uses to communicate with the gRPC interface. These can be found in `constraints.go`: +There are currently two constraints implemented that can be used by `lncli` to +restrict the macaroon it uses to communicate with the gRPC interface. These can +be found in `constraints.go`: -* `TimeoutConstraint`: Set a timeout in seconds after which the macaroon is no longer valid. - This constraint can be set by adding the parameter `--macaroontimeout xy` to the `lncli` command. +* `TimeoutConstraint`: Set a timeout in seconds after which the macaroon is no + longer valid. + This constraint can be set by adding the parameter `--macaroontimeout xy` to + the `lncli` command. * `IPLockConstraint`: Locks the macaroon to a specific IP address. - This constraint can be set by adding the parameter `--macaroonip a.b.c.d` to the `lncli` command. + This constraint can be set by adding the parameter `--macaroonip a.b.c.d` to + the `lncli` command. diff --git a/macaroons/service.go b/macaroons/service.go index 77363f6c..87ebc31a 100644 --- a/macaroons/service.go +++ b/macaroons/service.go @@ -85,7 +85,9 @@ func isRegistered(c *checkers.Checker, name string) bool { } for _, info := range c.Info() { - if info.Name == name && info.Prefix == "std" { + if info.Name == name && + info.Prefix == "" && + info.Namespace == "std" { return true } } diff --git a/macaroons/service_test.go b/macaroons/service_test.go index 23f8b386..599bcb88 100644 --- a/macaroons/service_test.go +++ b/macaroons/service_test.go @@ -97,7 +97,7 @@ func TestNewService(t *testing.T) { // TestValidateMacaroon tests the validation of a macaroon that is in an // incoming context. func TestValidateMacaroon(t *testing.T) { - // First, initialize the service and unlock it + // First, initialize the service and unlock it. tempDir := setupTestRootKeyStorage(t) defer os.RemoveAll(tempDir) service, err := macaroons.NewService(tempDir, macaroons.IPLockChecker) @@ -123,7 +123,9 @@ func TestValidateMacaroon(t *testing.T) { // Because the macaroons are always passed in a context, we need to // mock one that has just the serialized macaroon as a value. - md := metadata.New(map[string]string{"macaroon": hex.EncodeToString(macaroonBinary)}) + md := metadata.New(map[string]string{ + "macaroon": hex.EncodeToString(macaroonBinary), + }) mockContext := metadata.NewIncomingContext(context.Background(), md) // Finally, validate the macaroon against the required permissions.