multi: macaroon root key encryption
This commit is contained in:
1 parent
4b1cc98808
commit
de6efbd1a1
7 files changed
+381
-94
No files matched your search
+68
-8
@@ -1,13 +1,16 @@
|
||||
package macaroons
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
||||
"gopkg.in/macaroon-bakery.v2/bakery"
|
||||
"gopkg.in/macaroon-bakery.v2/bakery/checkers"
|
||||
macaroon "gopkg.in/macaroon.v2"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
@@ -20,6 +23,15 @@ var (
|
||||
dbFilename = "macaroons.db"
|
||||
)
|
||||
|
||||
// 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.
|
||||
type Service struct {
|
||||
bakery.Bakery
|
||||
|
||||
rks *RootKeyStorage
|
||||
}
|
||||
|
||||
// NewService returns a service backed by the macaroon Bolt DB stored in the
|
||||
// passed directory. The `checks` argument can be any of the `Checker` type
|
||||
// functions defined in this package, or a custom checker if desired. This
|
||||
@@ -27,7 +39,7 @@ var (
|
||||
// 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 NewService(dir string, checks ...Checker) (*bakery.Bakery, error) {
|
||||
func NewService(dir string, checks ...Checker) (*Service, error) {
|
||||
// Open the database that we'll use to store the primary macaroon key,
|
||||
// and all generated macaroons+caveats.
|
||||
macaroonDB, err := bolt.Open(path.Join(dir, dbFilename), 0600,
|
||||
@@ -62,7 +74,7 @@ func NewService(dir string, checks ...Checker) (*bakery.Bakery, error) {
|
||||
}
|
||||
}
|
||||
|
||||
return svc, nil
|
||||
return &Service{*svc, rootKeyStore}, nil
|
||||
}
|
||||
|
||||
// isRegistered checks to see if the required checker has already been
|
||||
@@ -83,7 +95,7 @@ func isRegistered(c *checkers.Checker, name string) bool {
|
||||
|
||||
// UnaryServerInterceptor is a GRPC interceptor that checks whether the
|
||||
// request is authorized by the included macaroons.
|
||||
func UnaryServerInterceptor(svc *bakery.Bakery,
|
||||
func (svc *Service) UnaryServerInterceptor(
|
||||
permissionMap map[string][]bakery.Op) grpc.UnaryServerInterceptor {
|
||||
|
||||
return func(ctx context.Context, req interface{},
|
||||
@@ -95,8 +107,7 @@ func UnaryServerInterceptor(svc *bakery.Bakery,
|
||||
"required for method", info.FullMethod)
|
||||
}
|
||||
|
||||
err := ValidateMacaroon(ctx, permissionMap[info.FullMethod],
|
||||
svc)
|
||||
err := svc.ValidateMacaroon(ctx, permissionMap[info.FullMethod])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -107,7 +118,7 @@ func UnaryServerInterceptor(svc *bakery.Bakery,
|
||||
|
||||
// StreamServerInterceptor is a GRPC interceptor that checks whether the
|
||||
// request is authorized by the included macaroons.
|
||||
func StreamServerInterceptor(svc *bakery.Bakery,
|
||||
func (svc *Service) StreamServerInterceptor(
|
||||
permissionMap map[string][]bakery.Op) grpc.StreamServerInterceptor {
|
||||
|
||||
return func(srv interface{}, ss grpc.ServerStream,
|
||||
@@ -118,8 +129,8 @@ func StreamServerInterceptor(svc *bakery.Bakery,
|
||||
"for method", info.FullMethod)
|
||||
}
|
||||
|
||||
err := ValidateMacaroon(ss.Context(),
|
||||
permissionMap[info.FullMethod], svc)
|
||||
err := svc.ValidateMacaroon(ss.Context(),
|
||||
permissionMap[info.FullMethod])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -127,3 +138,52 @@ func StreamServerInterceptor(svc *bakery.Bakery,
|
||||
return handler(srv, ss)
|
||||
}
|
||||
}
|
||||
|
||||
// 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".
|
||||
func (svc *Service) ValidateMacaroon(ctx context.Context,
|
||||
requiredPermissions []bakery.Op) error {
|
||||
|
||||
// Get macaroon bytes from context and unmarshal into macaroon.
|
||||
md, ok := metadata.FromIncomingContext(ctx)
|
||||
if !ok {
|
||||
return fmt.Errorf("unable to get metadata from context")
|
||||
}
|
||||
if len(md["macaroon"]) != 1 {
|
||||
return fmt.Errorf("expected 1 macaroon, got %d",
|
||||
len(md["macaroon"]))
|
||||
}
|
||||
|
||||
// With the macaroon obtained, we'll now decode the hex-string
|
||||
// encoding, then unmarshal it from binary into its concrete struct
|
||||
// representation.
|
||||
macBytes, err := hex.DecodeString(md["macaroon"][0])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mac := &macaroon.Macaroon{}
|
||||
err = mac.UnmarshalBinary(macBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Check the method being called against the permitted operation and
|
||||
// the expiration time and IP address and return the result.
|
||||
authChecker := svc.Checker.Auth(macaroon.Slice{mac})
|
||||
_, err = authChecker.Allow(ctx, requiredPermissions...)
|
||||
return err
|
||||
}
|
||||
|
||||
// Close closes the database that underlies the RootKeyStore and zeroes the
|
||||
// encryption keys.
|
||||
func (svc *Service) Close() error {
|
||||
return svc.rks.Close()
|
||||
}
|
||||
|
||||
// CreateUnlock calls the underlying root key store's CreateUnlock and returns
|
||||
// the result.
|
||||
func (svc *Service) CreateUnlock(password *[]byte) error {
|
||||
return svc.rks.CreateUnlock(password)
|
||||
}
|
||||
Reference in new issue
Block a user