macaroons

package
v0.0.0-...-9b6e4cd Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 19, 2022 License: MIT Imports: 18 Imported by: 7

README

macaroons

This is a more detailed, technical description of how macaroons work and how authentication and authorization is implemented in broln.

For a more high-level overview see macaroons.md in the docs.

Root key

At startup, if the option --no-macaroons is not used, a Bolt DB key/value store named data/macaroons.db is created with a bucket named macrootkeys. In this DB the following two key/value pairs are stored:

  • Key 0: the encrypted root key (32 bytes).
    • If the root key does not exist yet, 32 bytes of pseudo-random data is generated and used.
  • Key enckey: the parameters used to derive a secret encryption key from a passphrase.
    • The following parameters are stored: <salt><digest><N><R><P>
      • salt: 32 byte of random data used as salt for the scrypt key derivation.
      • digest: sha256 hashed key derived from the scrypt operation. Is used to verify if the password is correct.
      • N, P, R: Parameters used for the scrypt operation.
    • The root key is symmetrically encrypted with the derived secret key, using the secretbox method of the library brsuite/golangcrypto.
    • If the option --noseedbackup is used, then the default passphrase hello is used to encrypt the root key.

Generated macaroons

With the root key set up, broln continues with creating three macaroon files:

  • invoice.macaroon: Grants read and write access to all invoice related gRPC commands (like generating an address or adding an invoice). Can be used for a web shop application for example. Paying an invoice is not possible, even if the name might suggest it. The permission offchain is needed to pay an invoice which is currently only granted in the admin macaroon.
  • readonly.macaroon: Grants read-only access to all gRPC commands. Could be given to a monitoring application for example.
  • admin.macaroon: Grants full read and write access to all gRPC commands. This is used by the brolncli client.

These three macaroons all have the location field set to broln and have no conditions/first party caveats or third party caveats set.

The access restrictions are implemented with a list of entity/action pairs that is mapped to the gRPC functions by the rpcserver.go. For example, the permissions for the invoice.macaroon looks like this:

	// invoicePermissions is a slice of all the entities that allows a user
	// to only access calls that are related to invoices, so: streaming
	// RPCs, generating, and listening invoices.
	invoicePermissions = []bakery.Op{
		{
			Entity: "invoices",
			Action: "read",
		},
		{
			Entity: "invoices",
			Action: "write",
		},
		{
			Entity: "address",
			Action: "read",
		},
		{
			Entity: "address",
			Action: "write",
		},
	}

Constraints / First party caveats

There are currently two constraints implemented that can be used by brolncli to restrict the macaroon it uses to communicate with the gRPC interface. These can be found in constraints.go:

  • TimeoutConstraint: Set a timeout in seconds after which the macaroon is no longer valid. This constraint can be set by adding the parameter --macaroontimeout xy to the brolncli command.
  • IPLockConstraint: Locks the macaroon to a specific IP address. This constraint can be set by adding the parameter --macaroonip a.b.c.d to the brolncli command.

Bakery

As of broln v0.9.0-beta there is a macaroon bakery available through gRPC and command line. Users can create their own macaroons with custom permissions if the provided default macaroons (admin, invoice and readonly) are not sufficient.

For example, a macaroon that is only allowed to manage peers with a default root key 0 would be created with the following command:

⛰  brolncli bakemacaroon peers:read peers:write

For even more fine-grained permission control, it is also possible to specify 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:

⛰  brolncli 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 brolncli listpermissions command.

Upgrading from v0.8.0-beta or earlier

Users upgrading from a version prior to v0.9.0-beta might get a permission denied error when trying to use the brolncli bakemacaroon command. This is because the bakery requires a new permission (macaroon/generate) to access. Users can obtain a new admin.macaroon that contains this permission by removing all three default macaroons (admin.macaroon, invoice.macaroon and readonly.macaroon, NOT the macaroons.db!) from their data/chain/<chain>/<network>/ directory inside the broln data directory and restarting broln.

Root key rotation

To manage the root keys used by macaroons, there are listmacaroonids and deletemacaroonid available through gPRC and command line. Users can view a list of all macaroon root key IDs that are in use using:

⛰  brolncli listmacaroonids

And remove a specific macaroon root key ID using command:

⛰  brolncli deletemacaroonid root_key_id

Be careful with the deletemacaroonid command as when a root key is deleted, all the macaroons created from it are invalidated.

Documentation

Index

Constants

View Source
const (
	// CondbrolnCustom is the first party caveat condition name that is used
	// for all custom caveats in broln. Every custom caveat entry will be
	// encoded as the string
	// "broln-custom <custom-caveat-name> <custom-caveat-condition>"
	// in the serialized macaroon. We choose a single space as the delimiter
	// between the because that is also used by the macaroon bakery library.
	CondbrolnCustom = "broln-custom"
)
View Source
const (
	// RootKeyLen is the length of a root key.
	RootKeyLen = 32
)

Variables

View Source
var (
	// RootKeyIDContextKey is the key to get rootKeyID from context.
	RootKeyIDContextKey = contextKey{"rootkeyid"}

	// ErrContextRootKeyID is used when the supplied context doesn't have
	// a root key ID.
	ErrContextRootKeyID = fmt.Errorf("failed to read root key ID " +
		"from context")
)
View Source
var (
	// ErrMissingRootKeyID specifies the root key ID is missing.
	ErrMissingRootKeyID = fmt.Errorf("missing root key ID")

	// ErrDeletionForbidden is used when attempting to delete the
	// DefaultRootKeyID or the encryptedKeyID.
	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"
)
View Source
var (

	// DefaultRootKeyID is the ID of the default root key. The first is
	// just 0, to emulate the memory storage that comes with bakery.
	DefaultRootKeyID = []byte("0")

	// ErrAlreadyUnlocked specifies that the store has already been
	// unlocked.
	ErrAlreadyUnlocked = fmt.Errorf("macaroon store already unlocked")

	// ErrStoreLocked specifies that the store needs to be unlocked with
	// a password.
	ErrStoreLocked = fmt.Errorf("macaroon store is locked")

	// ErrPasswordRequired specifies that a nil password has been passed.
	ErrPasswordRequired = fmt.Errorf("a non-nil password is required")

	// ErrKeyValueForbidden is used when the root key ID uses encryptedKeyID as
	// its value.
	ErrKeyValueForbidden = fmt.Errorf("root key ID value is not allowed")

	// ErrRootKeyBucketNotFound specifies that there is no macaroon root key
	// bucket yet which can/should only happen if the store has been
	// corrupted or was initialized incorrectly.
	ErrRootKeyBucketNotFound = fmt.Errorf("root key bucket not found")

	// ErrEncKeyNotFound specifies that there was no encryption key found
	// even if one was expected to be generated.
	ErrEncKeyNotFound = fmt.Errorf("macaroon encryption key not found")
)

Functions

func AddConstraints

func AddConstraints(mac *macaroon.Macaroon,
	cs ...Constraint) (*macaroon.Macaroon, error)

AddConstraints returns new derived macaroon by applying every passed constraint and tightening its restrictions.

func ContextWithRootKeyID

func ContextWithRootKeyID(ctx context.Context,
	value interface{}) context.Context

ContextWithRootKeyID passes the root key ID value to context.

func CustomConstraint

func CustomConstraint(name, condition string) func(*macaroon.Macaroon) error

CustomConstraint returns a function that adds a custom caveat condition to a macaroon.

func GetCustomCaveatCondition

func GetCustomCaveatCondition(mac *macaroon.Macaroon,
	customCaveatName string) string

GetCustomCaveatCondition returns the custom caveat condition for the given custom caveat name from the given macaroon.

func HasCustomCaveat

func HasCustomCaveat(mac *macaroon.Macaroon, customCaveatName string) bool

HasCustomCaveat tests if the given macaroon has a custom caveat with the given custom caveat name.

func IPLockChecker

func IPLockChecker() (string, checkers.Func)

IPLockChecker accepts client IP from the validation context and compares it with IP locked in the macaroon. It is of the `Checker` type.

func IPLockConstraint

func IPLockConstraint(ipAddr string) func(*macaroon.Macaroon) error

IPLockConstraint locks macaroon to a specific IP address. If address is an empty string, this constraint does nothing to accommodate default value's desired behavior.

func RawMacaroonFromContext

func RawMacaroonFromContext(ctx context.Context) (string, error)

RawMacaroonFromContext is a helper function that extracts a raw macaroon from the given incoming gRPC request context.

func RootKeyIDFromContext

func RootKeyIDFromContext(ctx context.Context) ([]byte, error)

RootKeyIDFromContext retrieves the root key ID from context using the key RootKeyIDContextKey.

func SafeCopyMacaroon

func SafeCopyMacaroon(mac *macaroon.Macaroon) (*macaroon.Macaroon, error)

SafeCopyMacaroon creates a copy of a macaroon that is safe to be used and modified. This is necessary because the macaroon library's own Clone() method is unsafe for certain edge cases, resulting in both the cloned and the original macaroons to be modified.

func TimeoutConstraint

func TimeoutConstraint(seconds int64) func(*macaroon.Macaroon) error

TimeoutConstraint restricts the lifetime of the macaroon to the amount of seconds given.

Types

type Checker

type Checker func() (string, checkers.Func)

Checker type adds a layer of indirection over macaroon checkers. A Checker returns the name of the checker and the checker function; these are used to register the function with the bakery service's compound checker.

func CustomChecker

func CustomChecker(acceptor CustomCaveatAcceptor) Checker

CustomChecker returns a Checker function that is used by the macaroon bakery library to check whether a custom caveat is supported by broln in general or not. Support in this context means: An additional gRPC interceptor was set up that validates the content (=condition) of the custom caveat. If such an interceptor is in place then the acceptor should return a nil error. If no interceptor exists for the custom caveat in the macaroon of a request context then a non-nil error should be returned and the macaroon is rejected as a whole.

type Constraint

type Constraint func(*macaroon.Macaroon) error

Constraint type adds a layer of indirection over macaroon caveats.

type CustomCaveatAcceptor

type CustomCaveatAcceptor interface {
	// CustomCaveatSupported returns nil if a macaroon with the given custom
	// caveat name can be validated by any component in broln (for example an
	// RPC middleware). If no component is registered to handle the given
	// custom caveat then an error must be returned. This method only checks
	// the availability of a validating component, not the validity of the
	// macaroon itself.
	CustomCaveatSupported(customCaveatName string) error
}

CustomCaveatAcceptor is an interface that contains a single method for checking whether a macaroon with the given custom caveat name should be accepted or not.

type MacaroonCredential

type MacaroonCredential struct {
	*macaroon.Macaroon
}

MacaroonCredential wraps a macaroon to implement the credentials.PerRPCCredentials interface.

func NewMacaroonCredential

func NewMacaroonCredential(m *macaroon.Macaroon) (MacaroonCredential, error)

NewMacaroonCredential returns a copy of the passed macaroon wrapped in a MacaroonCredential struct which implements PerRPCCredentials.

func (MacaroonCredential) GetRequestMetadata

func (m MacaroonCredential) GetRequestMetadata(ctx context.Context,
	uri ...string) (map[string]string, error)

GetRequestMetadata implements the PerRPCCredentials interface. This method is required in order to pass the wrapped macaroon into the gRPC context. With this, the macaroon will be available within the request handling scope of the ultimate gRPC server implementation.

func (MacaroonCredential) RequireTransportSecurity

func (m MacaroonCredential) RequireTransportSecurity() bool

RequireTransportSecurity implements the PerRPCCredentials interface.

type MacaroonValidator

type MacaroonValidator interface {
	// ValidateMacaroon extracts the macaroon from the context's gRPC
	// metadata, checks its signature, makes sure all specified permissions
	// for the called method are contained within and finally ensures all
	// caveat conditions are met. A non-nil error is returned if any of the
	// checks fail.
	ValidateMacaroon(ctx context.Context,
		requiredPermissions []bakery.Op, fullMethod string) error
}

MacaroonValidator is an interface type that can check if macaroons are valid.

type RootKeyStorage

type RootKeyStorage struct {
	kvdb.Backend
	// contains filtered or unexported fields
}

RootKeyStorage implements the bakery.RootKeyStorage interface.

func NewRootKeyStorage

func NewRootKeyStorage(db kvdb.Backend) (*RootKeyStorage, error)

NewRootKeyStorage creates a RootKeyStorage instance.

func (*RootKeyStorage) ChangePassword

func (r *RootKeyStorage) ChangePassword(oldPw, newPw []byte) error

ChangePassword decrypts the macaroon root key with the old password and then encrypts it again with the new password.

func (*RootKeyStorage) Close

func (r *RootKeyStorage) Close() error

Close closes the underlying database and zeroes the encryption key stored in memory.

func (*RootKeyStorage) CreateUnlock

func (r *RootKeyStorage) CreateUnlock(password *[]byte) error

CreateUnlock sets an encryption key if one is not already set, otherwise it checks if the password is correct for the stored encryption key.

func (*RootKeyStorage) DeleteMacaroonID

func (r *RootKeyStorage) DeleteMacaroonID(
	_ context.Context, rootKeyID []byte) ([]byte, error)

DeleteMacaroonID removes one specific root key ID. If the root key ID is found and deleted, it will be returned.

func (*RootKeyStorage) GenerateNewRootKey

func (r *RootKeyStorage) GenerateNewRootKey() error

GenerateNewRootKey generates a new macaroon root key, replacing the previous root key if it existed.

func (*RootKeyStorage) Get

func (r *RootKeyStorage) Get(_ context.Context, id []byte) ([]byte, error)

Get implements the Get method for the bakery.RootKeyStorage interface.

func (*RootKeyStorage) ListMacaroonIDs

func (r *RootKeyStorage) ListMacaroonIDs(_ context.Context) ([][]byte, error)

ListMacaroonIDs returns all the root key ID values except the value of encryptedKeyID.

func (*RootKeyStorage) RootKey

func (r *RootKeyStorage) RootKey(ctx context.Context) ([]byte, []byte, error)

RootKey implements the RootKey method for the bakery.RootKeyStorage interface.

type Service

type Service struct {
	bakery.Bakery

	// ExternalValidators is a map between an absolute gRPC URIs and the
	// corresponding external macaroon validator to be used for that URI.
	// If no external validator for an URI is specified, the service will
	// use the internal validator.
	ExternalValidators map[string]MacaroonValidator

	// StatelessInit denotes if the service was initialized in the stateless
	// mode where no macaroon files should be created on disk.
	StatelessInit bool
	// contains filtered or unexported fields
}

Service encapsulates bakery.Bakery and adds a Close() method that zeroes the root key service encryption keys, as well as utility methods to validate a macaroon against the bakery and gRPC middleware for macaroon-based auth.

func NewService

func NewService(db kvdb.Backend, location string, statelessInit bool,
	checks ...Checker) (*Service, error)

NewService returns a service backed by the macaroon DB backend. The `checks` argument can be any of the `Checker` type functions defined in this package, or a custom checker if desired. This constructor prevents double-registration of checkers to prevent panics, so listing the same checker more than once is not harmful. Default checkers, such as those for `allow`, `time-before`, `declared`, and `error` caveats are registered automatically and don't need to be added.

func (*Service) ChangePassword

func (svc *Service) ChangePassword(oldPw, newPw []byte) error

ChangePassword calls the underlying root key store's ChangePassword and returns the result.

func (*Service) CheckMacAuth

func (svc *Service) CheckMacAuth(ctx context.Context, macBytes []byte,
	requiredPermissions []bakery.Op, fullMethod string) error

CheckMacAuth checks that the macaroon is not disobeying any caveats and is authorized to perform the operation the user wants to perform.

func (*Service) Close

func (svc *Service) Close() error

Close closes the database that underlies the RootKeyStore and zeroes the encryption keys.

func (*Service) CreateUnlock

func (svc *Service) CreateUnlock(password *[]byte) error

CreateUnlock calls the underlying root key store's CreateUnlock and returns the result.

func (*Service) DeleteMacaroonID

func (svc *Service) DeleteMacaroonID(ctxt context.Context,
	rootKeyID []byte) ([]byte, error)

DeleteMacaroonID removes one specific root key ID. If the root key ID is found and deleted, it will be returned.

func (*Service) GenerateNewRootKey

func (svc *Service) GenerateNewRootKey() error

GenerateNewRootKey calls the underlying root key store's GenerateNewRootKey and returns the result.

func (*Service) ListMacaroonIDs

func (svc *Service) ListMacaroonIDs(ctxt context.Context) ([][]byte, error)

ListMacaroonIDs returns all the root key ID values except the value of encryptedKeyID.

func (*Service) NewMacaroon

func (svc *Service) NewMacaroon(
	ctx context.Context, rootKeyID []byte,
	ops ...bakery.Op) (*bakery.Macaroon, error)

NewMacaroon wraps around the function Oven.NewMacaroon with the defaults,

  • version is always bakery.LatestVersion;
  • caveats is always nil.

In addition, it takes a rootKeyID parameter, and puts it into the context. The context is passed through Oven.NewMacaroon(), in which calls the function RootKey(), that reads the context for rootKeyID.

func (*Service) RegisterExternalValidator

func (svc *Service) RegisterExternalValidator(fullMethod string,
	validator MacaroonValidator) error

RegisterExternalValidator registers a custom, external macaroon validator for the specified absolute gRPC URI. That validator is then fully responsible to make sure any macaroon passed for a request to that URI is valid and satisfies all conditions.

func (*Service) ValidateMacaroon

func (svc *Service) ValidateMacaroon(ctx context.Context,
	requiredPermissions []bakery.Op, fullMethod string) error

ValidateMacaroon validates the capabilities of a given request given a bakery service, context, and uri. Within the passed context.Context, we expect a macaroon to be encoded as request metadata using the key "macaroon".

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL