mirror of
https://github.com/lightningnetwork/lnd.git
synced 2024-11-19 01:43:16 +01:00
251 lines
8.4 KiB
Go
251 lines
8.4 KiB
Go
package macaroons
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
"time"
|
|
|
|
"google.golang.org/grpc/peer"
|
|
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
|
|
macaroon "gopkg.in/macaroon.v2"
|
|
)
|
|
|
|
const (
|
|
// CondLndCustom is the first party caveat condition name that is used
|
|
// for all custom caveats in lnd. Every custom caveat entry will be
|
|
// encoded as the string
|
|
// "lnd-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.
|
|
CondLndCustom = "lnd-custom"
|
|
)
|
|
|
|
// 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 CustomCaveatAcceptor interface {
|
|
// CustomCaveatSupported returns nil if a macaroon with the given custom
|
|
// caveat name can be validated by any component in lnd (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
|
|
}
|
|
|
|
// Constraint type adds a layer of indirection over macaroon caveats.
|
|
type Constraint func(*macaroon.Macaroon) error
|
|
|
|
// 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.
|
|
type Checker func() (string, checkers.Func)
|
|
|
|
// AddConstraints returns new derived macaroon by applying every passed
|
|
// constraint and tightening its restrictions.
|
|
func AddConstraints(mac *macaroon.Macaroon,
|
|
cs ...Constraint) (*macaroon.Macaroon, error) {
|
|
|
|
// The macaroon library's Clone() method has a subtle bug that doesn't
|
|
// correctly clone all caveats. We need to use our own, safe clone
|
|
// function instead.
|
|
newMac, err := SafeCopyMacaroon(mac)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, constraint := range cs {
|
|
if err := constraint(newMac); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
return newMac, nil
|
|
}
|
|
|
|
// Each *Constraint function is a functional option, which takes a pointer
|
|
// to the macaroon and adds another restriction to it. For each *Constraint,
|
|
// the corresponding *Checker is provided if not provided by default.
|
|
|
|
// TimeoutConstraint restricts the lifetime of the macaroon
|
|
// to the amount of seconds given.
|
|
func TimeoutConstraint(seconds int64) func(*macaroon.Macaroon) error {
|
|
return func(mac *macaroon.Macaroon) error {
|
|
macaroonTimeout := time.Duration(seconds)
|
|
requestTimeout := time.Now().Add(time.Second * macaroonTimeout)
|
|
caveat := checkers.TimeBeforeCaveat(requestTimeout)
|
|
return mac.AddFirstPartyCaveat([]byte(caveat.Condition))
|
|
}
|
|
}
|
|
|
|
// 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 IPLockConstraint(ipAddr string) func(*macaroon.Macaroon) error {
|
|
return func(mac *macaroon.Macaroon) error {
|
|
if ipAddr != "" {
|
|
macaroonIPAddr := net.ParseIP(ipAddr)
|
|
if macaroonIPAddr == nil {
|
|
return fmt.Errorf("incorrect macaroon IP-" +
|
|
"lock address")
|
|
}
|
|
caveat := checkers.Condition("ipaddr",
|
|
macaroonIPAddr.String())
|
|
return mac.AddFirstPartyCaveat([]byte(caveat))
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// IPLockChecker accepts client IP from the validation context and compares it
|
|
// with IP locked in the macaroon. It is of the `Checker` type.
|
|
func IPLockChecker() (string, checkers.Func) {
|
|
return "ipaddr", func(ctx context.Context, cond, arg string) error {
|
|
// Get peer info and extract IP address from it for macaroon
|
|
// check.
|
|
pr, ok := peer.FromContext(ctx)
|
|
if !ok {
|
|
return fmt.Errorf("unable to get peer info from context")
|
|
}
|
|
peerAddr, _, err := net.SplitHostPort(pr.Addr.String())
|
|
if err != nil {
|
|
return fmt.Errorf("unable to parse peer address")
|
|
}
|
|
|
|
if !net.ParseIP(arg).Equal(net.ParseIP(peerAddr)) {
|
|
msg := "macaroon locked to different IP address"
|
|
return fmt.Errorf(msg)
|
|
}
|
|
return nil
|
|
}
|
|
}
|
|
|
|
// CustomConstraint returns a function that adds a custom caveat condition to
|
|
// a macaroon.
|
|
func CustomConstraint(name, condition string) func(*macaroon.Macaroon) error {
|
|
return func(mac *macaroon.Macaroon) error {
|
|
// We rely on a name being set for the interception, so don't
|
|
// allow creating a caveat without a name in the first place.
|
|
if name == "" {
|
|
return fmt.Errorf("name cannot be empty")
|
|
}
|
|
|
|
// The inner (custom) condition is optional.
|
|
outerCondition := fmt.Sprintf("%s %s", name, condition)
|
|
if condition == "" {
|
|
outerCondition = name
|
|
}
|
|
|
|
caveat := checkers.Condition(CondLndCustom, outerCondition)
|
|
return mac.AddFirstPartyCaveat([]byte(caveat))
|
|
}
|
|
}
|
|
|
|
// CustomChecker returns a Checker function that is used by the macaroon bakery
|
|
// library to check whether a custom caveat is supported by lnd 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.
|
|
func CustomChecker(acceptor CustomCaveatAcceptor) Checker {
|
|
// We return the general name of all lnd custom macaroons and a function
|
|
// that splits the outer condition to extract the name of the custom
|
|
// condition and the condition itself. In the bakery library that's used
|
|
// here, a caveat always has the following form:
|
|
//
|
|
// <condition-name> <condition-value>
|
|
//
|
|
// Because a checker function needs to be bound to the condition name we
|
|
// have to choose a static name for the first part ("lnd-custom", see
|
|
// CondLndCustom. Otherwise we'd need to register a new Checker function
|
|
// for each custom caveat that's registered. To allow for a generic
|
|
// custom caveat handling, we just add another layer and expand the
|
|
// initial <condition-value> into
|
|
//
|
|
// "<custom-condition-name> <custom-condition-value>"
|
|
//
|
|
// The full caveat string entry of a macaroon that uses this generic
|
|
// mechanism would therefore look like this:
|
|
//
|
|
// "lnd-custom <custom-condition-name> <custom-condition-value>"
|
|
checker := func(_ context.Context, _, outerCondition string) error {
|
|
if outerCondition != strings.TrimSpace(outerCondition) {
|
|
return fmt.Errorf("unexpected white space found in " +
|
|
"caveat condition")
|
|
}
|
|
if outerCondition == "" {
|
|
return fmt.Errorf("expected custom caveat, got empty " +
|
|
"string")
|
|
}
|
|
|
|
// The condition part of the original caveat is now name and
|
|
// condition of the custom caveat (we add a layer of conditions
|
|
// to allow one custom checker to work for all custom lnd
|
|
// conditions that implement arbitrary business logic).
|
|
parts := strings.Split(outerCondition, " ")
|
|
customCaveatName := parts[0]
|
|
|
|
return acceptor.CustomCaveatSupported(customCaveatName)
|
|
}
|
|
|
|
return func() (string, checkers.Func) {
|
|
return CondLndCustom, checker
|
|
}
|
|
}
|
|
|
|
// HasCustomCaveat tests if the given macaroon has a custom caveat with the
|
|
// given custom caveat name.
|
|
func HasCustomCaveat(mac *macaroon.Macaroon, customCaveatName string) bool {
|
|
if mac == nil {
|
|
return false
|
|
}
|
|
|
|
caveatPrefix := []byte(fmt.Sprintf(
|
|
"%s %s", CondLndCustom, customCaveatName,
|
|
))
|
|
for _, caveat := range mac.Caveats() {
|
|
if bytes.HasPrefix(caveat.Id, caveatPrefix) {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
// GetCustomCaveatCondition returns the custom caveat condition for the given
|
|
// custom caveat name from the given macaroon.
|
|
func GetCustomCaveatCondition(mac *macaroon.Macaroon,
|
|
customCaveatName string) string {
|
|
|
|
if mac == nil {
|
|
return ""
|
|
}
|
|
|
|
caveatPrefix := []byte(fmt.Sprintf(
|
|
"%s %s ", CondLndCustom, customCaveatName,
|
|
))
|
|
for _, caveat := range mac.Caveats() {
|
|
// The caveat id has a format of
|
|
// "lnd-custom [custom-caveat-name] [custom-caveat-condition]"
|
|
// and we only want the condition part. If we match the prefix
|
|
// part we return the condition that comes after the prefix.
|
|
if bytes.HasPrefix(caveat.Id, caveatPrefix) {
|
|
caveatSplit := strings.SplitN(
|
|
string(caveat.Id),
|
|
string(caveatPrefix),
|
|
2,
|
|
)
|
|
if len(caveatSplit) == 2 {
|
|
return caveatSplit[1]
|
|
}
|
|
}
|
|
}
|
|
|
|
// We didn't find a condition for the given custom caveat name.
|
|
return ""
|
|
}
|