multi: support regex URIs

With this commit, a user can now specify a regex when specifying custom
permissions for an LNC session. This regex will be used to select
permissions for URIs that match the regex.
This commit is contained in:
Elle Mouton 2022-11-04 13:46:35 +02:00
parent d41f796f8e
commit 15cd1bdef9
No known key found for this signature in database
GPG key ID: D7D916376026F177
4 changed files with 152 additions and 6 deletions

View file

@ -73,7 +73,11 @@ var addSessionCommand = cli.Command{
"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",
"should be included. Note that a regex can " +
"also be specified which will then result in " +
"all URIs matching the regex to be included. " +
"For example, '/lnrpc\\..*' will result in " +
"all `lnrpc` permissions being included.",
},
},
}

View file

@ -2,6 +2,7 @@ package perms
import (
"net"
"regexp"
"strings"
"sync"
@ -210,6 +211,40 @@ func (pm *Manager) URIPermissions(uri string) ([]bakery.Op, bool) {
return ops, ok
}
// MatchRegexURI first checks that the given URI is in fact a regex. If it is,
// then it is used to match on the perms that the manager has. The return values
// are a list of URIs that match the regex and the boolean represents whether
// the given uri is in fact a regex.
func (pm *Manager) MatchRegexURI(uriRegex string) ([]string, bool) {
pm.permsMu.RLock()
defer pm.permsMu.RUnlock()
// If the given uri string is one of our permissions, then it is not
// a regex.
if _, ok := pm.perms[uriRegex]; ok {
return nil, false
}
// Construct the regex type from the given string.
r, err := regexp.Compile(uriRegex)
if err != nil {
return nil, false
}
// Iterate over the list of permissions and collect all permissions that
// match the given regex.
var matches []string
for uri := range pm.perms {
if !r.MatchString(uri) {
continue
}
matches = append(matches, uri)
}
return matches, true
}
// ActivePermissions returns all the available active permissions that the
// manager is aware of. Optionally, readOnly can be set to true if only the
// read-only permissions should be returned.

81
perms/permissions_test.go Normal file
View file

@ -0,0 +1,81 @@
package perms
import (
"testing"
"github.com/stretchr/testify/require"
"gopkg.in/macaroon-bakery.v2/bakery"
)
// TestMatchRegexURI tests the behaviour of the MatchRegexURI method of the
// Manager.
func TestMatchRegexURI(t *testing.T) {
// Construct a new Manager with a predefined list of perms.
m := &Manager{
perms: map[string][]bakery.Op{
"/lnrpc.WalletUnlocker/GenSeed": {},
"/lnrpc.WalletUnlocker/InitWallet": {},
"/lnrpc.Lightning/SendCoins": {{
Entity: "onchain",
Action: "write",
}},
"/litrpc.Sessions/AddSession": {{
Entity: "sessions",
Action: "write",
}},
"/litrpc.Sessions/ListSessions": {{
Entity: "sessions",
Action: "read",
}},
"/litrpc.Sessions/RevokeSession": {{
Entity: "sessions",
Action: "write",
}},
},
}
// Assert that a full URI is not considered a wild card.
uris, isRegex := m.MatchRegexURI("/litrpc.Sessions/RevokeSession")
require.False(t, isRegex)
require.Empty(t, uris)
// Assert that an invalid URI is also caught as such.
uris, isRegex = m.MatchRegexURI("***")
require.False(t, isRegex)
require.Nil(t, uris)
// Assert that the function correctly matches on a valid wild card for
// litrpc URIs.
uris, isRegex = m.MatchRegexURI("/litrpc.Sessions/.*")
require.True(t, isRegex)
require.ElementsMatch(t, uris, []string{
"/litrpc.Sessions/AddSession",
"/litrpc.Sessions/ListSessions",
"/litrpc.Sessions/RevokeSession",
})
// Assert that the function correctly matches on a valid wild card for
// lnd URIs. First we check that we can specify that only the
// "WalletUnlocker" methods should be included.
uris, isRegex = m.MatchRegexURI("/lnrpc.WalletUnlocker/.*")
require.True(t, isRegex)
require.ElementsMatch(t, uris, []string{
"/lnrpc.WalletUnlocker/GenSeed",
"/lnrpc.WalletUnlocker/InitWallet",
})
// Now we check that we can include all the `lnrpc` methods.
uris, isRegex = m.MatchRegexURI("/lnrpc\\..*")
require.True(t, isRegex)
require.ElementsMatch(t, uris, []string{
"/lnrpc.WalletUnlocker/GenSeed",
"/lnrpc.WalletUnlocker/InitWallet",
"/lnrpc.Lightning/SendCoins",
})
// Assert that the function does not return any URIs for a wild card
// URI that does not match on any of its perms.
uris, isRegex = m.MatchRegexURI("/poolrpc.Trader/.*")
require.True(t, isRegex)
require.Empty(t, uris)
}

View file

@ -143,12 +143,38 @@ func (s *sessionRpcServer) AddSession(_ context.Context,
}
for _, op := range req.MacaroonCustomPermissions {
if op.Entity == macaroons.PermissionEntityCustomURI {
_, ok := s.cfg.permMgr.URIPermissions(op.Action)
if !ok {
return nil, fmt.Errorf("URI %s is "+
"unknown to LiT", op.Action)
if op.Entity != macaroons.PermissionEntityCustomURI {
permissions = append(permissions, bakery.Op{
Entity: op.Entity,
Action: op.Action,
})
continue
}
// First check if this is a regex URI.
uris, isRegex := s.cfg.permMgr.MatchRegexURI(op.Action)
if isRegex {
// This is a regex URI, and so we add each of
// the matching URIs returned from the
// permissions' manager.
for _, uri := range uris {
permissions = append(
permissions, bakery.Op{
Entity: op.Entity,
Action: uri,
},
)
}
continue
}
// This is not a wild card URI, so just check that the
// permissions' manager is aware of this URI.
_, ok := s.cfg.permMgr.URIPermissions(op.Action)
if !ok {
return nil, fmt.Errorf("URI %s is unknown to "+
"LiT", op.Action)
}
permissions = append(permissions, bakery.Op{