github.com/decred/dcrlnd@v0.7.6/macaroons/service.go (about)

     1  package macaroons
     2  
     3  import (
     4  	"context"
     5  	"encoding/hex"
     6  	"fmt"
     7  
     8  	"github.com/decred/dcrlnd/kvdb"
     9  	"google.golang.org/grpc/metadata"
    10  	"gopkg.in/macaroon-bakery.v2/bakery"
    11  	"gopkg.in/macaroon-bakery.v2/bakery/checkers"
    12  	macaroon "gopkg.in/macaroon.v2"
    13  )
    14  
    15  var (
    16  	// ErrMissingRootKeyID specifies the root key ID is missing.
    17  	ErrMissingRootKeyID = fmt.Errorf("missing root key ID")
    18  
    19  	// ErrDeletionForbidden is used when attempting to delete the
    20  	// DefaultRootKeyID or the encryptedKeyID.
    21  	ErrDeletionForbidden = fmt.Errorf("the specified ID cannot be deleted")
    22  
    23  	// PermissionEntityCustomURI is a special entity name for a permission
    24  	// that does not describe an entity:action pair but instead specifies a
    25  	// specific URI that needs to be granted access to. This can be used for
    26  	// more fine-grained permissions where a macaroon only grants access to
    27  	// certain methods instead of a whole list of methods that define the
    28  	// same entity:action pairs. For example: uri:/lnrpc.Lightning/GetInfo
    29  	// only gives access to the GetInfo call.
    30  	PermissionEntityCustomURI = "uri"
    31  )
    32  
    33  // MacaroonValidator is an interface type that can check if macaroons are valid.
    34  type MacaroonValidator interface {
    35  	// ValidateMacaroon extracts the macaroon from the context's gRPC
    36  	// metadata, checks its signature, makes sure all specified permissions
    37  	// for the called method are contained within and finally ensures all
    38  	// caveat conditions are met. A non-nil error is returned if any of the
    39  	// checks fail.
    40  	ValidateMacaroon(ctx context.Context,
    41  		requiredPermissions []bakery.Op, fullMethod string) error
    42  }
    43  
    44  // Service encapsulates bakery.Bakery and adds a Close() method that zeroes the
    45  // root key service encryption keys, as well as utility methods to validate a
    46  // macaroon against the bakery and gRPC middleware for macaroon-based auth.
    47  type Service struct {
    48  	bakery.Bakery
    49  
    50  	rks *RootKeyStorage
    51  
    52  	// ExternalValidators is a map between an absolute gRPC URIs and the
    53  	// corresponding external macaroon validator to be used for that URI.
    54  	// If no external validator for an URI is specified, the service will
    55  	// use the internal validator.
    56  	ExternalValidators map[string]MacaroonValidator
    57  
    58  	// StatelessInit denotes if the service was initialized in the stateless
    59  	// mode where no macaroon files should be created on disk.
    60  	StatelessInit bool
    61  }
    62  
    63  // NewService returns a service backed by the macaroon DB backend. The `checks`
    64  // argument can be any of the `Checker` type functions defined in this package,
    65  // or a custom checker if desired. This constructor prevents double-registration
    66  // of checkers to prevent panics, so listing the same checker more than once is
    67  // not harmful. Default checkers, such as those for `allow`, `time-before`,
    68  // `declared`, and `error` caveats are registered automatically and don't need
    69  // to be added.
    70  func NewService(db kvdb.Backend, location string, statelessInit bool,
    71  	checks ...Checker) (*Service, error) {
    72  
    73  	rootKeyStore, err := NewRootKeyStorage(db)
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	macaroonParams := bakery.BakeryParams{
    79  		Location:     location,
    80  		RootKeyStore: rootKeyStore,
    81  		// No third-party caveat support for now.
    82  		// TODO(aakselrod): Add third-party caveat support.
    83  		Locator: nil,
    84  		Key:     nil,
    85  	}
    86  
    87  	svc := bakery.New(macaroonParams)
    88  
    89  	// Register all custom caveat checkers with the bakery's checker.
    90  	// TODO(aakselrod): Add more checks as required.
    91  	checker := svc.Checker.FirstPartyCaveatChecker.(*checkers.Checker)
    92  	for _, check := range checks {
    93  		cond, fun := check()
    94  		if !isRegistered(checker, cond) {
    95  			checker.Register(cond, "std", fun)
    96  		}
    97  	}
    98  
    99  	return &Service{
   100  		Bakery:             *svc,
   101  		rks:                rootKeyStore,
   102  		ExternalValidators: make(map[string]MacaroonValidator),
   103  		StatelessInit:      statelessInit,
   104  	}, nil
   105  }
   106  
   107  // isRegistered checks to see if the required checker has already been
   108  // registered in order to avoid a panic caused by double registration.
   109  func isRegistered(c *checkers.Checker, name string) bool {
   110  	if c == nil {
   111  		return false
   112  	}
   113  
   114  	for _, info := range c.Info() {
   115  		if info.Name == name &&
   116  			info.Prefix == "" &&
   117  			info.Namespace == "std" {
   118  			return true
   119  		}
   120  	}
   121  
   122  	return false
   123  }
   124  
   125  // RegisterExternalValidator registers a custom, external macaroon validator for
   126  // the specified absolute gRPC URI. That validator is then fully responsible to
   127  // make sure any macaroon passed for a request to that URI is valid and
   128  // satisfies all conditions.
   129  func (svc *Service) RegisterExternalValidator(fullMethod string,
   130  	validator MacaroonValidator) error {
   131  
   132  	if validator == nil {
   133  		return fmt.Errorf("validator cannot be nil")
   134  	}
   135  
   136  	_, ok := svc.ExternalValidators[fullMethod]
   137  	if ok {
   138  		return fmt.Errorf("external validator for method %s already "+
   139  			"registered", fullMethod)
   140  	}
   141  
   142  	svc.ExternalValidators[fullMethod] = validator
   143  	return nil
   144  }
   145  
   146  // ValidateMacaroon validates the capabilities of a given request given a
   147  // bakery service, context, and uri. Within the passed context.Context, we
   148  // expect a macaroon to be encoded as request metadata using the key
   149  // "macaroon".
   150  func (svc *Service) ValidateMacaroon(ctx context.Context,
   151  	requiredPermissions []bakery.Op, fullMethod string) error {
   152  
   153  	// Get macaroon bytes from context and unmarshal into macaroon.
   154  	macHex, err := RawMacaroonFromContext(ctx)
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	// With the macaroon obtained, we'll now decode the hex-string encoding.
   160  	macBytes, err := hex.DecodeString(macHex)
   161  	if err != nil {
   162  		return err
   163  	}
   164  
   165  	return svc.CheckMacAuth(
   166  		ctx, macBytes, requiredPermissions, fullMethod,
   167  	)
   168  }
   169  
   170  // CheckMacAuth checks that the macaroon is not disobeying any caveats and is
   171  // authorized to perform the operation the user wants to perform.
   172  func (svc *Service) CheckMacAuth(ctx context.Context, macBytes []byte,
   173  	requiredPermissions []bakery.Op, fullMethod string) error {
   174  
   175  	// With the macaroon obtained, we'll now unmarshal it from binary into
   176  	// its concrete struct representation.
   177  	mac := &macaroon.Macaroon{}
   178  	err := mac.UnmarshalBinary(macBytes)
   179  	if err != nil {
   180  		return err
   181  	}
   182  
   183  	// Check the method being called against the permitted operation, the
   184  	// expiration time and IP address and return the result.
   185  	authChecker := svc.Checker.Auth(macaroon.Slice{mac})
   186  	_, err = authChecker.Allow(ctx, requiredPermissions...)
   187  
   188  	// If the macaroon contains broad permissions and checks out, we're
   189  	// done.
   190  	if err == nil {
   191  		return nil
   192  	}
   193  
   194  	// To also allow the special permission of "uri:<FullMethod>" to be a
   195  	// valid permission, we need to check it manually in case there is no
   196  	// broader scope permission defined.
   197  	_, err = authChecker.Allow(ctx, bakery.Op{
   198  		Entity: PermissionEntityCustomURI,
   199  		Action: fullMethod,
   200  	})
   201  	return err
   202  }
   203  
   204  // Close closes the database that underlies the RootKeyStore and zeroes the
   205  // encryption keys.
   206  func (svc *Service) Close() error {
   207  	return svc.rks.Close()
   208  }
   209  
   210  // CreateUnlock calls the underlying root key store's CreateUnlock and returns
   211  // the result.
   212  func (svc *Service) CreateUnlock(password *[]byte) error {
   213  	return svc.rks.CreateUnlock(password)
   214  }
   215  
   216  // NewMacaroon wraps around the function Oven.NewMacaroon with the defaults,
   217  //   - version is always bakery.LatestVersion;
   218  //   - caveats is always nil.
   219  //
   220  // In addition, it takes a rootKeyID parameter, and puts it into the context.
   221  // The context is passed through Oven.NewMacaroon(), in which calls the function
   222  // RootKey(), that reads the context for rootKeyID.
   223  func (svc *Service) NewMacaroon(
   224  	ctx context.Context, rootKeyID []byte,
   225  	ops ...bakery.Op) (*bakery.Macaroon, error) {
   226  
   227  	// Check rootKeyID is not called with nil or empty bytes. We want the
   228  	// caller to be aware the value of root key ID used, so we won't replace
   229  	// it with the DefaultRootKeyID if not specified.
   230  	if len(rootKeyID) == 0 {
   231  		return nil, ErrMissingRootKeyID
   232  	}
   233  
   234  	// // Pass the root key ID to context.
   235  	ctx = ContextWithRootKeyID(ctx, rootKeyID)
   236  
   237  	return svc.Oven.NewMacaroon(ctx, bakery.LatestVersion, nil, ops...)
   238  }
   239  
   240  // ListMacaroonIDs returns all the root key ID values except the value of
   241  // encryptedKeyID.
   242  func (svc *Service) ListMacaroonIDs(ctxt context.Context) ([][]byte, error) {
   243  	return svc.rks.ListMacaroonIDs(ctxt)
   244  }
   245  
   246  // DeleteMacaroonID removes one specific root key ID. If the root key ID is
   247  // found and deleted, it will be returned.
   248  func (svc *Service) DeleteMacaroonID(ctxt context.Context,
   249  	rootKeyID []byte) ([]byte, error) {
   250  	return svc.rks.DeleteMacaroonID(ctxt, rootKeyID)
   251  }
   252  
   253  // GenerateNewRootKey calls the underlying root key store's GenerateNewRootKey
   254  // and returns the result.
   255  func (svc *Service) GenerateNewRootKey() error {
   256  	return svc.rks.GenerateNewRootKey()
   257  }
   258  
   259  // ChangePassword calls the underlying root key store's ChangePassword and
   260  // returns the result.
   261  func (svc *Service) ChangePassword(oldPw, newPw []byte) error {
   262  	return svc.rks.ChangePassword(oldPw, newPw)
   263  }
   264  
   265  // RawMacaroonFromContext is a helper function that extracts a raw macaroon
   266  // from the given incoming gRPC request context.
   267  func RawMacaroonFromContext(ctx context.Context) (string, error) {
   268  	// Get macaroon bytes from context and unmarshal into macaroon.
   269  	md, ok := metadata.FromIncomingContext(ctx)
   270  	if !ok {
   271  		return "", fmt.Errorf("unable to get metadata from context")
   272  	}
   273  	if len(md["macaroon"]) != 1 {
   274  		return "", fmt.Errorf("expected 1 macaroon, got %d",
   275  			len(md["macaroon"]))
   276  	}
   277  
   278  	return md["macaroon"][0], nil
   279  }
   280  
   281  // SafeCopyMacaroon creates a copy of a macaroon that is safe to be used and
   282  // modified. This is necessary because the macaroon library's own Clone() method
   283  // is unsafe for certain edge cases, resulting in both the cloned and the
   284  // original macaroons to be modified.
   285  func SafeCopyMacaroon(mac *macaroon.Macaroon) (*macaroon.Macaroon, error) {
   286  	macBytes, err := mac.MarshalBinary()
   287  	if err != nil {
   288  		return nil, err
   289  	}
   290  
   291  	newMac := &macaroon.Macaroon{}
   292  	if err := newMac.UnmarshalBinary(macBytes); err != nil {
   293  		return nil, err
   294  	}
   295  
   296  	return newMac, nil
   297  }