Merge pull request #1317 from Cyberguru1/custom-session-permissions
Some checks failed
CI / frontend tests on macOS-latest (push) Has been cancelled
CI / frontend tests on ubuntu-latest (push) Has been cancelled
CI / frontend tests on windows-latest (push) Has been cancelled
CI / backend build on macOS-latest (push) Has been cancelled
CI / backend build on ubuntu-latest (push) Has been cancelled
CI / backend build on windows-latest (push) Has been cancelled
CI / cross compilation (push) Has been cancelled
CI / cross compilation-1 (push) Has been cancelled
CI / cross compilation-2 (push) Has been cancelled
CI / RPC proto compilation check (push) Has been cancelled
CI / check commits (push) Has been cancelled
CI / Sqlc check (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / run unit tests (push) Has been cancelled
CI / run unit tests-1 (push) Has been cancelled
CI / run unit tests-2 (push) Has been cancelled
CI / run unit tests-3 (push) Has been cancelled
CI / build itest binaries (push) Has been cancelled
CI / check release notes updated (push) Has been cancelled
CI / integration test (push) Has been cancelled
CI / integration test-1 (push) Has been cancelled
CI / integration test-2 (push) Has been cancelled

sessions: support entity:action format for custom sessions
This commit is contained in:
Viktor Torstensson 2026-06-22 12:38:44 +02:00 committed by GitHub
commit 916ca71d70
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 346 additions and 6 deletions

View file

@ -3,6 +3,7 @@ package main
import (
"encoding/hex"
"fmt"
"strings"
"time"
"github.com/lightninglabs/lightning-terminal/litrpc"
@ -85,6 +86,20 @@ var addSessionCommand = cli.Command{
"For example, '/lnrpc\\..*' will result in " +
"all `lnrpc` permissions being included.",
},
cli.StringSliceFlag{
Name: "permission",
Usage: "A permission that should be included in the " +
"macaroon of a custom session, in the " +
"format entity:action (e.g. info:read), " +
"similar to lncli bakemacaroon. " +
"Note that this flag will only be used if " +
"the 'type' flag is set to 'custom'. " +
"This flag can be specified multiple times, " +
"comma-separated, or mixed (e.g., " +
"'info:read,onchain:write' or " +
"--permission info:read --permission " +
"onchain:write).",
},
cli.StringFlag{
Name: "account_id",
Usage: "The account id that should be used for " +
@ -109,12 +124,12 @@ func addSession(cli *cli.Context) error {
return err
}
var macPerms []*litrpc.MacaroonPermission
for _, uri := range cli.StringSlice("uri") {
macPerms = append(macPerms, &litrpc.MacaroonPermission{
Entity: macaroons.PermissionEntityCustomURI,
Action: uri,
})
macPerms, err := parseCustomPermissions(
cli.StringSlice("uri"),
cli.StringSlice("permission"),
)
if err != nil {
return err
}
sessionLength := time.Second * time.Duration(cli.Uint64("expiry"))
@ -304,3 +319,51 @@ func revokeSession(cli *cli.Context) error {
return nil
}
func parseCustomPermissions(uris, permissions []string) (
[]*litrpc.MacaroonPermission, error) {
var macPerms []*litrpc.MacaroonPermission
for _, uri := range uris {
macPerms = append(macPerms, &litrpc.MacaroonPermission{
Entity: macaroons.PermissionEntityCustomURI,
Action: uri,
})
}
for _, perm := range permissions {
parts := strings.Split(perm, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
subParts := strings.Split(part, ":")
if len(subParts) != 2 {
return nil, fmt.Errorf("invalid permission "+
"format '%s', must be entity:action",
part)
}
entity := strings.TrimSpace(subParts[0])
action := strings.TrimSpace(subParts[1])
if entity == "" || action == "" {
return nil, fmt.Errorf("invalid permission "+
"format '%s', entity and action "+
"must not be empty", part)
}
macPerms = append(macPerms, &litrpc.MacaroonPermission{
Entity: entity,
Action: action,
})
}
}
return macPerms, nil
}

173
cmd/litcli/sessions_test.go Normal file
View file

@ -0,0 +1,173 @@
package main
import (
"testing"
"github.com/lightninglabs/lightning-terminal/litrpc"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/stretchr/testify/require"
)
// TestParseCustomPermissions tests that custom permissions (URIs and
// entity:action pairs) are parsed and validated correctly.
func TestParseCustomPermissions(t *testing.T) {
customURI := macaroons.PermissionEntityCustomURI
tests := []struct {
name string
uris []string
permissions []string
expected []*litrpc.MacaroonPermission
expectErr bool
errContains string
}{
{
name: "valid URIs only",
uris: []string{
"/lnrpc.Lightning/GetInfo",
"/lnrpc.Lightning/UpdateChannelPolicy",
},
permissions: nil,
expected: []*litrpc.MacaroonPermission{
{
Entity: customURI,
Action: "/lnrpc.Lightning/GetInfo",
},
{
Entity: customURI,
Action: "/lnrpc.Lightning/" +
"UpdateChannelPolicy",
},
},
expectErr: false,
},
{
name: "valid permissions only - repeated flags",
uris: nil,
permissions: []string{
"info:read",
"offchain:write",
},
expected: []*litrpc.MacaroonPermission{
{
Entity: "info",
Action: "read",
},
{
Entity: "offchain",
Action: "write",
},
},
expectErr: false,
},
{
name: "valid permissions only - comma separated",
uris: nil,
permissions: []string{
"info:read,offchain:write",
},
expected: []*litrpc.MacaroonPermission{
{
Entity: "info",
Action: "read",
},
{
Entity: "offchain",
Action: "write",
},
},
expectErr: false,
},
{
name: "valid permissions only - mixed with whitespace",
uris: nil,
permissions: []string{
" info:read , offchain:write ",
},
expected: []*litrpc.MacaroonPermission{
{
Entity: "info",
Action: "read",
},
{
Entity: "offchain",
Action: "write",
},
},
expectErr: false,
},
{
name: "combination of URIs and permissions",
uris: []string{
"/lnrpc.Lightning/GetInfo",
},
permissions: []string{
"info:read,offchain:write",
"onchain:read",
},
expected: []*litrpc.MacaroonPermission{
{
Entity: customURI,
Action: "/lnrpc.Lightning/GetInfo",
},
{
Entity: "info",
Action: "read",
},
{
Entity: "offchain",
Action: "write",
},
{
Entity: "onchain",
Action: "read",
},
},
expectErr: false,
},
{
name: "invalid perm - missing colon",
uris: nil,
permissions: []string{"inforead"},
expectErr: true,
errContains: "must be entity:action",
},
{
name: "invalid perm - multiple colons",
uris: nil,
permissions: []string{"info:read:extra"},
expectErr: true,
errContains: "must be entity:action",
},
{
name: "invalid perm - empty entity",
uris: nil,
permissions: []string{":read"},
expectErr: true,
errContains: "entity and action must not be empty",
},
{
name: "invalid perm - empty action",
uris: nil,
permissions: []string{"info:"},
expectErr: true,
errContains: "entity and action must not be empty",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
actual, err := parseCustomPermissions(
tt.uris, tt.permissions,
)
if tt.expectErr {
require.Error(t, err)
require.Contains(t, err.Error(), tt.errContains)
require.Nil(t, actual)
} else {
require.NoError(t, err)
require.Equal(t, tt.expected, actual)
}
})
}
}

View file

@ -85,6 +85,12 @@
`litcli accounts update debit` and `litcli accounts update credit` commands
to modify an account's balance.
* [Add custom permissions support to sessions](https://github.com/lightninglabs/lightning-terminal/pull/1317):
Added the `--permission` flag to `litcli sessions add` to allow specifying
custom `entity:action` permissions (e.g. `info:read`), similar to
`lncli bakemacaroon`, for sessions of type `custom`. Supports repeated flags,
comma-separated lists, and mixed input.
### Technical and Architectural Updates
## RPC Updates

View file

@ -692,6 +692,55 @@ func integratedTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T,
})
}
})
t.Run("lnc auth custom entity action perms", func(tt *testing.T) {
cfg := net.Alice.Cfg
ctx := context.Background()
ctxt, cancel := context.WithTimeout(ctx, defaultTimeout)
defer cancel()
customPerms := []*litrpc.MacaroonPermission{
{
Entity: "info",
Action: "read",
},
}
rawLNCConn := setUpLNCConn(
ctxt, t, 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,
)
})
}
})
}
func uiPasswordAuthCheck(t *testing.T, cfg *LitNodeConfig, subServersDisabled,

View file

@ -259,6 +259,55 @@ func remoteTestSuite(ctx context.Context, net *NetworkHarness, t *testing.T,
}
})
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