github.com/decred/dcrlnd@v0.7.6/macaroons/service_test.go (about) 1 package macaroons_test 2 3 import ( 4 "context" 5 "encoding/hex" 6 "io/ioutil" 7 "os" 8 "path" 9 "testing" 10 11 "github.com/decred/dcrlnd/kvdb" 12 "github.com/decred/dcrlnd/macaroons" 13 "github.com/stretchr/testify/require" 14 "google.golang.org/grpc/metadata" 15 "gopkg.in/macaroon-bakery.v2/bakery" 16 "gopkg.in/macaroon-bakery.v2/bakery/checkers" 17 ) 18 19 var ( 20 testOperation = bakery.Op{ 21 Entity: "testEntity", 22 Action: "read", 23 } 24 testOperationURI = bakery.Op{ 25 Entity: macaroons.PermissionEntityCustomURI, 26 Action: "SomeMethod", 27 } 28 defaultPw = []byte("hello") 29 ) 30 31 // setupTestRootKeyStorage creates a dummy root key storage by 32 // creating a temporary macaroons.db and initializing it with the 33 // default password of 'hello'. Only the path to the temporary 34 // DB file is returned, because the service will open the file 35 // and read the store on its own. 36 func setupTestRootKeyStorage(t *testing.T) (string, kvdb.Backend) { 37 tempDir, err := ioutil.TempDir("", "macaroonstore-") 38 if err != nil { 39 t.Fatalf("Error creating temp dir: %v", err) 40 } 41 db, err := kvdb.Create( 42 kvdb.BoltBackendName, path.Join(tempDir, "macaroons.db"), true, 43 kvdb.DefaultDBTimeout, 44 ) 45 if err != nil { 46 t.Fatalf("Error opening store DB: %v", err) 47 } 48 store, err := macaroons.NewRootKeyStorage(db) 49 if err != nil { 50 db.Close() 51 t.Fatalf("Error creating root key store: %v", err) 52 } 53 defer store.Close() 54 err = store.CreateUnlock(&defaultPw) 55 if err != nil { 56 t.Fatalf("Error setting an encryption key: %v", err) 57 } 58 return tempDir, db 59 } 60 61 // TestNewService tests the creation of the macaroon service. 62 func TestNewService(t *testing.T) { 63 // First, initialize a dummy DB file with a store that the service 64 // can read from. Make sure the file is removed in the end. 65 tempDir, db := setupTestRootKeyStorage(t) 66 defer os.RemoveAll(tempDir) 67 68 // Second, create the new service instance, unlock it and pass in a 69 // checker that we expect it to add to the bakery. 70 service, err := macaroons.NewService( 71 db, "dcrlnd", false, macaroons.IPLockChecker, 72 ) 73 if err != nil { 74 t.Fatalf("Error creating new service: %v", err) 75 } 76 defer service.Close() 77 err = service.CreateUnlock(&defaultPw) 78 if err != nil { 79 t.Fatalf("Error unlocking root key storage: %v", err) 80 } 81 82 // Third, check if the created service can bake macaroons. 83 _, err = service.NewMacaroon(context.TODO(), nil, testOperation) 84 if err != macaroons.ErrMissingRootKeyID { 85 t.Fatalf("Received %v instead of ErrMissingRootKeyID", err) 86 } 87 88 macaroon, err := service.NewMacaroon( 89 context.TODO(), macaroons.DefaultRootKeyID, testOperation, 90 ) 91 if err != nil { 92 t.Fatalf("Error creating macaroon from service: %v", err) 93 } 94 if macaroon.Namespace().String() != "std:" { 95 t.Fatalf("The created macaroon has an invalid namespace: %s", 96 macaroon.Namespace().String()) 97 } 98 99 // Finally, check if the service has been initialized correctly and 100 // the checker has been added. 101 var checkerFound = false 102 checker := service.Checker.FirstPartyCaveatChecker.(*checkers.Checker) 103 for _, info := range checker.Info() { 104 if info.Name == "ipaddr" && 105 info.Prefix == "" && 106 info.Namespace == "std" { 107 checkerFound = true 108 } 109 } 110 if !checkerFound { 111 t.Fatalf("Checker '%s' not found in service.", "ipaddr") 112 } 113 } 114 115 // TestValidateMacaroon tests the validation of a macaroon that is in an 116 // incoming context. 117 func TestValidateMacaroon(t *testing.T) { 118 // First, initialize the service and unlock it. 119 tempDir, db := setupTestRootKeyStorage(t) 120 defer os.RemoveAll(tempDir) 121 service, err := macaroons.NewService( 122 db, "dcrlnd", false, macaroons.IPLockChecker, 123 ) 124 if err != nil { 125 t.Fatalf("Error creating new service: %v", err) 126 } 127 defer service.Close() 128 129 err = service.CreateUnlock(&defaultPw) 130 if err != nil { 131 t.Fatalf("Error unlocking root key storage: %v", err) 132 } 133 134 // Then, create a new macaroon that we can serialize. 135 macaroon, err := service.NewMacaroon( 136 context.TODO(), macaroons.DefaultRootKeyID, testOperation, 137 testOperationURI, 138 ) 139 if err != nil { 140 t.Fatalf("Error creating macaroon from service: %v", err) 141 } 142 macaroonBinary, err := macaroon.M().MarshalBinary() 143 if err != nil { 144 t.Fatalf("Error serializing macaroon: %v", err) 145 } 146 147 // Because the macaroons are always passed in a context, we need to 148 // mock one that has just the serialized macaroon as a value. 149 md := metadata.New(map[string]string{ 150 "macaroon": hex.EncodeToString(macaroonBinary), 151 }) 152 mockContext := metadata.NewIncomingContext(context.Background(), md) 153 154 // Finally, validate the macaroon against the required permissions. 155 err = service.ValidateMacaroon( 156 mockContext, []bakery.Op{testOperation}, "FooMethod", 157 ) 158 if err != nil { 159 t.Fatalf("Error validating the macaroon: %v", err) 160 } 161 162 // If the macaroon has the method specific URI permission, the list of 163 // required entity/action pairs is irrelevant. 164 err = service.ValidateMacaroon( 165 mockContext, []bakery.Op{{Entity: "irrelevant"}}, "SomeMethod", 166 ) 167 if err != nil { 168 t.Fatalf("Error validating the macaroon: %v", err) 169 } 170 } 171 172 // TestListMacaroonIDs checks that ListMacaroonIDs returns the expected result. 173 func TestListMacaroonIDs(t *testing.T) { 174 // First, initialize a dummy DB file with a store that the service 175 // can read from. Make sure the file is removed in the end. 176 tempDir, db := setupTestRootKeyStorage(t) 177 defer os.RemoveAll(tempDir) 178 179 // Second, create the new service instance, unlock it and pass in a 180 // checker that we expect it to add to the bakery. 181 service, err := macaroons.NewService( 182 db, "dcrlnd", false, macaroons.IPLockChecker, 183 ) 184 require.NoError(t, err, "Error creating new service") 185 defer service.Close() 186 187 err = service.CreateUnlock(&defaultPw) 188 require.NoError(t, err, "Error unlocking root key storage") 189 190 // Third, make 3 new macaroons with different root key IDs. 191 expectedIDs := [][]byte{{1}, {2}, {3}} 192 for _, v := range expectedIDs { 193 _, err := service.NewMacaroon(context.TODO(), v, testOperation) 194 require.NoError(t, err, "Error creating macaroon from service") 195 } 196 197 // Finally, check that calling List return the expected values. 198 ids, _ := service.ListMacaroonIDs(context.TODO()) 199 require.Equal(t, expectedIDs, ids, "root key IDs mismatch") 200 } 201 202 // TestDeleteMacaroonID removes the specific root key ID. 203 func TestDeleteMacaroonID(t *testing.T) { 204 ctxb := context.Background() 205 206 // First, initialize a dummy DB file with a store that the service 207 // can read from. Make sure the file is removed in the end. 208 tempDir, db := setupTestRootKeyStorage(t) 209 defer os.RemoveAll(tempDir) 210 211 // Second, create the new service instance, unlock it and pass in a 212 // checker that we expect it to add to the bakery. 213 service, err := macaroons.NewService( 214 db, "dcrlnd", false, macaroons.IPLockChecker, 215 ) 216 require.NoError(t, err, "Error creating new service") 217 defer service.Close() 218 219 err = service.CreateUnlock(&defaultPw) 220 require.NoError(t, err, "Error unlocking root key storage") 221 222 // Third, checks that removing encryptedKeyID returns an error. 223 encryptedKeyID := []byte("enckey") 224 _, err = service.DeleteMacaroonID(ctxb, encryptedKeyID) 225 require.Equal(t, macaroons.ErrDeletionForbidden, err) 226 227 // Fourth, checks that removing DefaultKeyID returns an error. 228 _, err = service.DeleteMacaroonID(ctxb, macaroons.DefaultRootKeyID) 229 require.Equal(t, macaroons.ErrDeletionForbidden, err) 230 231 // Fifth, checks that removing empty key id returns an error. 232 _, err = service.DeleteMacaroonID(ctxb, []byte{}) 233 require.Equal(t, macaroons.ErrMissingRootKeyID, err) 234 235 // Sixth, checks that removing a non-existed key id returns nil. 236 nonExistedID := []byte("test-non-existed") 237 deletedID, err := service.DeleteMacaroonID(ctxb, nonExistedID) 238 require.NoError(t, err, "deleting macaroon ID got an error") 239 require.Nil(t, deletedID, "deleting non-existed ID should return nil") 240 241 // Seventh, make 3 new macaroons with different root key IDs, and delete 242 // one. 243 expectedIDs := [][]byte{{1}, {2}, {3}} 244 for _, v := range expectedIDs { 245 _, err := service.NewMacaroon(ctxb, v, testOperation) 246 require.NoError(t, err, "Error creating macaroon from service") 247 } 248 deletedID, err = service.DeleteMacaroonID(ctxb, expectedIDs[0]) 249 require.NoError(t, err, "deleting macaroon ID got an error") 250 251 // Finally, check that the ID is deleted. 252 require.Equal(t, expectedIDs[0], deletedID, "expected ID to be removed") 253 ids, _ := service.ListMacaroonIDs(ctxb) 254 require.Equal(t, expectedIDs[1:], ids, "root key IDs mismatch") 255 } 256 257 // TestCloneMacaroons tests that macaroons can be cloned correctly and that 258 // modifications to the copy don't affect the original. 259 func TestCloneMacaroons(t *testing.T) { 260 // Get a configured version of the constraint function. 261 constraintFunc := macaroons.TimeoutConstraint(3) 262 263 // Now we need a dummy macaroon that we can apply the constraint 264 // function to. 265 testMacaroon := createDummyMacaroon(t) 266 err := constraintFunc(testMacaroon) 267 require.NoError(t, err) 268 269 // Check that the caveat has an empty location. 270 require.Equal( 271 t, "", testMacaroon.Caveats()[0].Location, 272 "expected caveat location to be empty, found: %s", 273 testMacaroon.Caveats()[0].Location, 274 ) 275 276 // Make a copy of the macaroon. 277 newMacCred, err := macaroons.NewMacaroonCredential(testMacaroon) 278 require.NoError(t, err) 279 280 newMac := newMacCred.Macaroon 281 require.Equal( 282 t, "", newMac.Caveats()[0].Location, 283 "expected new caveat location to be empty, found: %s", 284 newMac.Caveats()[0].Location, 285 ) 286 287 // They should be deep equal as well. 288 testMacaroonBytes, err := testMacaroon.MarshalBinary() 289 require.NoError(t, err) 290 newMacBytes, err := newMac.MarshalBinary() 291 require.NoError(t, err) 292 require.Equal(t, testMacaroonBytes, newMacBytes) 293 294 // Modify the caveat location on the old macaroon. 295 testMacaroon.Caveats()[0].Location = "mars" 296 297 // The old macaroon's caveat location should be changed. 298 require.Equal( 299 t, "mars", testMacaroon.Caveats()[0].Location, 300 "expected caveat location to be empty, found: %s", 301 testMacaroon.Caveats()[0].Location, 302 ) 303 304 // The new macaroon's caveat location should stay untouched. 305 require.Equal( 306 t, "", newMac.Caveats()[0].Location, 307 "expected new caveat location to be empty, found: %s", 308 newMac.Caveats()[0].Location, 309 ) 310 }