-
Notifications
You must be signed in to change notification settings - Fork 514
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Per-DB signer tests #863
Merged
Merged
Per-DB signer tests #863
Changes from all commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
8ba0bb9
Split caching keys pulled from a DB in memory into a wrapper object a…
cyli 466e75f
Refactor the sql_keydbstore_test file into a more generic keydbstore_…
cyli 8dafbfd
Add RethinkDB signer tests, and elaborate in the key passphrase rotat…
cyli af37dd8
Some additional logging messages and assertions about the KeyDBStore …
cyli File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
package keydbstore | ||
|
||
import ( | ||
"sync" | ||
|
||
"github.com/docker/notary/trustmanager" | ||
"github.com/docker/notary/tuf/data" | ||
) | ||
|
||
// Note: once trustmanager's file KeyStore has been flattened, this can be moved to trustmanager | ||
|
||
type cachedKeyStore struct { | ||
trustmanager.KeyStore | ||
lock *sync.Mutex | ||
cachedKeys map[string]*cachedKey | ||
} | ||
|
||
type cachedKey struct { | ||
role string | ||
key data.PrivateKey | ||
} | ||
|
||
// NewCachedKeyStore returns a new trustmanager.KeyStore that includes caching | ||
func NewCachedKeyStore(baseStore trustmanager.KeyStore) trustmanager.KeyStore { | ||
return &cachedKeyStore{ | ||
KeyStore: baseStore, | ||
lock: &sync.Mutex{}, | ||
cachedKeys: make(map[string]*cachedKey), | ||
} | ||
} | ||
|
||
// AddKey stores the contents of a private key. Both role and gun are ignored, | ||
// we always use Key IDs as name, and don't support aliases | ||
func (s *cachedKeyStore) AddKey(keyInfo trustmanager.KeyInfo, privKey data.PrivateKey) error { | ||
if err := s.KeyStore.AddKey(keyInfo, privKey); err != nil { | ||
return err | ||
} | ||
|
||
// Add the private key to our cache | ||
s.lock.Lock() | ||
defer s.lock.Unlock() | ||
s.cachedKeys[privKey.ID()] = &cachedKey{ | ||
role: keyInfo.Role, | ||
key: privKey, | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// GetKey returns the PrivateKey given a KeyID | ||
func (s *cachedKeyStore) GetKey(keyID string) (data.PrivateKey, string, error) { | ||
cachedKeyEntry, ok := s.cachedKeys[keyID] | ||
if ok { | ||
return cachedKeyEntry.key, cachedKeyEntry.role, nil | ||
} | ||
|
||
// retrieve the key from the underlying store and put it into the cache | ||
privKey, role, err := s.KeyStore.GetKey(keyID) | ||
if err == nil { | ||
s.lock.Lock() | ||
defer s.lock.Unlock() | ||
// Add the key to cache | ||
s.cachedKeys[privKey.ID()] = &cachedKey{key: privKey, role: role} | ||
return privKey, role, nil | ||
} | ||
return nil, "", err | ||
} | ||
|
||
// RemoveKey removes the key from the keyfilestore | ||
func (s *cachedKeyStore) RemoveKey(keyID string) error { | ||
s.lock.Lock() | ||
defer s.lock.Unlock() | ||
|
||
delete(s.cachedKeys, keyID) | ||
return s.KeyStore.RemoveKey(keyID) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
package keydbstore | ||
|
||
import ( | ||
"crypto/rand" | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/docker/notary/trustmanager" | ||
"github.com/docker/notary/tuf/data" | ||
"github.com/docker/notary/tuf/utils" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
// gets a key from the DB store, and asserts that the key is the expected key | ||
func requireGetKeySuccess(t *testing.T, dbStore trustmanager.KeyStore, expectedRole string, expectedKey data.PrivateKey) { | ||
retrKey, role, err := dbStore.GetKey(expectedKey.ID()) | ||
require.NoError(t, err) | ||
require.Equal(t, expectedRole, role) | ||
require.Equal(t, retrKey.Private(), expectedKey.Private()) | ||
require.Equal(t, retrKey.Algorithm(), expectedKey.Algorithm()) | ||
require.Equal(t, retrKey.Public(), expectedKey.Public()) | ||
require.Equal(t, retrKey.SignatureAlgorithm(), expectedKey.SignatureAlgorithm()) | ||
} | ||
|
||
// closes the DB connection first so we can test that the successful get was | ||
// from the cache | ||
func requireGetKeySuccessFromCache(t *testing.T, cachedStore, underlyingStore trustmanager.KeyStore, expectedRole string, expectedKey data.PrivateKey) { | ||
require.NoError(t, underlyingStore.RemoveKey(expectedKey.ID())) | ||
requireGetKeySuccess(t, cachedStore, expectedRole, expectedKey) | ||
} | ||
|
||
func requireGetKeyFailure(t *testing.T, dbStore trustmanager.KeyStore, keyID string) { | ||
_, _, err := dbStore.GetKey(keyID) | ||
require.IsType(t, trustmanager.ErrKeyNotFound{}, err) | ||
} | ||
|
||
type unAddableKeyStore struct { | ||
trustmanager.KeyStore | ||
} | ||
|
||
func (u unAddableKeyStore) AddKey(_ trustmanager.KeyInfo, _ data.PrivateKey) error { | ||
return fmt.Errorf("Can't add to keystore!") | ||
} | ||
|
||
type unRemoveableKeyStore struct { | ||
trustmanager.KeyStore | ||
failToRemove bool | ||
} | ||
|
||
func (u unRemoveableKeyStore) RemoveKey(keyID string) error { | ||
if u.failToRemove { | ||
return fmt.Errorf("Can't remove from keystore!") | ||
} | ||
return u.KeyStore.RemoveKey(keyID) | ||
} | ||
|
||
// Getting a key, on succcess, populates the cache. | ||
func TestGetSuccessPopulatesCache(t *testing.T) { | ||
underlying := trustmanager.NewKeyMemoryStore(constRetriever) | ||
cached := NewCachedKeyStore(underlying) | ||
|
||
testKey, err := utils.GenerateECDSAKey(rand.Reader) | ||
require.NoError(t, err) | ||
|
||
// nothing there yet | ||
requireGetKeyFailure(t, cached, testKey.ID()) | ||
|
||
// Add key to underlying store only | ||
err = underlying.AddKey(trustmanager.KeyInfo{Role: data.CanonicalTimestampRole, Gun: "gun"}, testKey) | ||
require.NoError(t, err) | ||
|
||
// getting for the first time is successful, and after that getting from cache should be too | ||
requireGetKeySuccess(t, cached, data.CanonicalTimestampRole, testKey) | ||
requireGetKeySuccessFromCache(t, cached, underlying, data.CanonicalTimestampRole, testKey) | ||
} | ||
|
||
// Creating a key, on succcess, populates the cache, but does not do so on failure | ||
func TestAddKeyPopulatesCacheIfSuccessful(t *testing.T) { | ||
var underlying trustmanager.KeyStore | ||
underlying = trustmanager.NewKeyMemoryStore(constRetriever) | ||
cached := NewCachedKeyStore(underlying) | ||
|
||
testKeys := make([]data.PrivateKey, 2) | ||
for i := 0; i < 2; i++ { | ||
privKey, err := utils.GenerateECDSAKey(rand.Reader) | ||
require.NoError(t, err) | ||
testKeys[i] = privKey | ||
} | ||
|
||
// Writing in the keystore succeeds | ||
err := cached.AddKey(trustmanager.KeyInfo{Role: data.CanonicalTimestampRole, Gun: "gun"}, testKeys[0]) | ||
require.NoError(t, err) | ||
|
||
// Now even if it's deleted from the underlying database, it's fine because it's cached | ||
requireGetKeySuccessFromCache(t, cached, underlying, data.CanonicalTimestampRole, testKeys[0]) | ||
|
||
// Writing in the keystore fails | ||
underlying = unAddableKeyStore{KeyStore: underlying} | ||
cached = NewCachedKeyStore(underlying) | ||
err = cached.AddKey(trustmanager.KeyInfo{Role: data.CanonicalTimestampRole, Gun: "gun"}, testKeys[1]) | ||
require.Error(t, err) | ||
|
||
// And now it can't be found in either DB | ||
requireGetKeyFailure(t, cached, testKeys[1].ID()) | ||
} | ||
|
||
// Deleting a key, no matter whether we succeed in the underlying layer or not, evicts the cached key. | ||
func TestDeleteKeyRemovesKeyFromCache(t *testing.T) { | ||
underlying := trustmanager.NewKeyMemoryStore(constRetriever) | ||
cached := NewCachedKeyStore(underlying) | ||
|
||
testKey, err := utils.GenerateECDSAKey(rand.Reader) | ||
require.NoError(t, err) | ||
|
||
// Write the key, which puts it in the cache | ||
err = cached.AddKey(trustmanager.KeyInfo{Role: data.CanonicalTimestampRole, Gun: "gun"}, testKey) | ||
require.NoError(t, err) | ||
|
||
// Deleting removes the key from the cache and the underlying store | ||
err = cached.RemoveKey(testKey.ID()) | ||
require.NoError(t, err) | ||
requireGetKeyFailure(t, cached, testKey.ID()) | ||
|
||
// Now set up an underlying store where the key can't be deleted | ||
failingUnderlying := unRemoveableKeyStore{KeyStore: underlying, failToRemove: true} | ||
cached = NewCachedKeyStore(failingUnderlying) | ||
err = cached.AddKey(trustmanager.KeyInfo{Role: data.CanonicalTimestampRole, Gun: "gun"}, testKey) | ||
require.NoError(t, err) | ||
|
||
// Deleting fails to remove the key from the underlying store | ||
err = cached.RemoveKey(testKey.ID()) | ||
require.Error(t, err) | ||
requireGetKeySuccess(t, failingUnderlying, data.CanonicalTimestampRole, testKey) | ||
|
||
// now actually remove the key from the underlying store to test that it's gone from the cache | ||
failingUnderlying.failToRemove = false | ||
require.NoError(t, failingUnderlying.RemoveKey(testKey.ID())) | ||
|
||
// and it's not in the cache | ||
requireGetKeyFailure(t, cached, testKey.ID()) | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking this could also replace a chunk of
trustmanager.GenericKeyStore
down the road, particularly if we get around to separating the password stuff out. We'd end up with something likeNewCachedKeyStore( NewGenericKeyStore( NewPasswdStorage( NewFileStorage(keyDirectory) )))
:-D