Merge pull request #10030 from guggero/macaroon-docs

lnrpc+rpcserver: improve docs for CheckMacaroonPermissions
This commit is contained in:
Oliver Gugger authored and GitHub committed 2025-07-21 06:27:31 -06:00
commit 8b8290a64d
6 files changed
+736 -556

No files matched your search

+72
View File
@@ -12,6 +12,7 @@ import (
"github.com/lightningnetwork/lnd/lnrpc"
"github.com/lightningnetwork/lnd/lntest"
"github.com/lightningnetwork/lnd/lntest/node"
"github.com/lightningnetwork/lnd/lntest/wait"
"github.com/lightningnetwork/lnd/macaroons"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
@@ -316,6 +317,77 @@ func testMacaroonAuthentication(ht *lntest.HarnessTest) {
require.Error(t, err)
require.Contains(t, err.Error(), "permission denied")
},
}, {
// Check that with the CheckMacaroonPermissions RPC, we can
// check that a macaroon follows the permissions of a given
// method.
name: "default permissions from full method",
run: func(ctxt context.Context, t *testing.T) {
// We test that the macaroon of the test client has
// all the permissions for calling the BakeMacaroon RPC.
mac, err := testNode.ReadMacaroon(
testNode.Cfg.AdminMacPath, wait.DefaultTimeout,
)
require.NoError(t, err)
macBytes, err := mac.MarshalBinary()
require.NoError(t, err)
rpcURI := "/lnrpc.Lightning/BakeMacaroon"
checkReq := &lnrpc.CheckMacPermRequest{
Macaroon: macBytes,
FullMethod: rpcURI,
CheckDefaultPermsFromFullMethod: true,
}
// Test that CheckMacaroonPermissions accurately
// characterizes macaroon as valid, since the admin
// macaroon should have all the permissions.
checkResp, err := testClient.CheckMacaroonPermissions(
ctxt, checkReq,
)
require.NoError(t, err)
require.Equal(t, checkResp.Valid, true)
// Check different error cases.
dummy := []*lnrpc.MacaroonPermission{{
Entity: "foo",
}}
_, err = testClient.CheckMacaroonPermissions(
ctxt, &lnrpc.CheckMacPermRequest{
Permissions: dummy,
CheckDefaultPermsFromFullMethod: true,
},
)
require.ErrorContains(
t, err, "cannot check default permissions "+
"from full method and from provided "+
"permission list at the same time",
)
_, err = testClient.CheckMacaroonPermissions(
ctxt, &lnrpc.CheckMacPermRequest{
FullMethod: "",
CheckDefaultPermsFromFullMethod: true,
},
)
require.ErrorContains(
t, err, "cannot check default permissions "+
"from full method without providing "+
"the full method name",
)
_, err = testClient.CheckMacaroonPermissions(
ctxt, &lnrpc.CheckMacPermRequest{
FullMethod: "baz",
CheckDefaultPermsFromFullMethod: true,
},
)
require.ErrorContains(
t, err, "no permissions found for full method "+
"baz",
)
},
}}
for _, tc := range testCases {
+579 -542
View File
File diff suppressed because it is too large. Load diff
+32 -3
View File
@@ -548,9 +548,10 @@ service Lightning {
returns (ListPermissionsResponse);
/*
CheckMacaroonPermissions checks whether a request follows the constraints
imposed on the macaroon and that the macaroon is authorized to follow the
provided permissions.
CheckMacaroonPermissions checks whether the provided macaroon contains all
the provided permissions. If the macaroon is valid (e.g. all caveats are
satisfied), and all permissions provided in the request are met, then
this RPC returns true.
*/
rpc CheckMacaroonPermissions (CheckMacPermRequest)
returns (CheckMacPermResponse);
@@ -5107,9 +5108,37 @@ message Op {
}
message CheckMacPermRequest {
// The macaroon to check permissions for, serialized in binary format. For
// a macaroon to be valid, it must have been issued by lnd, must succeed all
// caveat conditions, and must contain all of the permissions specified in
// the permissions field.
bytes macaroon = 1;
// The list of permissions the macaroon should be checked against. Only if
// the macaroon contains all of these permissions, it is considered valid.
// If the list of permissions given is empty, then the macaroon is
// considered valid only based on issuance authority and caveat validity.
// An empty list of permissions is therefore equivalent to saying "skip
// checking permissions" (unless check_default_perms_from_full_method is
// specified).
repeated MacaroonPermission permissions = 2;
// The RPC method to check the macaroon against. This is only used if there
// are custom `uri:<rpcpackage>.<ServiceName>/<MethodName>` permissions in
// the permission list above. To check a macaroon against the list of
// permissions of a certain RPC method, query the `ListPermissions` RPC
// first, extract the permissions for the method, and then pass them in the
// `permissions` field above.
string fullMethod = 3;
// If this field is set to true, then the permissions list above MUST be
// empty. The default permissions for the provided fullMethod will be used
// to check the macaroon. This is equivalent to looking up the permissions
// for a method in the `ListPermissions` RPC and then calling this RPC with
// the permission list returned from that call. Without this flag, the list
// of permissions must be non-empty for the check to actually perform a
// permission check.
bool check_default_perms_from_full_method = 4;
}
message CheckMacPermResponse {
+11 -4
View File
@@ -1991,7 +1991,7 @@
},
"/v1/macaroon/checkpermissions": {
"post": {
"summary": "CheckMacaroonPermissions checks whether a request follows the constraints\nimposed on the macaroon and that the macaroon is authorized to follow the\nprovided permissions.",
"summary": "CheckMacaroonPermissions checks whether the provided macaroon contains all\nthe provided permissions. If the macaroon is valid (e.g. all caveats are\nsatisfied), and all permissions provided in the request are met, then\nthis RPC returns true.",
"operationId": "Lightning_CheckMacaroonPermissions",
"responses": {
"200": {
@@ -4667,17 +4667,24 @@
"properties": {
"macaroon": {
"type": "string",
"format": "byte"
"format": "byte",
"description": "The macaroon to check permissions for, serialized in binary format. For\na macaroon to be valid, it must have been issued by lnd, must succeed all\ncaveat conditions, and must contain all of the permissions specified in\nthe permissions field."
},
"permissions": {
"type": "array",
"items": {
"type": "object",
"$ref": "#/definitions/lnrpcMacaroonPermission"
}
},
"description": "The list of permissions the macaroon should be checked against. Only if\nthe macaroon contains all of these permissions, it is considered valid.\nIf the list of permissions given is empty, then the macaroon is\nconsidered valid only based on issuance authority and caveat validity.\nAn empty list of permissions is therefore equivalent to saying \"skip\nchecking permissions\" (unless check_default_perms_from_full_method is\nspecified)."
},
"fullMethod": {
"type": "string"
"type": "string",
"description": "The RPC method to check the macaroon against. This is only used if there\nare custom `uri:\u003crpcpackage\u003e.\u003cServiceName\u003e/\u003cMethodName\u003e` permissions in\nthe permission list above. To check a macaroon against the list of\npermissions of a certain RPC method, query the `ListPermissions` RPC\nfirst, extract the permissions for the method, and then pass them in the\n`permissions` field above."
},
"check_default_perms_from_full_method": {
"type": "boolean",
"description": "If this field is set to true, then the permissions list above MUST be\nempty. The default permissions for the provided fullMethod will be used\nto check the macaroon. This is equivalent to looking up the permissions\nfor a method in the `ListPermissions` RPC and then calling this RPC with\nthe permission list returned from that call. Without this flag, the list\nof permissions must be non-empty for the check to actually perform a\npermission check."
}
}
},
+8 -6
View File
@@ -384,9 +384,10 @@ type LightningClient interface {
// ListPermissions lists all RPC method URIs and their required macaroon
// permissions to access them.
ListPermissions(ctx context.Context, in *ListPermissionsRequest, opts ...grpc.CallOption) (*ListPermissionsResponse, error)
// CheckMacaroonPermissions checks whether a request follows the constraints
// imposed on the macaroon and that the macaroon is authorized to follow the
// provided permissions.
// CheckMacaroonPermissions checks whether the provided macaroon contains all
// the provided permissions. If the macaroon is valid (e.g. all caveats are
// satisfied), and all permissions provided in the request are met, then
// this RPC returns true.
CheckMacaroonPermissions(ctx context.Context, in *CheckMacPermRequest, opts ...grpc.CallOption) (*CheckMacPermResponse, error)
// RegisterRPCMiddleware adds a new gRPC middleware to the interceptor chain. A
// gRPC middleware is software component external to lnd that aims to add
@@ -1712,9 +1713,10 @@ type LightningServer interface {
// ListPermissions lists all RPC method URIs and their required macaroon
// permissions to access them.
ListPermissions(context.Context, *ListPermissionsRequest) (*ListPermissionsResponse, error)
// CheckMacaroonPermissions checks whether a request follows the constraints
// imposed on the macaroon and that the macaroon is authorized to follow the
// provided permissions.
// CheckMacaroonPermissions checks whether the provided macaroon contains all
// the provided permissions. If the macaroon is valid (e.g. all caveats are
// satisfied), and all permissions provided in the request are met, then
// this RPC returns true.
CheckMacaroonPermissions(context.Context, *CheckMacPermRequest) (*CheckMacPermResponse, error)
// RegisterRPCMiddleware adds a new gRPC middleware to the interceptor chain. A
// gRPC middleware is software component external to lnd that aims to add
+34 -1
View File
@@ -8787,10 +8787,27 @@ func (r *rpcServer) ListPermissions(_ context.Context,
}, nil
}
// CheckMacaroonPermissions checks the caveats and permissions of a macaroon.
// CheckMacaroonPermissions checks whether the provided macaroon contains all
// the provided permissions. If the macaroon is valid (e.g. all caveats are
// satisfied), and all permissions provided in the request are met, then
// this RPC returns true.
func (r *rpcServer) CheckMacaroonPermissions(ctx context.Context,
req *lnrpc.CheckMacPermRequest) (*lnrpc.CheckMacPermResponse, error) {
// Sanity-check the input parameters to eliminate impossible
// combinations.
switch {
case len(req.Permissions) > 0 && req.CheckDefaultPermsFromFullMethod:
return nil, fmt.Errorf("cannot check default permissions " +
"from full method and from provided permission list " +
"at the same time")
case len(req.FullMethod) == 0 && req.CheckDefaultPermsFromFullMethod:
return nil, fmt.Errorf("cannot check default permissions " +
"from full method without providing the full method " +
"name")
}
// Turn grpc macaroon permission into bakery.Op for the server to
// process.
permissions := make([]bakery.Op, len(req.Permissions))
@@ -8801,6 +8818,22 @@ func (r *rpcServer) CheckMacaroonPermissions(ctx context.Context,
}
}
// If the user wants to check the default permissions for the
// full method, then we'll use the interceptor chain to obtain the
// default permissions for the full method. This overwrites the
// user-provided permissions parsed above, but those are required to be
// empty anyway if the flag is turned on.
if req.CheckDefaultPermsFromFullMethod {
allPerms := r.interceptorChain.Permissions()
methodPerms, ok := allPerms[req.FullMethod]
if !ok {
return nil, fmt.Errorf("no permissions found for "+
"full method %s", req.FullMethod)
}
permissions = methodPerms
}
err := r.macService.CheckMacAuth(
ctx, req.Macaroon, permissions, req.FullMethod,
)