-
Notifications
You must be signed in to change notification settings - Fork 104
Knox Backend Database
Knox needs to store its key data somewhere. The way we use Knox for keys at Pinterest has very similar properties to config management systems in that it has frequent reads and infrequent writes. In that vein Pinterest uses a version of config v2 as the backing store for Knox (see https://github.com/pinterest/kingpin). This solution may not work for others as it requires Knox to be in AWS and for there to be a Zookeeper service available. This section describe the interface for Knox database implementations and the interface and current implementation of the DB cryptor.
The KeyDB is the interface Knox uses to store the actual keys. This interface has 5 functions total. Get and GetAll are called frequently and Update, Add, and Remove are called infrequently (only on write operations). For production usage, we recommend a solution that can meet these needs, provide some form of consistency (writes need to be ordered since there can be conflicts), and be accessible over the network. Knox was also built with the assumption that it wouldn’t need to scale out the number of keys (as evidenced by the GetAll call). The size of all key data will be relatively small, but for availability reasons you likely will want redundancy and backups.
// DB is the underlying database connection that KeyDB uses for all of its operations.
//
// This interface should not contain any business logic and should only deal with formatting
// and database specific logic.
type DB interface {
// Get returns the key specified by the ID.
Get(id string) (*DBKey, error)
// GetAll returns all of the keys in the database.
GetAll() ([]DBKey, error)
// Update makes an update to DBKey indexed by its ID.
// It will fail if the key has been changed since the specified version.
Update(key *DBKey) error
// Add adds the key(s) to the DB (it will fail if the key id exists).
Add(keys ...*DBKey) error
// Remove permanently removes the key specified by the ID.
Remove(id string) error
}
A cryptor converts a Knox key into a DBKey
which is meant to be a type that is protected against threats to confidentiality. The default cryptor (built using NewAESGCMCryptor
) uses AES-GCM to do authenticated encryption on each version of the key and keep the data associated with the key ID, the key version id, and the creation time.
type Cryptor interface {
Decrypt(*DBKey) (*knox.Key, error)
Encrypt(*knox.Key) (*DBKey, error)
EncryptVersion(*knox.Key, *knox.KeyVersion) (*EncKeyVersion, error)
}
type DBKey struct {
ID string `json:"id"`
ACL knox.ACL `json:"acl"`
VersionList []EncKeyVersion `json:"versions"`
VersionHash string `json:"hash"`
// The version should be set by the db provider and is not part of the data.
DBVersion int64 `json:"-"`
}
// EncKeyVersion is a struct for encrypting key data
type EncKeyVersion struct {
ID uint64 `json:"id"`
EncData []byte `json:"data"`
Status knox.VersionStatus `json:"status"`
CreationTime int64 `json:"ts"`
CryptoMetadata []byte `json:"crypt"`
}