github.com/decred/dcrlnd@v0.7.6/lntest/itest/lnd_macaroons_test.go (about)

     1  package itest
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"encoding/hex"
     7  	"os"
     8  	"sort"
     9  	"strconv"
    10  	"testing"
    11  
    12  	"github.com/decred/dcrd/txscript/v4/stdaddr"
    13  	"github.com/decred/dcrlnd/lnrpc"
    14  	"github.com/decred/dcrlnd/lntest"
    15  	"github.com/decred/dcrlnd/macaroons"
    16  	"github.com/stretchr/testify/assert"
    17  	"github.com/stretchr/testify/require"
    18  	"google.golang.org/protobuf/proto"
    19  	"gopkg.in/macaroon.v2"
    20  )
    21  
    22  // testMacaroonAuthentication makes sure that if macaroon authentication is
    23  // enabled on the gRPC interface, no requests with missing or invalid
    24  // macaroons are allowed. Further, the specific access rights (read/write,
    25  // entity based) and first-party caveats are tested as well.
    26  func testMacaroonAuthentication(net *lntest.NetworkHarness, ht *harnessTest) {
    27  	var (
    28  		infoReq    = &lnrpc.GetInfoRequest{}
    29  		newAddrReq = &lnrpc.NewAddressRequest{
    30  			Type: AddrTypePubkeyHash,
    31  		}
    32  		testNode = net.Alice
    33  	)
    34  
    35  	testCases := []struct {
    36  		name string
    37  		run  func(ctxt context.Context, t *testing.T)
    38  	}{{
    39  		// First test: Make sure we get an error if we use no macaroons
    40  		// but try to connect to a node that has macaroon authentication
    41  		// enabled.
    42  		name: "no macaroon",
    43  		run: func(ctxt context.Context, t *testing.T) {
    44  			conn, err := testNode.ConnectRPC(false)
    45  			require.NoError(t, err)
    46  			defer func() { _ = conn.Close() }()
    47  			client := lnrpc.NewLightningClient(conn)
    48  			_, err = client.GetInfo(ctxt, infoReq)
    49  			require.Error(t, err)
    50  			require.Contains(t, err.Error(), "expected 1 macaroon")
    51  		},
    52  	}, {
    53  		// Second test: Ensure that an invalid macaroon also triggers an
    54  		// error.
    55  		name: "invalid macaroon",
    56  		run: func(ctxt context.Context, t *testing.T) {
    57  			invalidMac, _ := macaroon.New(
    58  				[]byte("dummy_root_key"), []byte("0"), "itest",
    59  				macaroon.LatestVersion,
    60  			)
    61  			cleanup, client := macaroonClient(
    62  				t, testNode, invalidMac,
    63  			)
    64  			defer cleanup()
    65  			_, err := client.GetInfo(ctxt, infoReq)
    66  			require.Error(t, err)
    67  			require.Contains(t, err.Error(), "cannot get macaroon")
    68  		},
    69  	}, {
    70  		// Third test: Try to access a write method with read-only
    71  		// macaroon.
    72  		name: "read only macaroon",
    73  		run: func(ctxt context.Context, t *testing.T) {
    74  			readonlyMac, err := testNode.ReadMacaroon(
    75  				testNode.ReadMacPath(), defaultTimeout,
    76  			)
    77  			require.NoError(t, err)
    78  			cleanup, client := macaroonClient(
    79  				t, testNode, readonlyMac,
    80  			)
    81  			defer cleanup()
    82  			_, err = client.NewAddress(ctxt, newAddrReq)
    83  			require.Error(t, err)
    84  			require.Contains(t, err.Error(), "permission denied")
    85  		},
    86  	}, {
    87  		// Fourth test: Check first-party caveat with timeout that
    88  		// expired 30 seconds ago.
    89  		name: "expired macaroon",
    90  		run: func(ctxt context.Context, t *testing.T) {
    91  			readonlyMac, err := testNode.ReadMacaroon(
    92  				testNode.ReadMacPath(), defaultTimeout,
    93  			)
    94  			require.NoError(t, err)
    95  			timeoutMac, err := macaroons.AddConstraints(
    96  				readonlyMac, macaroons.TimeoutConstraint(-30),
    97  			)
    98  			require.NoError(t, err)
    99  			cleanup, client := macaroonClient(
   100  				t, testNode, timeoutMac,
   101  			)
   102  			defer cleanup()
   103  			_, err = client.GetInfo(ctxt, infoReq)
   104  			require.Error(t, err)
   105  			require.Contains(t, err.Error(), "macaroon has expired")
   106  		},
   107  	}, {
   108  		// Fifth test: Check first-party caveat with invalid IP address.
   109  		name: "invalid IP macaroon",
   110  		run: func(ctxt context.Context, t *testing.T) {
   111  			readonlyMac, err := testNode.ReadMacaroon(
   112  				testNode.ReadMacPath(), defaultTimeout,
   113  			)
   114  			require.NoError(t, err)
   115  			invalidIPAddrMac, err := macaroons.AddConstraints(
   116  				readonlyMac, macaroons.IPLockConstraint(
   117  					"1.1.1.1",
   118  				),
   119  			)
   120  			require.NoError(t, err)
   121  			cleanup, client := macaroonClient(
   122  				t, testNode, invalidIPAddrMac,
   123  			)
   124  			defer cleanup()
   125  			_, err = client.GetInfo(ctxt, infoReq)
   126  			require.Error(t, err)
   127  			require.Contains(t, err.Error(), "different IP address")
   128  		},
   129  	}, {
   130  		// Sixth test: Make sure that if we do everything correct and
   131  		// send the admin macaroon with first-party caveats that we can
   132  		// satisfy, we get a correct answer.
   133  		name: "correct macaroon",
   134  		run: func(ctxt context.Context, t *testing.T) {
   135  			adminMac, err := testNode.ReadMacaroon(
   136  				testNode.AdminMacPath(), defaultTimeout,
   137  			)
   138  			require.NoError(t, err)
   139  			adminMac, err = macaroons.AddConstraints(
   140  				adminMac, macaroons.TimeoutConstraint(30),
   141  				macaroons.IPLockConstraint("127.0.0.1"),
   142  			)
   143  			require.NoError(t, err)
   144  			cleanup, client := macaroonClient(t, testNode, adminMac)
   145  			defer cleanup()
   146  			res, err := client.NewAddress(ctxt, newAddrReq)
   147  			require.NoError(t, err, "get new address")
   148  			assert.Contains(t, res.Address, "Ss")
   149  		},
   150  	}, {
   151  		// Seventh test: Bake a macaroon that can only access exactly
   152  		// two RPCs and make sure it works as expected.
   153  		name: "custom URI permissions",
   154  		run: func(ctxt context.Context, t *testing.T) {
   155  			entity := macaroons.PermissionEntityCustomURI
   156  			req := &lnrpc.BakeMacaroonRequest{
   157  				Permissions: []*lnrpc.MacaroonPermission{{
   158  					Entity: entity,
   159  					Action: "/lnrpc.Lightning/GetInfo",
   160  				}, {
   161  					Entity: entity,
   162  					Action: "/lnrpc.Lightning/List" +
   163  						"Permissions",
   164  				}},
   165  			}
   166  			bakeRes, err := testNode.BakeMacaroon(ctxt, req)
   167  			require.NoError(t, err)
   168  
   169  			// Create a connection that uses the custom macaroon.
   170  			customMacBytes, err := hex.DecodeString(
   171  				bakeRes.Macaroon,
   172  			)
   173  			require.NoError(t, err)
   174  			customMac := &macaroon.Macaroon{}
   175  			err = customMac.UnmarshalBinary(customMacBytes)
   176  			require.NoError(t, err)
   177  			cleanup, client := macaroonClient(
   178  				t, testNode, customMac,
   179  			)
   180  			defer cleanup()
   181  
   182  			// Call GetInfo which should succeed.
   183  			_, err = client.GetInfo(ctxt, infoReq)
   184  			require.NoError(t, err)
   185  
   186  			// Call ListPermissions which should also succeed.
   187  			permReq := &lnrpc.ListPermissionsRequest{}
   188  			permRes, err := client.ListPermissions(ctxt, permReq)
   189  			require.NoError(t, err)
   190  			require.Greater(
   191  				t, len(permRes.MethodPermissions), 10,
   192  				"permissions",
   193  			)
   194  
   195  			// Try NewAddress which should be denied.
   196  			_, err = client.NewAddress(ctxt, newAddrReq)
   197  			require.Error(t, err)
   198  			require.Contains(t, err.Error(), "permission denied")
   199  		},
   200  	}, {
   201  		// Eighth test: check that with the CheckMacaroonPermissions
   202  		// RPC, we can check that a macaroon follows (or doesn't)
   203  		// permissions and constraints.
   204  		name: "unknown permissions",
   205  		run: func(ctxt context.Context, t *testing.T) {
   206  			// A test macaroon created with permissions from pool,
   207  			// to make sure CheckMacaroonPermissions RPC accepts
   208  			// them.
   209  			rootKeyID := uint64(4200)
   210  			req := &lnrpc.BakeMacaroonRequest{
   211  				RootKeyId: rootKeyID,
   212  				Permissions: []*lnrpc.MacaroonPermission{{
   213  					Entity: "account",
   214  					Action: "read",
   215  				}, {
   216  					Entity: "recommendation",
   217  					Action: "read",
   218  				}},
   219  				AllowExternalPermissions: true,
   220  			}
   221  			bakeResp, err := testNode.BakeMacaroon(ctxt, req)
   222  			require.NoError(t, err)
   223  
   224  			macBytes, err := hex.DecodeString(bakeResp.Macaroon)
   225  			require.NoError(t, err)
   226  
   227  			checkReq := &lnrpc.CheckMacPermRequest{
   228  				Macaroon:    macBytes,
   229  				Permissions: req.Permissions,
   230  			}
   231  
   232  			// Test that CheckMacaroonPermissions accurately
   233  			// characterizes macaroon as valid, even if the
   234  			// permissions are not native to LND.
   235  			checkResp, err := testNode.CheckMacaroonPermissions(
   236  				ctxt, checkReq,
   237  			)
   238  			require.NoError(t, err)
   239  			require.Equal(t, checkResp.Valid, true)
   240  
   241  			mac, err := readMacaroonFromHex(bakeResp.Macaroon)
   242  			require.NoError(t, err)
   243  
   244  			// Test that CheckMacaroonPermissions responds that the
   245  			// macaroon is invalid if timed out.
   246  			timeoutMac, err := macaroons.AddConstraints(
   247  				mac, macaroons.TimeoutConstraint(-30),
   248  			)
   249  			require.NoError(t, err)
   250  
   251  			timeoutMacBytes, err := timeoutMac.MarshalBinary()
   252  			require.NoError(t, err)
   253  
   254  			checkReq.Macaroon = timeoutMacBytes
   255  
   256  			_, err = testNode.CheckMacaroonPermissions(
   257  				ctxt, checkReq,
   258  			)
   259  			require.Error(t, err)
   260  			require.Contains(t, err.Error(), "macaroon has expired")
   261  
   262  			// Test that CheckMacaroonPermissions labels macaroon
   263  			// input with wrong permissions as invalid.
   264  			wrongPermissions := []*lnrpc.MacaroonPermission{{
   265  				Entity: "invoice",
   266  				Action: "read",
   267  			}}
   268  
   269  			checkReq.Permissions = wrongPermissions
   270  			checkReq.Macaroon = macBytes
   271  
   272  			_, err = testNode.CheckMacaroonPermissions(
   273  				ctxt, checkReq,
   274  			)
   275  			require.Error(t, err)
   276  			require.Contains(t, err.Error(), "permission denied")
   277  		},
   278  	}}
   279  
   280  	for _, tc := range testCases {
   281  		tc := tc
   282  		ht.t.Run(tc.name, func(tt *testing.T) {
   283  			ctxt, cancel := context.WithTimeout(
   284  				context.Background(), defaultTimeout,
   285  			)
   286  			defer cancel()
   287  
   288  			tc.run(ctxt, tt)
   289  		})
   290  	}
   291  }
   292  
   293  // testBakeMacaroon checks that when creating macaroons, the permissions param
   294  // in the request must be set correctly, and the baked macaroon has the intended
   295  // permissions.
   296  func testBakeMacaroon(net *lntest.NetworkHarness, t *harnessTest) {
   297  	var testNode = net.Alice
   298  
   299  	testCases := []struct {
   300  		name string
   301  		run  func(ctxt context.Context, t *testing.T,
   302  			adminClient lnrpc.LightningClient)
   303  	}{{
   304  		// First test: when the permission list is empty in the request,
   305  		// an error should be returned.
   306  		name: "no permission list",
   307  		run: func(ctxt context.Context, t *testing.T,
   308  			adminClient lnrpc.LightningClient) {
   309  
   310  			req := &lnrpc.BakeMacaroonRequest{}
   311  			_, err := adminClient.BakeMacaroon(ctxt, req)
   312  			require.Error(t, err)
   313  			assert.Contains(
   314  				t, err.Error(), "permission list cannot be "+
   315  					"empty",
   316  			)
   317  		},
   318  	}, {
   319  		// Second test: when the action in the permission list is not
   320  		// valid, an error should be returned.
   321  		name: "invalid permission list",
   322  		run: func(ctxt context.Context, t *testing.T,
   323  			adminClient lnrpc.LightningClient) {
   324  
   325  			req := &lnrpc.BakeMacaroonRequest{
   326  				Permissions: []*lnrpc.MacaroonPermission{{
   327  					Entity: "macaroon",
   328  					Action: "invalid123",
   329  				}},
   330  			}
   331  			_, err := adminClient.BakeMacaroon(ctxt, req)
   332  			require.Error(t, err)
   333  			assert.Contains(
   334  				t, err.Error(), "invalid permission action",
   335  			)
   336  		},
   337  	}, {
   338  		// Third test: when the entity in the permission list is not
   339  		// valid, an error should be returned.
   340  		name: "invalid permission entity",
   341  		run: func(ctxt context.Context, t *testing.T,
   342  			adminClient lnrpc.LightningClient) {
   343  
   344  			req := &lnrpc.BakeMacaroonRequest{
   345  				Permissions: []*lnrpc.MacaroonPermission{{
   346  					Entity: "invalid123",
   347  					Action: "read",
   348  				}},
   349  			}
   350  			_, err := adminClient.BakeMacaroon(ctxt, req)
   351  			require.Error(t, err)
   352  			assert.Contains(
   353  				t, err.Error(), "invalid permission entity",
   354  			)
   355  		},
   356  	}, {
   357  		// Fourth test: check that when no root key ID is specified, the
   358  		// default root keyID is used.
   359  		name: "default root key ID",
   360  		run: func(ctxt context.Context, t *testing.T,
   361  			adminClient lnrpc.LightningClient) {
   362  
   363  			req := &lnrpc.BakeMacaroonRequest{
   364  				Permissions: []*lnrpc.MacaroonPermission{{
   365  					Entity: "macaroon",
   366  					Action: "read",
   367  				}},
   368  			}
   369  			_, err := adminClient.BakeMacaroon(ctxt, req)
   370  			require.NoError(t, err)
   371  
   372  			listReq := &lnrpc.ListMacaroonIDsRequest{}
   373  			resp, err := adminClient.ListMacaroonIDs(ctxt, listReq)
   374  			require.NoError(t, err)
   375  			require.Equal(t, resp.RootKeyIds[0], uint64(0))
   376  		},
   377  	}, {
   378  		// Fifth test: create a macaroon use a non-default root key ID.
   379  		name: "custom root key ID",
   380  		run: func(ctxt context.Context, t *testing.T,
   381  			adminClient lnrpc.LightningClient) {
   382  
   383  			rootKeyID := uint64(4200)
   384  			req := &lnrpc.BakeMacaroonRequest{
   385  				RootKeyId: rootKeyID,
   386  				Permissions: []*lnrpc.MacaroonPermission{{
   387  					Entity: "macaroon",
   388  					Action: "read",
   389  				}},
   390  			}
   391  			_, err := adminClient.BakeMacaroon(ctxt, req)
   392  			require.NoError(t, err)
   393  
   394  			listReq := &lnrpc.ListMacaroonIDsRequest{}
   395  			resp, err := adminClient.ListMacaroonIDs(ctxt, listReq)
   396  			require.NoError(t, err)
   397  
   398  			// the ListMacaroonIDs should give a list of two IDs,
   399  			// the default ID 0, and the newly created ID. The
   400  			// returned response is sorted to guarantee the order so
   401  			// that we can compare them one by one.
   402  			sort.Slice(resp.RootKeyIds, func(i, j int) bool {
   403  				return resp.RootKeyIds[i] < resp.RootKeyIds[j]
   404  			})
   405  			require.Equal(t, resp.RootKeyIds[0], uint64(0))
   406  			require.Equal(t, resp.RootKeyIds[1], rootKeyID)
   407  		},
   408  	}, {
   409  		// Sixth test: check the baked macaroon has the intended
   410  		// permissions. It should succeed in reading, and fail to write
   411  		// a macaroon.
   412  		name: "custom macaroon permissions",
   413  		run: func(ctxt context.Context, t *testing.T,
   414  			adminClient lnrpc.LightningClient) {
   415  
   416  			rootKeyID := uint64(4200)
   417  			req := &lnrpc.BakeMacaroonRequest{
   418  				RootKeyId: rootKeyID,
   419  				Permissions: []*lnrpc.MacaroonPermission{{
   420  					Entity: "macaroon",
   421  					Action: "read",
   422  				}},
   423  			}
   424  			bakeResp, err := adminClient.BakeMacaroon(ctxt, req)
   425  			require.NoError(t, err)
   426  
   427  			newMac, err := readMacaroonFromHex(bakeResp.Macaroon)
   428  			require.NoError(t, err)
   429  			cleanup, readOnlyClient := macaroonClient(
   430  				t, testNode, newMac,
   431  			)
   432  			defer cleanup()
   433  
   434  			// BakeMacaroon requires a write permission, so this
   435  			// call should return an error.
   436  			_, err = readOnlyClient.BakeMacaroon(ctxt, req)
   437  			require.Error(t, err)
   438  			require.Contains(t, err.Error(), "permission denied")
   439  
   440  			// ListMacaroon requires a read permission, so this call
   441  			// should succeed.
   442  			listReq := &lnrpc.ListMacaroonIDsRequest{}
   443  			_, err = readOnlyClient.ListMacaroonIDs(ctxt, listReq)
   444  			require.NoError(t, err)
   445  
   446  			// Current macaroon can only work on entity macaroon, so
   447  			// a GetInfo request will fail.
   448  			infoReq := &lnrpc.GetInfoRequest{}
   449  			_, err = readOnlyClient.GetInfo(ctxt, infoReq)
   450  			require.Error(t, err)
   451  			require.Contains(t, err.Error(), "permission denied")
   452  		},
   453  	}, {
   454  		// Seventh test: check that if the allow_external_permissions
   455  		// flag is set, we are able to feed BakeMacaroons permissions
   456  		// that LND is not familiar with.
   457  		name: "allow external macaroon permissions",
   458  		run: func(ctxt context.Context, t *testing.T,
   459  			adminClient lnrpc.LightningClient) {
   460  
   461  			// We'll try permissions from Pool to test that the
   462  			// allow_external_permissions flag properly allows it.
   463  			rootKeyID := uint64(4200)
   464  			req := &lnrpc.BakeMacaroonRequest{
   465  				RootKeyId: rootKeyID,
   466  				Permissions: []*lnrpc.MacaroonPermission{{
   467  					Entity: "account",
   468  					Action: "read",
   469  				}},
   470  				AllowExternalPermissions: true,
   471  			}
   472  
   473  			resp, err := adminClient.BakeMacaroon(ctxt, req)
   474  			require.NoError(t, err)
   475  
   476  			// We'll also check that the external permission was
   477  			// successfully added to the macaroon.
   478  			macBytes, err := hex.DecodeString(resp.Macaroon)
   479  			require.NoError(t, err)
   480  
   481  			mac := &macaroon.Macaroon{}
   482  			err = mac.UnmarshalBinary(macBytes)
   483  			require.NoError(t, err)
   484  
   485  			rawID := mac.Id()
   486  			decodedID := &lnrpc.MacaroonId{}
   487  			idProto := rawID[1:]
   488  			err = proto.Unmarshal(idProto, decodedID)
   489  			require.NoError(t, err)
   490  
   491  			require.Equal(t, "account", decodedID.Ops[0].Entity)
   492  			require.Equal(t, "read", decodedID.Ops[0].Actions[0])
   493  		},
   494  	}}
   495  
   496  	for _, tc := range testCases {
   497  		tc := tc
   498  		t.t.Run(tc.name, func(tt *testing.T) {
   499  			ctxt, cancel := context.WithTimeout(
   500  				context.Background(), defaultTimeout,
   501  			)
   502  			defer cancel()
   503  
   504  			adminMac, err := testNode.ReadMacaroon(
   505  				testNode.AdminMacPath(), defaultTimeout,
   506  			)
   507  			require.NoError(tt, err)
   508  			cleanup, client := macaroonClient(tt, testNode, adminMac)
   509  			defer cleanup()
   510  
   511  			tc.run(ctxt, tt, client)
   512  		})
   513  	}
   514  }
   515  
   516  // testDeleteMacaroonID checks that when deleting a macaroon ID, it removes the
   517  // specified ID and invalidates all macaroons derived from the key with that ID.
   518  // Also, it checks deleting the reserved marcaroon ID, DefaultRootKeyID or is
   519  // forbidden.
   520  func testDeleteMacaroonID(net *lntest.NetworkHarness, t *harnessTest) {
   521  	var (
   522  		ctxb     = context.Background()
   523  		testNode = net.Alice
   524  	)
   525  	ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
   526  	defer cancel()
   527  
   528  	// Use admin macaroon to create a connection.
   529  	adminMac, err := testNode.ReadMacaroon(
   530  		testNode.AdminMacPath(), defaultTimeout,
   531  	)
   532  	require.NoError(t.t, err)
   533  	cleanup, client := macaroonClient(t.t, testNode, adminMac)
   534  	defer cleanup()
   535  
   536  	// Record the number of macaroon IDs before creation.
   537  	listReq := &lnrpc.ListMacaroonIDsRequest{}
   538  	listResp, err := client.ListMacaroonIDs(ctxt, listReq)
   539  	require.NoError(t.t, err)
   540  	numMacIDs := len(listResp.RootKeyIds)
   541  
   542  	// Create macaroons for testing.
   543  	rootKeyIDs := []uint64{1, 2, 3}
   544  	macList := make([]string, 0, len(rootKeyIDs))
   545  	for _, id := range rootKeyIDs {
   546  		req := &lnrpc.BakeMacaroonRequest{
   547  			RootKeyId: id,
   548  			Permissions: []*lnrpc.MacaroonPermission{{
   549  				Entity: "macaroon",
   550  				Action: "read",
   551  			}},
   552  		}
   553  		resp, err := client.BakeMacaroon(ctxt, req)
   554  		require.NoError(t.t, err)
   555  		macList = append(macList, resp.Macaroon)
   556  	}
   557  
   558  	// Check that the creation is successful.
   559  	listReq = &lnrpc.ListMacaroonIDsRequest{}
   560  	listResp, err = client.ListMacaroonIDs(ctxt, listReq)
   561  	require.NoError(t.t, err)
   562  
   563  	// The number of macaroon IDs should be increased by len(rootKeyIDs).
   564  	require.Equal(t.t, numMacIDs+len(rootKeyIDs), len(listResp.RootKeyIds))
   565  
   566  	// First test: check deleting the DefaultRootKeyID returns an error.
   567  	defaultID, _ := strconv.ParseUint(
   568  		string(macaroons.DefaultRootKeyID), 10, 64,
   569  	)
   570  	req := &lnrpc.DeleteMacaroonIDRequest{
   571  		RootKeyId: defaultID,
   572  	}
   573  	_, err = client.DeleteMacaroonID(ctxt, req)
   574  	require.Error(t.t, err)
   575  	require.Contains(
   576  		t.t, err.Error(), macaroons.ErrDeletionForbidden.Error(),
   577  	)
   578  
   579  	// Second test: check deleting the customized ID returns success.
   580  	req = &lnrpc.DeleteMacaroonIDRequest{
   581  		RootKeyId: rootKeyIDs[0],
   582  	}
   583  	resp, err := client.DeleteMacaroonID(ctxt, req)
   584  	require.NoError(t.t, err)
   585  	require.True(t.t, resp.Deleted)
   586  
   587  	// Check that the deletion is successful.
   588  	listReq = &lnrpc.ListMacaroonIDsRequest{}
   589  	listResp, err = client.ListMacaroonIDs(ctxt, listReq)
   590  	require.NoError(t.t, err)
   591  
   592  	// The number of macaroon IDs should be decreased by 1.
   593  	require.Equal(t.t, numMacIDs+len(rootKeyIDs)-1, len(listResp.RootKeyIds))
   594  
   595  	// Check that the deleted macaroon can no longer access macaroon:read.
   596  	deletedMac, err := readMacaroonFromHex(macList[0])
   597  	require.NoError(t.t, err)
   598  	cleanup, client = macaroonClient(t.t, testNode, deletedMac)
   599  	defer cleanup()
   600  
   601  	// Because the macaroon is deleted, it will be treated as an invalid one.
   602  	listReq = &lnrpc.ListMacaroonIDsRequest{}
   603  	_, err = client.ListMacaroonIDs(ctxt, listReq)
   604  	require.Error(t.t, err)
   605  	require.Contains(t.t, err.Error(), "cannot get macaroon")
   606  }
   607  
   608  // testStatelessInit checks that the stateless initialization of the daemon
   609  // does not write any macaroon files to the daemon's file system and returns
   610  // the admin macaroon in the response. It then checks that the password
   611  // change of the wallet can also happen stateless.
   612  func testStatelessInit(net *lntest.NetworkHarness, t *harnessTest) {
   613  	var (
   614  		initPw     = []byte("stateless")
   615  		newPw      = []byte("stateless-new")
   616  		newAddrReq = &lnrpc.NewAddressRequest{
   617  			Type: AddrTypePubkeyHash,
   618  		}
   619  	)
   620  
   621  	// First, create a new node and request it to initialize stateless.
   622  	// This should return us the binary serialized admin macaroon that we
   623  	// can then use for further calls.
   624  	carol, _, macBytes, err := net.NewNodeWithSeed(
   625  		"Carol", nil, initPw, true,
   626  	)
   627  	require.NoError(t.t, err)
   628  	if len(macBytes) == 0 {
   629  		t.Fatalf("invalid macaroon returned in stateless init")
   630  	}
   631  
   632  	// Now make sure no macaroon files have been created by the node Carol.
   633  	_, err = os.Stat(carol.AdminMacPath())
   634  	require.Error(t.t, err)
   635  	_, err = os.Stat(carol.ReadMacPath())
   636  	require.Error(t.t, err)
   637  	_, err = os.Stat(carol.InvoiceMacPath())
   638  	require.Error(t.t, err)
   639  
   640  	// Then check that we can unmarshal the binary serialized macaroon.
   641  	adminMac := &macaroon.Macaroon{}
   642  	err = adminMac.UnmarshalBinary(macBytes)
   643  	require.NoError(t.t, err)
   644  
   645  	// Find out if we can actually use the macaroon that has been returned
   646  	// to us for a RPC call.
   647  	conn, err := carol.ConnectRPCWithMacaroon(adminMac)
   648  	require.NoError(t.t, err)
   649  	defer conn.Close()
   650  	adminMacClient := lnrpc.NewLightningClient(conn)
   651  	ctxt, _ := context.WithTimeout(context.Background(), defaultTimeout)
   652  	res, err := adminMacClient.NewAddress(ctxt, newAddrReq)
   653  	require.NoError(t.t, err)
   654  	if _, err := stdaddr.DecodeAddressV0(res.Address, harnessNetParams); err != nil {
   655  		t.Fatalf("returned address was not a regtest address")
   656  	}
   657  
   658  	if carol.Cfg.RemoteWallet {
   659  		t.Log("Skipping rest of test because RemoteWallet does not support " +
   660  			"changing password")
   661  		return
   662  	}
   663  
   664  	// As a second part, shut down the node and then try to change the
   665  	// password when we start it up again.
   666  	if err := net.RestartNodeNoUnlock(carol, nil, true); err != nil {
   667  		t.Fatalf("Node restart failed: %v", err)
   668  	}
   669  	changePwReq := &lnrpc.ChangePasswordRequest{
   670  		CurrentPassword: initPw,
   671  		NewPassword:     newPw,
   672  		StatelessInit:   true,
   673  	}
   674  	response, err := carol.InitChangePassword(changePwReq)
   675  	require.NoError(t.t, err)
   676  
   677  	// Again, make  sure no macaroon files have been created by the node
   678  	// Carol.
   679  	_, err = os.Stat(carol.AdminMacPath())
   680  	require.Error(t.t, err)
   681  	_, err = os.Stat(carol.ReadMacPath())
   682  	require.Error(t.t, err)
   683  	_, err = os.Stat(carol.InvoiceMacPath())
   684  	require.Error(t.t, err)
   685  
   686  	// Then check that we can unmarshal the new binary serialized macaroon
   687  	// and that it really is a new macaroon.
   688  	if err = adminMac.UnmarshalBinary(response.AdminMacaroon); err != nil {
   689  		t.Fatalf("unable to unmarshal macaroon: %v", err)
   690  	}
   691  	if bytes.Equal(response.AdminMacaroon, macBytes) {
   692  		t.Fatalf("expected new macaroon to be different")
   693  	}
   694  
   695  	// Finally, find out if we can actually use the new macaroon that has
   696  	// been returned to us for a RPC call.
   697  	conn2, err := carol.ConnectRPCWithMacaroon(adminMac)
   698  	require.NoError(t.t, err)
   699  	defer conn2.Close()
   700  	adminMacClient = lnrpc.NewLightningClient(conn2)
   701  
   702  	// Changing the password takes a while, so we use the default timeout
   703  	// of 30 seconds to wait for the connection to be ready.
   704  	ctxt, _ = context.WithTimeout(context.Background(), defaultTimeout)
   705  	res, err = adminMacClient.NewAddress(ctxt, newAddrReq)
   706  	require.NoError(t.t, err)
   707  	if _, err := stdaddr.DecodeAddressV0(res.Address, harnessNetParams); err != nil {
   708  		t.Fatalf("returned address was not a regtest address")
   709  	}
   710  }
   711  
   712  // readMacaroonFromHex loads a macaroon from a hex string.
   713  func readMacaroonFromHex(macHex string) (*macaroon.Macaroon, error) {
   714  	macBytes, err := hex.DecodeString(macHex)
   715  	if err != nil {
   716  		return nil, err
   717  	}
   718  
   719  	mac := &macaroon.Macaroon{}
   720  	if err := mac.UnmarshalBinary(macBytes); err != nil {
   721  		return nil, err
   722  	}
   723  	return mac, nil
   724  }
   725  
   726  func macaroonClient(t *testing.T, testNode *lntest.HarnessNode,
   727  	mac *macaroon.Macaroon) (func(), lnrpc.LightningClient) {
   728  
   729  	conn, err := testNode.ConnectRPCWithMacaroon(mac)
   730  	require.NoError(t, err, "connect to alice")
   731  
   732  	cleanup := func() {
   733  		err := conn.Close()
   734  		require.NoError(t, err, "close")
   735  	}
   736  	return cleanup, lnrpc.NewLightningClient(conn)
   737  }