macaroons: add special permission entity for URI specific permissions
To make the permission system even more fine-grained, we want to allow users to specify exact gRPC URIs in the macaroon permissions instead of just broad entity/action groups. For this we add the special entity "uri" which allows an URI specific permission to be defined as "uri:/lnrpc.Lightning/GetInfo" for example instead of the more coarse "info:read" which gives access to multiple URIs.
This commit is contained in:
parent
2284d8c775
commit
6d201ef4fc
@ -100,8 +100,18 @@ key `0` would be created with the following command:
|
|||||||
|
|
||||||
`lncli bakemacaroon peers:read peers:write`
|
`lncli bakemacaroon peers:read peers:write`
|
||||||
|
|
||||||
A full and up-to-date list of available entity/action pairs can be found by
|
For even more fine-grained permission control, it is also possible to specify
|
||||||
looking at the `rpcserver.go` in the root folder of the project.
|
single RPC method URIs that are allowed to be accessed by a macaroon. This can
|
||||||
|
be achieved by passing `uri:<methodURI>` pairs to `bakemacaroon`, for example:
|
||||||
|
|
||||||
|
`lncli bakemacaroon uri:/lnrpc.Lightning/GetInfo uri:/verrpc.Versioner/GetVersion`
|
||||||
|
|
||||||
|
The macaroon created by this call would only be allowed to call the `GetInfo` and
|
||||||
|
`GetVersion` methods instead of all methods that have similar permissions (like
|
||||||
|
`info:read` for example).
|
||||||
|
|
||||||
|
A full list of available entity/action pairs and RPC method URIs can be queried
|
||||||
|
by using the `lncli listpermissions` command.
|
||||||
|
|
||||||
### Upgrading from v0.8.0-beta or earlier
|
### Upgrading from v0.8.0-beta or earlier
|
||||||
|
|
||||||
|
@ -27,6 +27,15 @@ var (
|
|||||||
// ErrDeletionForbidden is used when attempting to delete the
|
// ErrDeletionForbidden is used when attempting to delete the
|
||||||
// DefaultRootKeyID or the encryptedKeyID.
|
// DefaultRootKeyID or the encryptedKeyID.
|
||||||
ErrDeletionForbidden = fmt.Errorf("the specified ID cannot be deleted")
|
ErrDeletionForbidden = fmt.Errorf("the specified ID cannot be deleted")
|
||||||
|
|
||||||
|
// PermissionEntityCustomURI is a special entity name for a permission
|
||||||
|
// that does not describe an entity:action pair but instead specifies a
|
||||||
|
// specific URI that needs to be granted access to. This can be used for
|
||||||
|
// more fine-grained permissions where a macaroon only grants access to
|
||||||
|
// certain methods instead of a whole list of methods that define the
|
||||||
|
// same entity:action pairs. For example: uri:/lnrpc.Lightning/GetInfo
|
||||||
|
// only gives access to the GetInfo call.
|
||||||
|
PermissionEntityCustomURI = "uri"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Service encapsulates bakery.Bakery and adds a Close() method that zeroes the
|
// Service encapsulates bakery.Bakery and adds a Close() method that zeroes the
|
||||||
@ -118,12 +127,15 @@ func (svc *Service) UnaryServerInterceptor(
|
|||||||
info *grpc.UnaryServerInfo,
|
info *grpc.UnaryServerInfo,
|
||||||
handler grpc.UnaryHandler) (interface{}, error) {
|
handler grpc.UnaryHandler) (interface{}, error) {
|
||||||
|
|
||||||
if _, ok := permissionMap[info.FullMethod]; !ok {
|
uriPermissions, ok := permissionMap[info.FullMethod]
|
||||||
|
if !ok {
|
||||||
return nil, fmt.Errorf("%s: unknown permissions "+
|
return nil, fmt.Errorf("%s: unknown permissions "+
|
||||||
"required for method", info.FullMethod)
|
"required for method", info.FullMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := svc.ValidateMacaroon(ctx, permissionMap[info.FullMethod])
|
err := svc.ValidateMacaroon(
|
||||||
|
ctx, uriPermissions, info.FullMethod,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -140,13 +152,14 @@ func (svc *Service) StreamServerInterceptor(
|
|||||||
return func(srv interface{}, ss grpc.ServerStream,
|
return func(srv interface{}, ss grpc.ServerStream,
|
||||||
info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
|
||||||
|
|
||||||
if _, ok := permissionMap[info.FullMethod]; !ok {
|
uriPermissions, ok := permissionMap[info.FullMethod]
|
||||||
|
if !ok {
|
||||||
return fmt.Errorf("%s: unknown permissions required "+
|
return fmt.Errorf("%s: unknown permissions required "+
|
||||||
"for method", info.FullMethod)
|
"for method", info.FullMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := svc.ValidateMacaroon(
|
err := svc.ValidateMacaroon(
|
||||||
ss.Context(), permissionMap[info.FullMethod],
|
ss.Context(), uriPermissions, info.FullMethod,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -161,7 +174,7 @@ func (svc *Service) StreamServerInterceptor(
|
|||||||
// expect a macaroon to be encoded as request metadata using the key
|
// expect a macaroon to be encoded as request metadata using the key
|
||||||
// "macaroon".
|
// "macaroon".
|
||||||
func (svc *Service) ValidateMacaroon(ctx context.Context,
|
func (svc *Service) ValidateMacaroon(ctx context.Context,
|
||||||
requiredPermissions []bakery.Op) error {
|
requiredPermissions []bakery.Op, fullMethod string) error {
|
||||||
|
|
||||||
// Get macaroon bytes from context and unmarshal into macaroon.
|
// Get macaroon bytes from context and unmarshal into macaroon.
|
||||||
md, ok := metadata.FromIncomingContext(ctx)
|
md, ok := metadata.FromIncomingContext(ctx)
|
||||||
@ -190,6 +203,20 @@ func (svc *Service) ValidateMacaroon(ctx context.Context,
|
|||||||
// the expiration time and IP address and return the result.
|
// the expiration time and IP address and return the result.
|
||||||
authChecker := svc.Checker.Auth(macaroon.Slice{mac})
|
authChecker := svc.Checker.Auth(macaroon.Slice{mac})
|
||||||
_, err = authChecker.Allow(ctx, requiredPermissions...)
|
_, err = authChecker.Allow(ctx, requiredPermissions...)
|
||||||
|
|
||||||
|
// If the macaroon contains broad permissions and checks out, we're
|
||||||
|
// done.
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// To also allow the special permission of "uri:<FullMethod>" to be a
|
||||||
|
// valid permission, we need to check it manually in case there is no
|
||||||
|
// broader scope permission defined.
|
||||||
|
_, err = authChecker.Allow(ctx, bakery.Op{
|
||||||
|
Entity: PermissionEntityCustomURI,
|
||||||
|
Action: fullMethod,
|
||||||
|
})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -21,6 +21,10 @@ var (
|
|||||||
Entity: "testEntity",
|
Entity: "testEntity",
|
||||||
Action: "read",
|
Action: "read",
|
||||||
}
|
}
|
||||||
|
testOperationURI = bakery.Op{
|
||||||
|
Entity: macaroons.PermissionEntityCustomURI,
|
||||||
|
Action: "SomeMethod",
|
||||||
|
}
|
||||||
defaultPw = []byte("hello")
|
defaultPw = []byte("hello")
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -125,6 +129,7 @@ func TestValidateMacaroon(t *testing.T) {
|
|||||||
// Then, create a new macaroon that we can serialize.
|
// Then, create a new macaroon that we can serialize.
|
||||||
macaroon, err := service.NewMacaroon(
|
macaroon, err := service.NewMacaroon(
|
||||||
context.TODO(), macaroons.DefaultRootKeyID, testOperation,
|
context.TODO(), macaroons.DefaultRootKeyID, testOperation,
|
||||||
|
testOperationURI,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error creating macaroon from service: %v", err)
|
t.Fatalf("Error creating macaroon from service: %v", err)
|
||||||
@ -142,7 +147,18 @@ func TestValidateMacaroon(t *testing.T) {
|
|||||||
mockContext := metadata.NewIncomingContext(context.Background(), md)
|
mockContext := metadata.NewIncomingContext(context.Background(), md)
|
||||||
|
|
||||||
// Finally, validate the macaroon against the required permissions.
|
// Finally, validate the macaroon against the required permissions.
|
||||||
err = service.ValidateMacaroon(mockContext, []bakery.Op{testOperation})
|
err = service.ValidateMacaroon(
|
||||||
|
mockContext, []bakery.Op{testOperation}, "FooMethod",
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Error validating the macaroon: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the macaroon has the method specific URI permission, the list of
|
||||||
|
// required entity/action pairs is irrelevant.
|
||||||
|
err = service.ValidateMacaroon(
|
||||||
|
mockContext, []bakery.Op{{Entity: "irrelevant"}}, "SomeMethod",
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Error validating the macaroon: %v", err)
|
t.Fatalf("Error validating the macaroon: %v", err)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user