lightning-terminal/itest/litd_mode_remote_test.go
cyberguru1 484d5d789e itest: add custom permissions integration tests
Add a new sub-test case to verify that custom `entity:action`
permissions are correctly handled and enforced for LNC custom
sessions. The test uses the `info:read` permission to
assert that the connection can only query LND's GetInfo endpoint
and is blocked on other endpoints. Tests are added for both
integrated mode and remote mode suites.
2026-06-22 01:50:56 -05:00

330 lines
8.1 KiB
Go

package itest
import (
"context"
"testing"
"github.com/btcsuite/btcd/btcutil"
"github.com/lightninglabs/lightning-terminal/litrpc"
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/stretchr/testify/require"
)
// testModeIntegrated makes sure that in integrated mode all daemons work
// correctly. It tests the full integrated mode test suite with the ui password
// set and then again with no ui password and a disabled UI.
func testModeRemote(ctx context.Context, net *NetworkHarness,
t *harnessTest) {
testWithAndWithoutUIPassword(ctx, net, t.t, remoteTestSuite, net.Bob)
testDisablingSubServers(ctx, net, t.t, remoteTestSuite, net.Bob)
}
// remoteTestSuite makes sure that in remote mode all daemons work correctly.
func remoteTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T,
withoutUIPassword, subServersDisabled bool, runNum int) {
// Some very basic functionality tests to make sure lnd is working fine
// in remote mode.
net.LNDHarness.FundCoins(btcutil.SatoshiPerBitcoin, net.Bob.RemoteLnd)
// We expect a non-empty alias (truncated node ID) to be returned.
resp, err := net.Bob.GetInfo(ctx, &lnrpc.GetInfoRequest{})
require.NoError(t, err)
require.NotEmpty(t, resp.Alias)
require.Contains(t, resp.Alias, "0")
t.Run("certificate check", func(tt *testing.T) {
cfg := net.Bob.Cfg
// In remote mode we expect the LiT HTTPS port (8443 by default)
// and to have its own certificate
litCerts, err := getServerCertificates(cfg.LitAddr())
require.NoError(tt, err)
require.Len(tt, litCerts, 1)
require.Equal(
tt, "litd autogenerated cert",
litCerts[0].Issuer.Organization[0],
)
})
t.Run("gRPC macaroon auth check", func(tt *testing.T) {
cfg := net.Bob.Cfg
for _, endpoint := range endpoints {
endpoint := endpoint
endpointEnabled := subServersDisabled &&
endpoint.canDisable
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
runGRPCAuthTest(
ttt, cfg.LitAddr(), cfg.LitTLSCertPath,
endpoint.macaroonFn(cfg),
endpoint.noAuth,
endpoint.requestFn,
endpoint.successPattern,
endpointEnabled,
endpoint.disabledPattern,
endpoint.isSubServer,
false,
)
})
}
})
t.Run("UI password auth check", func(tt *testing.T) {
cfg := net.Bob.Cfg
for _, endpoint := range endpoints {
endpoint := endpoint
endpointEnabled := subServersDisabled &&
endpoint.canDisable
shouldFailWithoutMacaroon := false
if withoutUIPassword {
shouldFailWithoutMacaroon = true
}
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
runUIPasswordCheck(
ttt, cfg.LitAddr(), cfg.LitTLSCertPath,
cfg.UIPassword, endpoint.requestFn,
endpoint.noAuth,
shouldFailWithoutMacaroon,
endpoint.successPattern,
endpointEnabled,
endpoint.disabledPattern,
endpoint.isSubServer, false,
)
})
}
})
t.Run("UI index page fallback", func(tt *testing.T) {
runIndexPageCheck(tt, net.Bob.Cfg.LitAddr(), withoutUIPassword)
})
t.Run("grpc-web auth", func(tt *testing.T) {
cfg := net.Bob.Cfg
for _, endpoint := range endpoints {
endpoint := endpoint
endpointEnabled := subServersDisabled &&
endpoint.canDisable
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
runGRPCWebAuthTest(
ttt, cfg.LitAddr(), cfg.UIPassword,
endpoint.grpcWebURI, withoutUIPassword,
endpointEnabled,
endpoint.disabledPattern,
endpoint.noAuth,
)
})
}
})
t.Run("gRPC super macaroon auth check", func(tt *testing.T) {
cfg := net.Bob.Cfg
superMacFile := bakeSuperMacaroon(
tt, cfg, getLiTMacFromFile, true,
)
for _, endpoint := range endpoints {
endpoint := endpoint
endpointEnabled := subServersDisabled &&
endpoint.canDisable
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
runGRPCAuthTest(
ttt, cfg.LitAddr(), cfg.LitTLSCertPath,
superMacFile, endpoint.noAuth,
endpoint.requestFn,
endpoint.successPattern,
endpointEnabled,
endpoint.disabledPattern,
endpoint.isSubServer,
false,
)
})
}
})
t.Run("REST auth", func(tt *testing.T) {
cfg := net.Bob.Cfg
for _, endpoint := range endpoints {
endpoint := endpoint
endpointDisabled := subServersDisabled &&
endpoint.canDisable
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
runRESTAuthTest(
ttt, cfg.LitAddr(), cfg.UIPassword,
endpoint.macaroonFn(cfg),
endpoint.restWebURI,
endpoint.successPattern,
endpoint.restPOST, withoutUIPassword,
endpointDisabled, endpoint.noAuth,
)
})
}
})
t.Run("lnc auth", func(tt *testing.T) {
cfg := net.Bob.Cfg
ctx := context.Background()
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
rawLNCConn := setUpLNCConn(
ctxt, tt, cfg.LitAddr(), cfg.LitTLSCertPath,
cfg.LitMacPath,
litrpc.SessionType_TYPE_MACAROON_READONLY, nil,
)
defer rawLNCConn.Close()
for _, endpoint := range endpoints {
endpoint := endpoint
endpointDisabled := subServersDisabled &&
endpoint.canDisable
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
runLNCAuthTest(
ttt, rawLNCConn, endpoint.requestFn,
endpoint.successPattern,
endpoint.allowedThroughLNC,
"unknown service",
endpointDisabled,
endpoint.disabledPattern,
endpoint.noAuth,
)
})
}
})
t.Run("lnc auth custom mac perms", func(tt *testing.T) {
cfg := net.Bob.Cfg
ctx := context.Background()
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
customPerms := make(
[]*litrpc.MacaroonPermission, 0, len(customURIs),
)
customURIKeyword := macaroons.PermissionEntityCustomURI
for uri := range customURIs {
customPerms = append(
customPerms, &litrpc.MacaroonPermission{
Entity: customURIKeyword,
Action: uri,
},
)
}
rawLNCConn := setUpLNCConn(
ctxt, tt, cfg.LitAddr(), cfg.LitTLSCertPath,
cfg.LitMacPath,
litrpc.SessionType_TYPE_MACAROON_CUSTOM, customPerms,
)
defer rawLNCConn.Close()
for _, endpoint := range endpoints {
endpoint := endpoint
endpointDisabled := subServersDisabled &&
endpoint.canDisable
expectedErr := "permission denied"
if endpoint.noAuth {
expectedErr = "unknown service"
}
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
allowed := customURIs[endpoint.grpcWebURI]
runLNCAuthTest(
ttt, rawLNCConn, endpoint.requestFn,
endpoint.successPattern,
allowed, expectedErr,
endpointDisabled,
endpoint.disabledPattern,
endpoint.noAuth,
)
})
}
})
t.Run("lnc auth custom entity action perms", func(tt *testing.T) {
cfg := net.Bob.Cfg
ctx := context.Background()
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
customPerms := []*litrpc.MacaroonPermission{
{
Entity: "info",
Action: "read",
},
}
rawLNCConn := setUpLNCConn(
ctxt, tt, cfg.LitAddr(), cfg.LitTLSCertPath,
cfg.LitMacPath,
litrpc.SessionType_TYPE_MACAROON_CUSTOM,
customPerms,
)
defer rawLNCConn.Close()
for _, endpoint := range endpoints {
endpoint := endpoint
endpointDisabled := subServersDisabled &&
endpoint.canDisable
expectedErr := "permission denied"
if endpoint.noAuth {
expectedErr = "unknown service"
}
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
// Only lnrpc (GetInfo) is allowed, as we
// only granted the "info:read" permission.
allowed := endpoint.name == "lnrpc"
runLNCAuthTest(
ttt, rawLNCConn, endpoint.requestFn,
endpoint.successPattern,
allowed, expectedErr,
endpointDisabled,
endpoint.disabledPattern,
endpoint.noAuth,
)
})
}
})
t.Run("gRPC super macaroon account system test", func(tt *testing.T) {
cfg := net.Bob.Cfg
// If the accounts service is disabled, we skip this test as it
// will fail due to the accounts service being disabled.
if subServersDisabled {
return
}
superMacFile := bakeSuperMacaroon(
tt, cfg, getLiTMacFromFile, false,
)
ht := newHarnessTest(tt, net)
runAccountSystemTest(
ht, net.Bob, cfg.LitAddr(), cfg.LitTLSCertPath,
superMacFile, runNum,
)
})
}