2023-03-07 08:03:42 -08:00
|
|
|
package perms
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"regexp"
|
|
|
|
|
"strings"
|
|
|
|
|
"sync"
|
|
|
|
|
|
|
|
|
|
"github.com/lightningnetwork/lnd"
|
|
|
|
|
"github.com/lightningnetwork/lnd/lnrpc"
|
|
|
|
|
"gopkg.in/macaroon-bakery.v2/bakery"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const (
|
2023-05-02 08:57:35 +02:00
|
|
|
litPerms string = "lit"
|
|
|
|
|
lndPerms string = "lnd"
|
2023-03-07 08:03:42 -08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Manager manages the permission lists that Lit requires.
|
|
|
|
|
type Manager struct {
|
|
|
|
|
// lndSubServerPerms is a map from LND subserver name to permissions
|
|
|
|
|
// map. This is used once the manager receives a list of build tags
|
|
|
|
|
// that LND has been compiled with so that the correct permissions can
|
|
|
|
|
// be extracted based on subservers that LND has been compiled with.
|
|
|
|
|
lndSubServerPerms map[string]map[string][]bakery.Op
|
|
|
|
|
|
|
|
|
|
// fixedPerms is constructed once on creation of the Manager.
|
|
|
|
|
// It contains all the permissions that will not change throughout the
|
|
|
|
|
// lifetime of the manager. It maps sub-server name to uri to permission
|
|
|
|
|
// operations.
|
|
|
|
|
fixedPerms map[string]map[string][]bakery.Op
|
|
|
|
|
|
|
|
|
|
// perms is a map containing all permissions that the manager knows
|
|
|
|
|
// are available for use. This map will start out not including any of
|
|
|
|
|
// lnd's sub-server permissions. Only when the LND build tags are
|
|
|
|
|
// obtained and OnLNDBuildTags is called will this map include the
|
2024-09-13 13:44:29 +02:00
|
|
|
// available LND sub-server permissions.
|
|
|
|
|
perms map[string][]bakery.Op
|
|
|
|
|
|
|
|
|
|
mu sync.RWMutex
|
2023-03-07 08:03:42 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NewManager constructs a new Manager instance and collects any of the
|
|
|
|
|
// fixed permissions. If withAllSubServers is true, then all the LND sub-server
|
|
|
|
|
// permissions will be added to the available permissions set regardless of
|
|
|
|
|
// whether LND was compiled with those sub-servers. If it is not set, however,
|
|
|
|
|
// then OnLNDBuildTags can be used to specify the exact sub-servers that LND
|
|
|
|
|
// was compiled with and then only the corresponding permissions will be added.
|
|
|
|
|
func NewManager(withAllSubServers bool) (*Manager, error) {
|
|
|
|
|
permissions := make(map[string]map[string][]bakery.Op)
|
|
|
|
|
permissions[litPerms] = RequiredPermissions
|
|
|
|
|
permissions[lndPerms] = lnd.MainRPCServerPermissions()
|
2023-08-09 07:45:03 +02:00
|
|
|
|
|
|
|
|
for url := range whiteListedLitMethods {
|
|
|
|
|
permissions[litPerms][url] = []bakery.Op{}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for url := range whiteListedLNDMethods {
|
|
|
|
|
permissions[lndPerms][url] = []bakery.Op{}
|
2023-03-07 08:03:42 -08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Collect all LND sub-server permissions along with the name of the
|
|
|
|
|
// sub-server that each permission is associated with.
|
|
|
|
|
lndSubServerPerms := make(map[string]map[string][]bakery.Op)
|
|
|
|
|
ss := lnrpc.RegisteredSubServers()
|
|
|
|
|
for _, subServer := range ss {
|
|
|
|
|
_, perms, err := subServer.NewGrpcHandler().CreateSubServer(
|
|
|
|
|
&mockConfig{},
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
name := subServer.SubServerName
|
|
|
|
|
lndSubServerPerms[name] = make(map[string][]bakery.Op)
|
|
|
|
|
for key, value := range perms {
|
|
|
|
|
lndSubServerPerms[name][key] = value
|
|
|
|
|
|
|
|
|
|
// If this sub-server is one that we know is
|
|
|
|
|
// automatically compiled in LND then we add it to our
|
|
|
|
|
// map of active permissions. We also add the permission
|
|
|
|
|
// if withAllSubServers is true.
|
|
|
|
|
if withAllSubServers ||
|
|
|
|
|
lndAutoCompiledSubServers[name] {
|
|
|
|
|
|
|
|
|
|
permissions[lndPerms][key] = value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
allPerms := make(map[string][]bakery.Op)
|
|
|
|
|
for _, perms := range permissions {
|
|
|
|
|
for k, v := range perms {
|
|
|
|
|
allPerms[k] = v
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return &Manager{
|
|
|
|
|
lndSubServerPerms: lndSubServerPerms,
|
|
|
|
|
fixedPerms: permissions,
|
|
|
|
|
perms: allPerms,
|
|
|
|
|
}, nil
|
|
|
|
|
}
|
|
|
|
|
|
2023-08-09 07:45:03 +02:00
|
|
|
// IsWhiteListedURL returns true if the given URL has been whitelisted meaning
|
|
|
|
|
// that it does not require a macaroon for validation. A URL is considered
|
|
|
|
|
// white-listed if it has no operations associated with a URL.
|
|
|
|
|
func (pm *Manager) IsWhiteListedURL(url string) bool {
|
2024-09-13 13:44:29 +02:00
|
|
|
pm.mu.Lock()
|
|
|
|
|
defer pm.mu.Unlock()
|
2023-08-09 07:45:03 +02:00
|
|
|
|
|
|
|
|
ops, ok := pm.perms[url]
|
|
|
|
|
|
|
|
|
|
return ok && len(ops) == 0
|
|
|
|
|
}
|
|
|
|
|
|
2023-05-02 08:57:35 +02:00
|
|
|
// RegisterSubServer adds the permissions of a given sub-server to the set
|
|
|
|
|
// managed by the Manager.
|
|
|
|
|
func (pm *Manager) RegisterSubServer(name string,
|
2023-08-09 08:03:28 +02:00
|
|
|
permissions map[string][]bakery.Op, whiteListURLs map[string]struct{}) {
|
2023-05-02 08:57:35 +02:00
|
|
|
|
2024-09-13 13:44:29 +02:00
|
|
|
pm.mu.Lock()
|
|
|
|
|
defer pm.mu.Unlock()
|
2023-05-02 08:57:35 +02:00
|
|
|
|
|
|
|
|
pm.fixedPerms[name] = permissions
|
|
|
|
|
|
|
|
|
|
for uri, ops := range permissions {
|
|
|
|
|
pm.perms[uri] = ops
|
|
|
|
|
}
|
2023-08-09 08:03:28 +02:00
|
|
|
|
|
|
|
|
for url := range whiteListURLs {
|
|
|
|
|
pm.perms[url] = nil
|
|
|
|
|
|
|
|
|
|
if pm.fixedPerms[name] == nil {
|
|
|
|
|
pm.fixedPerms[name] = make(map[string][]bakery.Op)
|
|
|
|
|
}
|
|
|
|
|
pm.fixedPerms[name][url] = []bakery.Op{}
|
|
|
|
|
}
|
2023-05-02 08:57:35 +02:00
|
|
|
}
|
|
|
|
|
|
2023-03-07 08:03:42 -08:00
|
|
|
// OnLNDBuildTags should be called once a list of LND build tags has been
|
|
|
|
|
// obtained. It then uses those build tags to decide which of the LND sub-server
|
|
|
|
|
// permissions to add to the main permissions list. This method should only
|
|
|
|
|
// be called once.
|
|
|
|
|
func (pm *Manager) OnLNDBuildTags(lndBuildTags []string) {
|
2024-09-13 13:44:29 +02:00
|
|
|
pm.mu.Lock()
|
|
|
|
|
defer pm.mu.Unlock()
|
2023-03-07 08:03:42 -08:00
|
|
|
|
|
|
|
|
tagLookup := make(map[string]bool)
|
|
|
|
|
for _, t := range lndBuildTags {
|
|
|
|
|
tagLookup[strings.ToLower(t)] = true
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for subServerName, perms := range pm.lndSubServerPerms {
|
|
|
|
|
name := subServerName
|
|
|
|
|
if tagName, ok := lndSubServerNameToTag[name]; ok {
|
|
|
|
|
name = tagName
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if !tagLookup[strings.ToLower(name)] {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for key, value := range perms {
|
|
|
|
|
pm.perms[key] = value
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// URIPermissions returns a list of permission operations for the given URI if
|
|
|
|
|
// the uri is known to the manager. The second return parameter will be false
|
|
|
|
|
// if the URI is unknown to the manager.
|
|
|
|
|
func (pm *Manager) URIPermissions(uri string) ([]bakery.Op, bool) {
|
2024-09-13 13:44:29 +02:00
|
|
|
pm.mu.RLock()
|
|
|
|
|
defer pm.mu.RUnlock()
|
2023-03-07 08:03:42 -08:00
|
|
|
|
|
|
|
|
ops, ok := pm.perms[uri]
|
|
|
|
|
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) {
|
2024-09-13 13:44:29 +02:00
|
|
|
pm.mu.RLock()
|
|
|
|
|
defer pm.mu.RUnlock()
|
2023-03-07 08:03:42 -08:00
|
|
|
|
|
|
|
|
// 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.
|
|
|
|
|
func (pm *Manager) ActivePermissions(readOnly bool) []bakery.Op {
|
2024-09-13 13:44:29 +02:00
|
|
|
pm.mu.RLock()
|
|
|
|
|
defer pm.mu.RUnlock()
|
2023-03-07 08:03:42 -08:00
|
|
|
|
|
|
|
|
// De-dup the permissions and optionally apply the read-only filter.
|
|
|
|
|
dedupMap := make(map[string]map[string]bool)
|
|
|
|
|
for _, methodPerms := range pm.perms {
|
|
|
|
|
for _, methodPerm := range methodPerms {
|
|
|
|
|
if methodPerm.Action == "" || methodPerm.Entity == "" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if readOnly && methodPerm.Action != "read" {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if dedupMap[methodPerm.Entity] == nil {
|
|
|
|
|
dedupMap[methodPerm.Entity] = make(
|
|
|
|
|
map[string]bool,
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
dedupMap[methodPerm.Entity][methodPerm.Action] = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
result := make([]bakery.Op, 0, len(dedupMap))
|
|
|
|
|
for entity, actions := range dedupMap {
|
|
|
|
|
for action := range actions {
|
|
|
|
|
result = append(result, bakery.Op{
|
|
|
|
|
Entity: entity,
|
|
|
|
|
Action: action,
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// GetLitPerms returns a map of all permissions that the manager is aware of
|
|
|
|
|
// _except_ for any LND permissions. In other words, this returns permissions
|
|
|
|
|
// for which the external validator of Lit is responsible.
|
|
|
|
|
func (pm *Manager) GetLitPerms() map[string][]bakery.Op {
|
2024-09-13 13:44:29 +02:00
|
|
|
pm.mu.Lock()
|
|
|
|
|
defer pm.mu.Unlock()
|
|
|
|
|
|
2023-05-02 08:57:35 +02:00
|
|
|
result := make(map[string][]bakery.Op)
|
|
|
|
|
for subserver, ops := range pm.fixedPerms {
|
|
|
|
|
if subserver == lndPerms {
|
|
|
|
|
continue
|
|
|
|
|
}
|
2023-03-07 08:03:42 -08:00
|
|
|
|
2023-05-02 08:57:35 +02:00
|
|
|
for key, value := range ops {
|
|
|
|
|
result[key] = value
|
|
|
|
|
}
|
2023-03-07 08:03:42 -08:00
|
|
|
}
|
|
|
|
|
return result
|
|
|
|
|
}
|
|
|
|
|
|
2023-03-07 08:33:06 -08:00
|
|
|
// IsSubServerURI if the given URI belongs to the RPC of the given server.
|
|
|
|
|
func (pm *Manager) IsSubServerURI(name string, uri string) bool {
|
2024-09-13 13:44:29 +02:00
|
|
|
pm.mu.Lock()
|
|
|
|
|
defer pm.mu.Unlock()
|
|
|
|
|
|
2023-03-07 08:33:06 -08:00
|
|
|
if name == lndPerms {
|
|
|
|
|
return pm.isLndURI(uri)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
_, ok := pm.fixedPerms[name][uri]
|
|
|
|
|
return ok
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// isLndURI returns true if the given URI belongs to an RPC of lnd.
|
|
|
|
|
func (pm *Manager) isLndURI(uri string) bool {
|
2023-03-07 08:03:42 -08:00
|
|
|
var lndSubServerCall bool
|
|
|
|
|
for _, subserverPermissions := range pm.lndSubServerPerms {
|
|
|
|
|
_, found := subserverPermissions[uri]
|
|
|
|
|
if found {
|
|
|
|
|
lndSubServerCall = true
|
|
|
|
|
break
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
_, lndCall := pm.fixedPerms[lndPerms][uri]
|
|
|
|
|
return lndCall || lndSubServerCall
|
|
|
|
|
}
|