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 }