Skip to content

multi: custom URI sessions #438

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Oct 27, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 27 additions & 6 deletions cmd/litcli/sessions.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"time"

"github.com/lightninglabs/lightning-terminal/litrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/urfave/cli"
)

Expand Down Expand Up @@ -62,9 +63,18 @@ var addSessionCommand = cli.Command{
Usage: "session type to be created which will " +
"determine the permissions a user has when " +
"connecting with the session. Options " +
"include readonly|admin",
"include readonly|admin|custom",
Value: "readonly",
},
cli.StringSliceFlag{
Name: "uri",
Usage: "A URI that should be included in the " +
"macaroon of a custom session. Note that " +
"this flag will only be used if the 'type' " +
"flag is set to 'custom'. This flag can be " +
"specified multiple times if multiple URIs " +
"should be included",
},
},
}

Expand All @@ -87,17 +97,26 @@ func addSession(ctx *cli.Context) error {
return err
}

var macPerms []*litrpc.MacaroonPermission
for _, uri := range ctx.StringSlice("uri") {
macPerms = append(macPerms, &litrpc.MacaroonPermission{
Entity: macaroons.PermissionEntityCustomURI,
Action: uri,
})
}

sessionLength := time.Second * time.Duration(ctx.Uint64("expiry"))
sessionExpiry := time.Now().Add(sessionLength).Unix()

ctxb := context.Background()
resp, err := client.AddSession(
ctxb, &litrpc.AddSessionRequest{
Label: label,
SessionType: sessType,
ExpiryTimestampSeconds: uint64(sessionExpiry),
MailboxServerAddr: ctx.String("mailboxserveraddr"),
DevServer: ctx.Bool("devserver"),
Label: label,
SessionType: sessType,
ExpiryTimestampSeconds: uint64(sessionExpiry),
MailboxServerAddr: ctx.String("mailboxserveraddr"),
DevServer: ctx.Bool("devserver"),
MacaroonCustomPermissions: macPerms,
},
)
if err != nil {
Expand All @@ -115,6 +134,8 @@ func parseSessionType(sessionType string) (litrpc.SessionType, error) {
return litrpc.SessionType_TYPE_MACAROON_ADMIN, nil
case "readonly":
return litrpc.SessionType_TYPE_MACAROON_READONLY, nil
case "custom":
return litrpc.SessionType_TYPE_MACAROON_CUSTOM, nil
default:
return 0, fmt.Errorf("unsupported session type %s", sessionType)
}
Expand Down
144 changes: 106 additions & 38 deletions itest/litd_mode_integrated_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lnrpc/routerrpc"
"github.com/lightningnetwork/lnd/lnrpc/walletrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/stretchr/testify/require"
"golang.org/x/net/http2"
"google.golang.org/grpc"
Expand Down Expand Up @@ -224,6 +225,13 @@ var (
allowedThroughLNC: false,
grpcWebURI: "/litrpc.Sessions/ListSessions",
}}

// customURIs is a map of endpoint URIs that we want to allow via a
// custom-macaroon session type.
customURIs = map[string]bool{
"/lnrpc.Lightning/GetInfo": true,
"/frdrpc.FaradayServer/RevenueReport": true,
}
)

// testModeIntegrated makes sure that in integrated mode all daemons work
Expand Down Expand Up @@ -367,20 +375,109 @@ func testModeIntegrated(net *NetworkHarness, t *harnessTest) {
t.t.Run("lnc auth", func(tt *testing.T) {
cfg := net.Alice.Cfg

ctx := context.Background()
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()

rawLNCConn := setUpLNCConn(
ctxt, t.t, cfg.LitAddr(), cfg.TLSCertPath,
cfg.LitMacPath,
litrpc.SessionType_TYPE_MACAROON_READONLY, nil,
)
defer rawLNCConn.Close()

for _, endpoint := range endpoints {
endpoint := endpoint
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
runLNCAuthTest(
ttt, cfg.LitAddr(), cfg.TLSCertPath,
cfg.LitMacPath, endpoint.requestFn,
ttt, rawLNCConn, endpoint.requestFn,
endpoint.successPattern,
endpoint.allowedThroughLNC,
"unknown service",
)
})
}
})

t.t.Run("lnc auth custom mac perms", func(tt *testing.T) {
cfg := net.Alice.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, t.t, cfg.LitAddr(), cfg.TLSCertPath,
cfg.LitMacPath,
litrpc.SessionType_TYPE_MACAROON_CUSTOM, customPerms,
)
defer rawLNCConn.Close()

for _, endpoint := range endpoints {
endpoint := endpoint
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
allowed := customURIs[endpoint.grpcWebURI]
runLNCAuthTest(
ttt, rawLNCConn, endpoint.requestFn,
endpoint.successPattern,
allowed, "permission denied",
)
})
}
})
}

// setUpLNCConn creates a new LNC session and then creates a connection to that
// session via the mailbox that the session was created with.
func setUpLNCConn(ctx context.Context, t *testing.T, hostPort, tlsCertPath,
macPath string, sessType litrpc.SessionType,
customMacPerms []*litrpc.MacaroonPermission) *grpc.ClientConn {

rawConn, err := connectRPC(ctx, hostPort, tlsCertPath)
require.NoError(t, err)

macBytes, err := ioutil.ReadFile(macPath)
require.NoError(t, err)
ctxm := macaroonContext(ctx, macBytes)

// We first need to create an LNC session that we can use to connect.
litClient := litrpc.NewSessionsClient(rawConn)
sessResp, err := litClient.AddSession(ctxm, &litrpc.AddSessionRequest{
Label: "integration-test",
SessionType: sessType,
ExpiryTimestampSeconds: uint64(
time.Now().Add(5 * time.Minute).Unix(),
),
MailboxServerAddr: mailboxServerAddr,
MacaroonCustomPermissions: customMacPerms,
})
require.NoError(t, err)

// Try the LNC connection now.
connectPhrase := strings.Split(
sessResp.Session.PairingSecretMnemonic, " ",
)

rawLNCConn, err := connectMailbox(ctx, connectPhrase)
require.NoError(t, err)

return rawLNCConn
}

// runCertificateCheck checks that the TLS certificates presented to clients are
// what we expect them to be.
func runCertificateCheck(t *testing.T, node *HarnessNode) {
Expand Down Expand Up @@ -624,44 +721,15 @@ func runRESTAuthTest(t *testing.T, hostPort, uiPassword, macaroonPath, restURI,

// runLNCAuthTest tests authentication of the given interface when connecting
// through Lightning Node Connect.
func runLNCAuthTest(t *testing.T, hostPort, tlsCertPath, macPath string,
makeRequest requestFn, successContent string, callAllowed bool) {

ctxb := context.Background()
ctxt, cancel := context.WithTimeout(ctxb, defaultTimeout)
defer cancel()

rawConn, err := connectRPC(ctxt, hostPort, tlsCertPath)
require.NoError(t, err)
func runLNCAuthTest(t *testing.T, rawLNCConn grpc.ClientConnInterface,
makeRequest requestFn, successContent string, callAllowed bool,
expectErrContains string) {

macBytes, err := ioutil.ReadFile(macPath)
require.NoError(t, err)
ctxm := macaroonContext(ctxt, macBytes)

// We first need to create an LNC session that we can use to connect.
// We use the UI password to create the session.
litClient := litrpc.NewSessionsClient(rawConn)
sessResp, err := litClient.AddSession(ctxm, &litrpc.AddSessionRequest{
Label: "integration-test",
SessionType: litrpc.SessionType_TYPE_MACAROON_READONLY,
ExpiryTimestampSeconds: uint64(
time.Now().Add(5 * time.Minute).Unix(),
),
MailboxServerAddr: mailboxServerAddr,
})
require.NoError(t, err)

// Try the LNC connection now.
connectPhrase := strings.Split(
sessResp.Session.PairingSecretMnemonic, " ",
ctxt, cancel := context.WithTimeout(
context.Background(), defaultTimeout,
)

ctxt, cancel = context.WithTimeout(ctxb, defaultTimeout)
defer cancel()

rawLNCConn, err := connectMailbox(ctxt, connectPhrase)
require.NoError(t, err)

// We should be able to make a request via LNC to the given RPC
// endpoint, unless it is explicitly disallowed (we currently don't want
// to support creating more sessions through LNC until we have all
Expand All @@ -671,7 +739,7 @@ func runLNCAuthTest(t *testing.T, hostPort, tlsCertPath, macPath string,
// Is this a disallowed call?
if !callAllowed {
require.Error(t, err)
require.Contains(t, err.Error(), "unknown service")
require.Contains(t, err.Error(), expectErrContains)

return
}
Expand Down Expand Up @@ -767,7 +835,7 @@ func getServerCertificates(hostPort string) ([]*x509.Certificate, error) {
// connectMailbox tries to establish a connection through LNC using the given
// connect phrase and the test mailbox server.
func connectMailbox(ctx context.Context,
connectPhrase []string) (grpc.ClientConnInterface, error) {
connectPhrase []string) (*grpc.ClientConn, error) {

var mnemonicWords [mailbox.NumPassphraseWords]string
copy(mnemonicWords[:], connectPhrase)
Expand Down
58 changes: 56 additions & 2 deletions itest/litd_mode_remote_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"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"
)

Expand Down Expand Up @@ -134,14 +136,66 @@ func testModeRemote(net *NetworkHarness, t *harnessTest) {
t.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
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
runLNCAuthTest(
ttt, cfg.LitAddr(), cfg.LitTLSCertPath,
cfg.LitMacPath, endpoint.requestFn,
ttt, rawLNCConn, endpoint.requestFn,
endpoint.successPattern,
endpoint.allowedThroughLNC,
"unknown service",
)
})
}
})

t.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
tt.Run(endpoint.name+" lit port", func(ttt *testing.T) {
allowed := customURIs[endpoint.grpcWebURI]
runLNCAuthTest(
ttt, rawLNCConn, endpoint.requestFn,
endpoint.successPattern,
allowed, "permission denied",
)
})
}
Expand Down
Loading