Update Redwood encryption interface (#8172)
Update Redwood encryption interface to make it better suit for per-tenant encryption, where we will need to do tenant page split.
This commit is contained in:
parent
2bdfc52f97
commit
e66942ada4
|
@ -2192,7 +2192,7 @@ public:
|
|||
int64_t remapCleanupWindowBytes,
|
||||
int concurrentExtentReads,
|
||||
bool memoryOnly,
|
||||
Reference<IEncryptionKeyProvider> keyProvider,
|
||||
Reference<IPageEncryptionKeyProvider> keyProvider,
|
||||
Promise<Void> errorPromise = {})
|
||||
: keyProvider(keyProvider), ioLock(FLOW_KNOBS->MAX_OUTSTANDING, ioMaxPriority, FLOW_KNOBS->MAX_OUTSTANDING / 2),
|
||||
pageCacheBytes(pageCacheSizeBytes), desiredPageSize(desiredPageSize), desiredExtentSize(desiredExtentSize),
|
||||
|
@ -2974,7 +2974,7 @@ public:
|
|||
try {
|
||||
page->postReadHeader(pageID);
|
||||
if (page->isEncrypted()) {
|
||||
EncryptionKey k = wait(self->keyProvider->getSecrets(page->encryptionKey));
|
||||
ArenaPage::EncryptionKey k = wait(self->keyProvider->getEncryptionKey(page->getEncodingHeader()));
|
||||
page->encryptionKey = k;
|
||||
}
|
||||
page->postReadPayload(pageID);
|
||||
|
@ -3042,7 +3042,7 @@ public:
|
|||
try {
|
||||
page->postReadHeader(pageIDs.front());
|
||||
if (page->isEncrypted()) {
|
||||
EncryptionKey k = wait(self->keyProvider->getSecrets(page->encryptionKey));
|
||||
ArenaPage::EncryptionKey k = wait(self->keyProvider->getEncryptionKey(page->getEncodingHeader()));
|
||||
page->encryptionKey = k;
|
||||
}
|
||||
page->postReadPayload(pageIDs.front());
|
||||
|
@ -3955,7 +3955,7 @@ private:
|
|||
int physicalExtentSize;
|
||||
int pagesPerExtent;
|
||||
|
||||
Reference<IEncryptionKeyProvider> keyProvider;
|
||||
Reference<IPageEncryptionKeyProvider> keyProvider;
|
||||
|
||||
PriorityMultiLock ioLock;
|
||||
|
||||
|
@ -5036,7 +5036,7 @@ public:
|
|||
VersionedBTree(IPager2* pager,
|
||||
std::string name,
|
||||
EncodingType defaultEncodingType,
|
||||
Reference<IEncryptionKeyProvider> keyProvider)
|
||||
Reference<IPageEncryptionKeyProvider> keyProvider)
|
||||
: m_pager(pager), m_encodingType(defaultEncodingType), m_enforceEncodingType(false), m_keyProvider(keyProvider),
|
||||
m_pBuffer(nullptr), m_mutationCount(0), m_name(name) {
|
||||
|
||||
|
@ -5064,7 +5064,7 @@ public:
|
|||
state Reference<ArenaPage> page = self->m_pager->newPageBuffer();
|
||||
page->init(self->m_encodingType, PageType::BTreeNode, 1);
|
||||
if (page->isEncrypted()) {
|
||||
EncryptionKey k = wait(self->m_keyProvider->getByRange(dbBegin.key, dbEnd.key));
|
||||
ArenaPage::EncryptionKey k = wait(self->m_keyProvider->getLatestDefaultEncryptionKey());
|
||||
page->encryptionKey = k;
|
||||
}
|
||||
|
||||
|
@ -5543,7 +5543,7 @@ private:
|
|||
IPager2* m_pager;
|
||||
EncodingType m_encodingType;
|
||||
bool m_enforceEncodingType;
|
||||
Reference<IEncryptionKeyProvider> m_keyProvider;
|
||||
Reference<IPageEncryptionKeyProvider> m_keyProvider;
|
||||
|
||||
// Counter to update with DecodeCache memory usage
|
||||
int64_t* m_pDecodeCacheMemory = nullptr;
|
||||
|
@ -5843,7 +5843,7 @@ private:
|
|||
(pagesToBuild[pageIndex].blockCount == 1) ? PageType::BTreeNode : PageType::BTreeSuperNode,
|
||||
height);
|
||||
if (page->isEncrypted()) {
|
||||
EncryptionKey k = wait(self->m_keyProvider->getByRange(pageLowerBound.key, pageUpperBound.key));
|
||||
ArenaPage::EncryptionKey k = wait(self->m_keyProvider->getLatestDefaultEncryptionKey());
|
||||
page->encryptionKey = k;
|
||||
}
|
||||
|
||||
|
@ -7694,7 +7694,7 @@ RedwoodRecordRef VersionedBTree::dbEnd(LiteralStringRef("\xff\xff\xff\xff\xff"))
|
|||
|
||||
class KeyValueStoreRedwood : public IKeyValueStore {
|
||||
public:
|
||||
KeyValueStoreRedwood(std::string filename, UID logID, Reference<IEncryptionKeyProvider> encryptionKeyProvider)
|
||||
KeyValueStoreRedwood(std::string filename, UID logID, Reference<IPageEncryptionKeyProvider> encryptionKeyProvider)
|
||||
: m_filename(filename), m_concurrentReads(SERVER_KNOBS->REDWOOD_KVSTORE_CONCURRENT_READS, 0),
|
||||
prefetch(SERVER_KNOBS->REDWOOD_KVSTORE_RANGE_PREFETCH) {
|
||||
|
||||
|
@ -7723,7 +7723,7 @@ public:
|
|||
//
|
||||
// TODO(yiwu): When the cluster encryption config is available later, fail if the cluster is configured to
|
||||
// enable encryption, but the Redwood instance is unencrypted.
|
||||
if (encryptionKeyProvider && encryptionKeyProvider->shouldEnableEncryption()) {
|
||||
if (encryptionKeyProvider && encryptionKeyProvider->enableEncryption()) {
|
||||
encodingType = EncodingType::AESEncryptionV1;
|
||||
m_keyProvider = encryptionKeyProvider;
|
||||
}
|
||||
|
@ -8020,7 +8020,7 @@ private:
|
|||
PriorityMultiLock m_concurrentReads;
|
||||
bool prefetch;
|
||||
Version m_nextCommitVersion;
|
||||
Reference<IEncryptionKeyProvider> m_keyProvider;
|
||||
Reference<IPageEncryptionKeyProvider> m_keyProvider;
|
||||
Future<Void> m_lastCommit = Void();
|
||||
|
||||
template <typename T>
|
||||
|
@ -8031,7 +8031,7 @@ private:
|
|||
|
||||
IKeyValueStore* keyValueStoreRedwoodV1(std::string const& filename,
|
||||
UID logID,
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider) {
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider) {
|
||||
return new KeyValueStoreRedwood(filename, logID, encryptionKeyProvider);
|
||||
}
|
||||
|
||||
|
@ -9814,7 +9814,7 @@ TEST_CASE("Lredwood/correctness/btree") {
|
|||
|
||||
state EncodingType encodingType =
|
||||
static_cast<EncodingType>(deterministicRandom()->randomInt(0, EncodingType::MAX_ENCODING_TYPE));
|
||||
state Reference<IEncryptionKeyProvider> keyProvider;
|
||||
state Reference<IPageEncryptionKeyProvider> keyProvider;
|
||||
if (encodingType == EncodingType::AESEncryptionV1) {
|
||||
keyProvider = makeReference<RandomEncryptionKeyProvider>();
|
||||
} else if (encodingType == EncodingType::XOREncryption_TestOnly) {
|
||||
|
@ -10300,7 +10300,7 @@ TEST_CASE(":/redwood/performance/extentQueue") {
|
|||
remapCleanupWindowBytes,
|
||||
concurrentExtentReads,
|
||||
false,
|
||||
Reference<IEncryptionKeyProvider>());
|
||||
Reference<IPageEncryptionKeyProvider>());
|
||||
|
||||
wait(success(pager->init()));
|
||||
|
||||
|
@ -10358,7 +10358,7 @@ TEST_CASE(":/redwood/performance/extentQueue") {
|
|||
remapCleanupWindowBytes,
|
||||
concurrentExtentReads,
|
||||
false,
|
||||
Reference<IEncryptionKeyProvider>());
|
||||
Reference<IPageEncryptionKeyProvider>());
|
||||
wait(success(pager->init()));
|
||||
|
||||
printf("Starting ExtentQueue FastPath Recovery from Disk.\n");
|
||||
|
@ -10503,9 +10503,9 @@ TEST_CASE(":/redwood/performance/set") {
|
|||
remapCleanupWindowBytes,
|
||||
concurrentExtentReads,
|
||||
pagerMemoryOnly,
|
||||
Reference<IEncryptionKeyProvider>());
|
||||
Reference<IPageEncryptionKeyProvider>());
|
||||
state VersionedBTree* btree =
|
||||
new VersionedBTree(pager, file, EncodingType::XXHash64, Reference<IEncryptionKeyProvider>());
|
||||
new VersionedBTree(pager, file, EncodingType::XXHash64, Reference<IPageEncryptionKeyProvider>());
|
||||
wait(btree->init());
|
||||
printf("Initialized. StorageBytes=%s\n", btree->getStorageBytes().toString().c_str());
|
||||
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
* IClosable.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
#ifndef FDBSERVER_ICLOSABLE_H
|
||||
#define FDBSERVER_ICLOSABLE_H
|
||||
#pragma once
|
||||
|
||||
class IClosable {
|
||||
public:
|
||||
// IClosable is a base interface for any disk-backed data structure that needs to support asynchronous errors,
|
||||
// shutdown and deletion
|
||||
|
||||
virtual Future<Void> getError()
|
||||
const = 0; // asynchronously throws an error if there is an internal error. Never set
|
||||
// inside (on the stack of) a call to another API function on this object.
|
||||
virtual Future<Void> onClosed()
|
||||
const = 0; // the future is set to Void when this is totally shut down after dispose() or
|
||||
// close(). But this function cannot be called after dispose or close!
|
||||
virtual void dispose() = 0; // permanently delete the data AND invalidate this interface
|
||||
virtual void close() = 0; // invalidate this interface, but do not delete the data. Outstanding operations may or
|
||||
// may not take effect in the background.
|
||||
};
|
||||
|
||||
#endif
|
|
@ -1,298 +0,0 @@
|
|||
/*
|
||||
* IEncryptionKeyProvider.actor.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "fdbclient/BlobCipher.h"
|
||||
#if defined(NO_INTELLISENSE) && !defined(FDBSERVER_IENCRYPTIONKEYPROVIDER_ACTOR_G_H)
|
||||
#define FDBSERVER_IENCRYPTIONKEYPROVIDER_ACTOR_G_H
|
||||
#include "fdbserver/IEncryptionKeyProvider.actor.g.h"
|
||||
#elif !defined(FDBSERVER_IENCRYPTIONKEYPROVIDER_ACTOR_H)
|
||||
#define FDBSERVER_IENCRYPTIONKEYPROVIDER_ACTOR_H
|
||||
|
||||
#include "fdbclient/GetEncryptCipherKeys.actor.h"
|
||||
#include "fdbclient/Tenant.h"
|
||||
|
||||
#include "fdbserver/EncryptionOpsUtils.h"
|
||||
#include "fdbserver/ServerDBInfo.h"
|
||||
|
||||
#include "flow/Arena.h"
|
||||
#include "flow/EncryptUtils.h"
|
||||
#define XXH_INLINE_ALL
|
||||
#include "flow/xxhash.h"
|
||||
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
||||
typedef uint64_t XOREncryptionKeyID;
|
||||
|
||||
// EncryptionKeyRef is somewhat multi-variant, it will contain members representing the union
|
||||
// of all fields relevant to any implemented encryption scheme. They are generally of
|
||||
// the form
|
||||
// Page Fields - fields which come from or are stored in the Page
|
||||
// Secret Fields - fields which are only known by the Key Provider
|
||||
// but it is up to each encoding and provider which fields are which and which ones are used
|
||||
//
|
||||
// TODO(yiwu): Rename and/or refactor this struct. It doesn't sound like an encryption key should
|
||||
// contain page fields like encryption header.
|
||||
struct EncryptionKeyRef {
|
||||
|
||||
EncryptionKeyRef(){};
|
||||
EncryptionKeyRef(Arena& arena, const EncryptionKeyRef& toCopy)
|
||||
: cipherKeys(toCopy.cipherKeys), secret(arena, toCopy.secret), id(toCopy.id) {}
|
||||
int expectedSize() const { return secret.size(); }
|
||||
|
||||
// Fields for AESEncryptionV1
|
||||
TextAndHeaderCipherKeys cipherKeys;
|
||||
Optional<BlobCipherEncryptHeader> cipherHeader;
|
||||
// Fields for XOREncryption_TestOnly
|
||||
StringRef secret;
|
||||
Optional<XOREncryptionKeyID> id;
|
||||
};
|
||||
typedef Standalone<EncryptionKeyRef> EncryptionKey;
|
||||
|
||||
// Interface used by pager to get encryption keys reading pages from disk
|
||||
// and by the BTree to get encryption keys to use for new pages
|
||||
class IEncryptionKeyProvider : public ReferenceCounted<IEncryptionKeyProvider> {
|
||||
public:
|
||||
virtual ~IEncryptionKeyProvider() {}
|
||||
|
||||
// Get an EncryptionKey with Secret Fields populated based on the given Page Fields.
|
||||
// It is up to the implementation which fields those are.
|
||||
// The output Page Fields must match the input Page Fields.
|
||||
virtual Future<EncryptionKey> getSecrets(const EncryptionKeyRef& key) = 0;
|
||||
|
||||
// Get encryption key that should be used for a given user Key-Value range
|
||||
virtual Future<EncryptionKey> getByRange(const KeyRef& begin, const KeyRef& end) = 0;
|
||||
|
||||
// Setting tenant prefix to tenant name map.
|
||||
virtual void setTenantPrefixIndex(Reference<TenantPrefixIndex> tenantPrefixIndex) {}
|
||||
|
||||
virtual bool shouldEnableEncryption() const = 0;
|
||||
};
|
||||
|
||||
// The null key provider is useful to simplify page decoding.
|
||||
// It throws an error for any key info requested.
|
||||
class NullKeyProvider : public IEncryptionKeyProvider {
|
||||
public:
|
||||
virtual ~NullKeyProvider() {}
|
||||
bool shouldEnableEncryption() const override { return true; }
|
||||
Future<EncryptionKey> getSecrets(const EncryptionKeyRef& key) override { throw encryption_key_not_found(); }
|
||||
Future<EncryptionKey> getByRange(const KeyRef& begin, const KeyRef& end) override {
|
||||
throw encryption_key_not_found();
|
||||
}
|
||||
};
|
||||
|
||||
// Key provider for dummy XOR encryption scheme
|
||||
class XOREncryptionKeyProvider_TestOnly : public IEncryptionKeyProvider {
|
||||
public:
|
||||
XOREncryptionKeyProvider_TestOnly(std::string filename) {
|
||||
ASSERT(g_network->isSimulated());
|
||||
|
||||
// Choose a deterministic random filename (without path) byte for secret generation
|
||||
// Remove any leading directory names
|
||||
size_t lastSlash = filename.find_last_of("\\/");
|
||||
if (lastSlash != filename.npos) {
|
||||
filename.erase(0, lastSlash);
|
||||
}
|
||||
xorWith = filename.empty() ? 0x5e
|
||||
: (uint8_t)filename[XXH3_64bits(filename.data(), filename.size()) % filename.size()];
|
||||
}
|
||||
|
||||
virtual ~XOREncryptionKeyProvider_TestOnly() {}
|
||||
|
||||
bool shouldEnableEncryption() const override { return true; }
|
||||
|
||||
Future<EncryptionKey> getSecrets(const EncryptionKeyRef& key) override {
|
||||
if (!key.id.present()) {
|
||||
throw encryption_key_not_found();
|
||||
}
|
||||
EncryptionKey s = key;
|
||||
uint8_t secret = ~(uint8_t)key.id.get() ^ xorWith;
|
||||
s.secret = StringRef(s.arena(), &secret, 1);
|
||||
return s;
|
||||
}
|
||||
|
||||
Future<EncryptionKey> getByRange(const KeyRef& begin, const KeyRef& end) override {
|
||||
EncryptionKeyRef k;
|
||||
k.id = end.empty() ? 0 : *(end.end() - 1);
|
||||
return getSecrets(k);
|
||||
}
|
||||
|
||||
uint8_t xorWith;
|
||||
};
|
||||
|
||||
// Key provider to provider cipher keys randomly from a pre-generated pool. Use for testing.
|
||||
class RandomEncryptionKeyProvider : public IEncryptionKeyProvider {
|
||||
public:
|
||||
RandomEncryptionKeyProvider() {
|
||||
for (unsigned i = 0; i < NUM_CIPHER; i++) {
|
||||
BlobCipherDetails cipherDetails;
|
||||
cipherDetails.encryptDomainId = i;
|
||||
cipherDetails.baseCipherId = deterministicRandom()->randomUInt64();
|
||||
cipherDetails.salt = deterministicRandom()->randomUInt64();
|
||||
cipherKeys[i] = generateCipherKey(cipherDetails);
|
||||
}
|
||||
}
|
||||
virtual ~RandomEncryptionKeyProvider() = default;
|
||||
|
||||
bool shouldEnableEncryption() const override { return true; }
|
||||
|
||||
Future<EncryptionKey> getSecrets(const EncryptionKeyRef& key) override {
|
||||
ASSERT(key.cipherHeader.present());
|
||||
EncryptionKey s = key;
|
||||
s.cipherKeys.cipherTextKey = cipherKeys[key.cipherHeader.get().cipherTextDetails.encryptDomainId];
|
||||
s.cipherKeys.cipherHeaderKey = cipherKeys[key.cipherHeader.get().cipherHeaderDetails.encryptDomainId];
|
||||
return s;
|
||||
}
|
||||
|
||||
Future<EncryptionKey> getByRange(const KeyRef& /*begin*/, const KeyRef& /*end*/) override {
|
||||
EncryptionKey s;
|
||||
s.cipherKeys.cipherTextKey = getRandomCipherKey();
|
||||
s.cipherKeys.cipherHeaderKey = getRandomCipherKey();
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
Reference<BlobCipherKey> generateCipherKey(const BlobCipherDetails& cipherDetails) {
|
||||
static unsigned char SHA_KEY[] = "3ab9570b44b8315fdb261da6b1b6c13b";
|
||||
Arena arena;
|
||||
StringRef digest = computeAuthToken(reinterpret_cast<const unsigned char*>(&cipherDetails.baseCipherId),
|
||||
sizeof(EncryptCipherBaseKeyId),
|
||||
SHA_KEY,
|
||||
AES_256_KEY_LENGTH,
|
||||
arena);
|
||||
return makeReference<BlobCipherKey>(cipherDetails.encryptDomainId,
|
||||
cipherDetails.baseCipherId,
|
||||
digest.begin(),
|
||||
AES_256_KEY_LENGTH,
|
||||
cipherDetails.salt,
|
||||
std::numeric_limits<int64_t>::max() /* refreshAt */,
|
||||
std::numeric_limits<int64_t>::max() /* expireAt */);
|
||||
}
|
||||
|
||||
Reference<BlobCipherKey> getRandomCipherKey() {
|
||||
return cipherKeys[deterministicRandom()->randomInt(0, NUM_CIPHER)];
|
||||
}
|
||||
|
||||
static constexpr int NUM_CIPHER = 1000;
|
||||
Reference<BlobCipherKey> cipherKeys[NUM_CIPHER];
|
||||
};
|
||||
|
||||
// Key provider which extract tenant id from range key prefixes, and fetch tenant specific encryption keys from
|
||||
// EncryptKeyProxy.
|
||||
class TenantAwareEncryptionKeyProvider : public IEncryptionKeyProvider {
|
||||
public:
|
||||
TenantAwareEncryptionKeyProvider(Reference<AsyncVar<ServerDBInfo> const> db) : db(db) {}
|
||||
|
||||
virtual ~TenantAwareEncryptionKeyProvider() = default;
|
||||
|
||||
bool shouldEnableEncryption() const override {
|
||||
return isEncryptionOpSupported(EncryptOperationType::STORAGE_SERVER_ENCRYPTION, db->get().client);
|
||||
}
|
||||
|
||||
ACTOR static Future<EncryptionKey> getSecrets(TenantAwareEncryptionKeyProvider* self, EncryptionKeyRef key) {
|
||||
if (!key.cipherHeader.present()) {
|
||||
TraceEvent("TenantAwareEncryptionKeyProvider_CipherHeaderMissing");
|
||||
throw encrypt_ops_error();
|
||||
}
|
||||
TextAndHeaderCipherKeys cipherKeys =
|
||||
wait(getEncryptCipherKeys(self->db, key.cipherHeader.get(), BlobCipherMetrics::KV_REDWOOD));
|
||||
EncryptionKey s = key;
|
||||
s.cipherKeys = cipherKeys;
|
||||
return s;
|
||||
}
|
||||
|
||||
Future<EncryptionKey> getSecrets(const EncryptionKeyRef& key) override { return getSecrets(this, key); }
|
||||
|
||||
ACTOR static Future<EncryptionKey> getByRange(TenantAwareEncryptionKeyProvider* self, KeyRef begin, KeyRef end) {
|
||||
EncryptCipherDomainNameRef domainName;
|
||||
EncryptCipherDomainId domainId = self->getEncryptionDomainId(begin, end, &domainName);
|
||||
TextAndHeaderCipherKeys cipherKeys =
|
||||
wait(getLatestEncryptCipherKeysForDomain(self->db, domainId, domainName, BlobCipherMetrics::KV_REDWOOD));
|
||||
EncryptionKey s;
|
||||
s.cipherKeys = cipherKeys;
|
||||
return s;
|
||||
}
|
||||
|
||||
Future<EncryptionKey> getByRange(const KeyRef& begin, const KeyRef& end) override {
|
||||
return getByRange(this, begin, end);
|
||||
}
|
||||
|
||||
void setTenantPrefixIndex(Reference<TenantPrefixIndex> tenantPrefixIndex) override {
|
||||
ASSERT(tenantPrefixIndex.isValid());
|
||||
this->tenantPrefixIndex = tenantPrefixIndex;
|
||||
}
|
||||
|
||||
private:
|
||||
EncryptCipherDomainId getEncryptionDomainId(const KeyRef& begin,
|
||||
const KeyRef& end,
|
||||
EncryptCipherDomainNameRef* domainName) {
|
||||
int64_t domainId = SYSTEM_KEYSPACE_ENCRYPT_DOMAIN_ID;
|
||||
int64_t beginTenantId = getTenantId(begin, true /*inclusive*/);
|
||||
int64_t endTenantId = getTenantId(end, false /*inclusive*/);
|
||||
if (beginTenantId == endTenantId && beginTenantId != SYSTEM_KEYSPACE_ENCRYPT_DOMAIN_ID) {
|
||||
ASSERT(tenantPrefixIndex.isValid());
|
||||
Key tenantPrefix = TenantMapEntry::idToPrefix(beginTenantId);
|
||||
auto view = tenantPrefixIndex->atLatest();
|
||||
auto itr = view.find(tenantPrefix);
|
||||
if (itr != view.end()) {
|
||||
*domainName = *itr;
|
||||
domainId = beginTenantId;
|
||||
} else {
|
||||
// No tenant with the same tenant id. We could be in optional or disabled tenant mode.
|
||||
}
|
||||
}
|
||||
if (domainId == SYSTEM_KEYSPACE_ENCRYPT_DOMAIN_ID) {
|
||||
*domainName = FDB_SYSTEM_KEYSPACE_ENCRYPT_DOMAIN_NAME;
|
||||
}
|
||||
return domainId;
|
||||
}
|
||||
|
||||
int64_t getTenantId(const KeyRef& key, bool inclusive) {
|
||||
// A valid tenant id is always a valid encrypt domain id.
|
||||
static_assert(INVALID_ENCRYPT_DOMAIN_ID == -1);
|
||||
|
||||
if (key.size() && key >= systemKeys.begin) {
|
||||
return SYSTEM_KEYSPACE_ENCRYPT_DOMAIN_ID;
|
||||
}
|
||||
|
||||
if (key.size() < TENANT_PREFIX_SIZE) {
|
||||
// Encryption domain information not available, leverage 'default encryption domain'
|
||||
return FDB_DEFAULT_ENCRYPT_DOMAIN_ID;
|
||||
}
|
||||
|
||||
StringRef prefix = key.substr(0, TENANT_PREFIX_SIZE);
|
||||
int64_t tenantId = TenantMapEntry::prefixToId(prefix, EnforceValidTenantId::False);
|
||||
if (tenantId == TenantInfo::INVALID_TENANT) {
|
||||
// Encryption domain information not available, leverage 'default encryption domain'
|
||||
return FDB_DEFAULT_ENCRYPT_DOMAIN_ID;
|
||||
}
|
||||
|
||||
if (!inclusive && key.size() == TENANT_PREFIX_SIZE) {
|
||||
tenantId = tenantId - 1;
|
||||
}
|
||||
ASSERT(tenantId >= 0);
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
Reference<AsyncVar<ServerDBInfo> const> db;
|
||||
Reference<TenantPrefixIndex> tenantPrefixIndex;
|
||||
};
|
||||
|
||||
#include "flow/unactorcompiler.h"
|
||||
#endif
|
|
@ -26,7 +26,8 @@
|
|||
#include "fdbclient/StorageCheckpoint.h"
|
||||
#include "fdbclient/Tenant.h"
|
||||
#include "fdbserver/Knobs.h"
|
||||
#include "fdbserver/IEncryptionKeyProvider.actor.h"
|
||||
#include "fdbserver/IClosable.h"
|
||||
#include "fdbserver/IPageEncryptionKeyProvider.actor.h"
|
||||
#include "fdbserver/ServerDBInfo.h"
|
||||
|
||||
struct CheckpointRequest {
|
||||
|
@ -44,22 +45,6 @@ struct CheckpointRequest {
|
|||
: version(version), range(range), format(format), checkpointID(id), checkpointDir(checkpointDir) {}
|
||||
};
|
||||
|
||||
class IClosable {
|
||||
public:
|
||||
// IClosable is a base interface for any disk-backed data structure that needs to support asynchronous errors,
|
||||
// shutdown and deletion
|
||||
|
||||
virtual Future<Void> getError()
|
||||
const = 0; // asynchronously throws an error if there is an internal error. Never set
|
||||
// inside (on the stack of) a call to another API function on this object.
|
||||
virtual Future<Void> onClosed()
|
||||
const = 0; // the future is set to Void when this is totally shut down after dispose() or
|
||||
// close(). But this function cannot be called after dispose or close!
|
||||
virtual void dispose() = 0; // permanently delete the data AND invalidate this interface
|
||||
virtual void close() = 0; // invalidate this interface, but do not delete the data. Outstanding operations may or
|
||||
// may not take effect in the background.
|
||||
};
|
||||
|
||||
class IKeyValueStore : public IClosable {
|
||||
public:
|
||||
virtual KeyValueStoreType getType() const = 0;
|
||||
|
@ -151,7 +136,7 @@ extern IKeyValueStore* keyValueStoreSQLite(std::string const& filename,
|
|||
bool checkIntegrity = false);
|
||||
extern IKeyValueStore* keyValueStoreRedwoodV1(std::string const& filename,
|
||||
UID logID,
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider = {});
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider = {});
|
||||
extern IKeyValueStore* keyValueStoreRocksDB(std::string const& path,
|
||||
UID logID,
|
||||
KeyValueStoreType storeType,
|
||||
|
@ -190,7 +175,7 @@ inline IKeyValueStore* openKVStore(KeyValueStoreType storeType,
|
|||
bool checkChecksums = false,
|
||||
bool checkIntegrity = false,
|
||||
bool openRemotely = false,
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider = {}) {
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider = {}) {
|
||||
if (openRemotely) {
|
||||
return openRemoteKVStore(storeType, filename, logID, memoryLimit, checkChecksums, checkIntegrity);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,346 @@
|
|||
/*
|
||||
* IPageEncryptionKeyProvider.actor.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2022 Apple Inc. and the FoundationDB project authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#if defined(NO_INTELLISENSE) && !defined(FDBSERVER_IPAGEENCRYPTIONKEYPROVIDER_ACTOR_G_H)
|
||||
#define FDBSERVER_IPAGEENCRYPTIONKEYPROVIDER_ACTOR_G_H
|
||||
#include "fdbserver/IPageEncryptionKeyProvider.actor.g.h"
|
||||
#elif !defined(FDBSERVER_IPAGEENCRYPTIONKEYPROVIDER_ACTOR_H)
|
||||
#define FDBSERVER_IPAGEENCRYPTIONKEYPROVIDER_ACTOR_H
|
||||
|
||||
#include "fdbclient/GetEncryptCipherKeys.actor.h"
|
||||
#include "fdbclient/Tenant.h"
|
||||
|
||||
#include "fdbserver/EncryptionOpsUtils.h"
|
||||
#include "fdbserver/IPager.h"
|
||||
#include "fdbserver/ServerDBInfo.h"
|
||||
|
||||
#include "flow/Arena.h"
|
||||
#include "flow/EncryptUtils.h"
|
||||
#define XXH_INLINE_ALL
|
||||
#include "flow/xxhash.h"
|
||||
|
||||
#include <tuple>
|
||||
|
||||
#include "flow/actorcompiler.h" // This must be the last #include.
|
||||
|
||||
// Interface used by pager to get encryption keys reading pages from disk
|
||||
// and by the BTree to get encryption keys to use for new pages.
|
||||
//
|
||||
// Cipher key rotation:
|
||||
// The key provider can rotate encryption keys, potentially per encryption domain (see below). Each of the new pages
|
||||
// are encrypted using the latest encryption keys.
|
||||
//
|
||||
// Encryption domains:
|
||||
// The key provider can specify how the page split the full key range into encryption domains by key prefixes.
|
||||
// Encryption domains are expected to have their own set of encryption keys, which is managed by the key provider.
|
||||
// The pager will ensure that data from different encryption domain won't fall in the same page, to make
|
||||
// sure it can use one single encryption key to encrypt the whole page.
|
||||
// The key provider needs to provide a default encryption domain, which is used to encrypt pages contain only
|
||||
// full or partial encryption domain prefixes.
|
||||
class IPageEncryptionKeyProvider : public ReferenceCounted<IPageEncryptionKeyProvider> {
|
||||
public:
|
||||
using EncryptionKey = ArenaPage::EncryptionKey;
|
||||
|
||||
virtual ~IPageEncryptionKeyProvider() = default;
|
||||
|
||||
// Expected encoding type being used with the encryption key provider.
|
||||
virtual EncodingType expectedEncodingType() const = 0;
|
||||
|
||||
// Checks whether encryption should be enabled. If not, the encryption key provider will not be used by
|
||||
// the pager, and instead the default non-encrypted encoding type (XXHash64) is used.
|
||||
virtual bool enableEncryption() const = 0;
|
||||
|
||||
// Whether encryption domain is enabled.
|
||||
virtual bool enableEncryptionDomain() const { return false; }
|
||||
|
||||
// Get an encryption key from given encoding header.
|
||||
virtual Future<EncryptionKey> getEncryptionKey(void* encodingHeader) { throw not_implemented(); }
|
||||
|
||||
// Get latest encryption key. If encryption domain is enabled, get encryption key for the default domain.
|
||||
virtual Future<EncryptionKey> getLatestDefaultEncryptionKey() { throw not_implemented(); }
|
||||
|
||||
// Get latest encryption key for data in given encryption domain.
|
||||
virtual Future<EncryptionKey> getLatestEncryptionKey(int64_t domainId) { throw not_implemented(); }
|
||||
|
||||
// Return the default encryption domain.
|
||||
virtual int64_t getDefaultEncryptionDomainId() const { throw not_implemented(); }
|
||||
|
||||
// Get encryption domain from a key. Return the domain id, and the size of the encryption domain prefix.
|
||||
// It is assumed that all keys with the same encryption domain prefix as the given key falls in the same encryption
|
||||
// domain. If possibleDomainId is given, it is a valid domain id previously returned by the key provider,
|
||||
// potentially for a different key. The possibleDomainId parm is used by TenantAwareEncryptionKeyProvider to speed
|
||||
// up encryption domain lookup.
|
||||
virtual std::tuple<int64_t, size_t> getEncryptionDomain(const KeyRef& key,
|
||||
Optional<int64_t> possibleDomainId = Optional<int64_t>()) {
|
||||
throw not_implemented();
|
||||
}
|
||||
|
||||
// Get encryption domain of a page given encoding header.
|
||||
virtual int64_t getEncryptionDomain(void* encodingHeader) { throw not_implemented(); }
|
||||
|
||||
// Setting tenant prefix to tenant name map. Used by TenantAwareEncryptionKeyProvider.
|
||||
virtual void setTenantPrefixIndex(Reference<TenantPrefixIndex> tenantPrefixIndex) {}
|
||||
};
|
||||
|
||||
// The null key provider is useful to simplify page decoding.
|
||||
// It throws an error for any key info requested.
|
||||
class NullKeyProvider : public IPageEncryptionKeyProvider {
|
||||
public:
|
||||
virtual ~NullKeyProvider() {}
|
||||
EncodingType expectedEncodingType() const override { return EncodingType::XXHash64; }
|
||||
bool enableEncryption() const override { return false; }
|
||||
};
|
||||
|
||||
// Key provider for dummy XOR encryption scheme
|
||||
class XOREncryptionKeyProvider_TestOnly : public IPageEncryptionKeyProvider {
|
||||
public:
|
||||
using EncodingHeader = ArenaPage::XOREncryptionEncoder::Header;
|
||||
|
||||
XOREncryptionKeyProvider_TestOnly(std::string filename) {
|
||||
ASSERT(g_network->isSimulated());
|
||||
|
||||
// Choose a deterministic random filename (without path) byte for secret generation
|
||||
// Remove any leading directory names
|
||||
size_t lastSlash = filename.find_last_of("\\/");
|
||||
if (lastSlash != filename.npos) {
|
||||
filename.erase(0, lastSlash);
|
||||
}
|
||||
xorWith = filename.empty() ? 0x5e
|
||||
: (uint8_t)filename[XXH3_64bits(filename.data(), filename.size()) % filename.size()];
|
||||
}
|
||||
|
||||
virtual ~XOREncryptionKeyProvider_TestOnly() {}
|
||||
|
||||
EncodingType expectedEncodingType() const override { return EncodingType::XOREncryption_TestOnly; }
|
||||
|
||||
bool enableEncryption() const override { return true; }
|
||||
|
||||
bool enableEncryptionDomain() const override { return true; }
|
||||
|
||||
Future<EncryptionKey> getEncryptionKey(void* encodingHeader) override {
|
||||
|
||||
EncodingHeader* h = reinterpret_cast<EncodingHeader*>(encodingHeader);
|
||||
EncryptionKey s;
|
||||
s.xorKey = h->xorKey;
|
||||
return s;
|
||||
}
|
||||
|
||||
Future<EncryptionKey> getLatestDefaultEncryptionKey() override { return getLatestEncryptionKey(0); }
|
||||
|
||||
Future<EncryptionKey> getLatestEncryptionKey(int64_t domainId) override {
|
||||
EncryptionKey s;
|
||||
s.xorKey = ~(uint8_t)domainId ^ xorWith;
|
||||
return s;
|
||||
}
|
||||
|
||||
int64_t getDefaultEncryptionDomainId() const override { return 0; }
|
||||
|
||||
std::tuple<int64_t, size_t> getEncryptionDomain(const KeyRef& key,
|
||||
Optional<int64_t> /*possibleDomainId*/) override {
|
||||
if (key.size() > 0) {
|
||||
return { *key.begin(), 1 };
|
||||
}
|
||||
return { 0, 0 };
|
||||
}
|
||||
|
||||
int64_t getEncryptionDomain(void* encodingHeader) override {
|
||||
uint8_t xorKey = reinterpret_cast<EncodingHeader*>(encodingHeader)->xorKey;
|
||||
return (int64_t)(~xorKey ^ xorWith);
|
||||
}
|
||||
|
||||
uint8_t xorWith;
|
||||
};
|
||||
|
||||
// Key provider to provider cipher keys randomly from a pre-generated pool. It does not maintain encryption domains.
|
||||
// Use for testing.
|
||||
class RandomEncryptionKeyProvider : public IPageEncryptionKeyProvider {
|
||||
public:
|
||||
RandomEncryptionKeyProvider() {
|
||||
for (unsigned i = 0; i < NUM_CIPHER; i++) {
|
||||
BlobCipherDetails cipherDetails;
|
||||
cipherDetails.encryptDomainId = i;
|
||||
cipherDetails.baseCipherId = deterministicRandom()->randomUInt64();
|
||||
cipherDetails.salt = deterministicRandom()->randomUInt64();
|
||||
cipherKeys[i] = generateCipherKey(cipherDetails);
|
||||
}
|
||||
}
|
||||
virtual ~RandomEncryptionKeyProvider() = default;
|
||||
|
||||
EncodingType expectedEncodingType() const override { return EncodingType::AESEncryptionV1; }
|
||||
|
||||
bool enableEncryption() const override { return true; }
|
||||
|
||||
Future<EncryptionKey> getEncryptionKey(void* encodingHeader) override {
|
||||
using Header = ArenaPage::AESEncryptionV1Encoder::Header;
|
||||
Header* h = reinterpret_cast<Header*>(encodingHeader);
|
||||
EncryptionKey s;
|
||||
s.aesKey.cipherTextKey = cipherKeys[h->cipherTextDetails.encryptDomainId];
|
||||
s.aesKey.cipherHeaderKey = cipherKeys[h->cipherHeaderDetails.encryptDomainId];
|
||||
return s;
|
||||
}
|
||||
|
||||
Future<EncryptionKey> getLatestDefaultEncryptionKey() override {
|
||||
EncryptionKey s;
|
||||
s.aesKey.cipherTextKey = cipherKeys[deterministicRandom()->randomInt(0, NUM_CIPHER)];
|
||||
s.aesKey.cipherHeaderKey = cipherKeys[deterministicRandom()->randomInt(0, NUM_CIPHER)];
|
||||
return s;
|
||||
}
|
||||
|
||||
private:
|
||||
Reference<BlobCipherKey> generateCipherKey(const BlobCipherDetails& cipherDetails) {
|
||||
static unsigned char SHA_KEY[] = "3ab9570b44b8315fdb261da6b1b6c13b";
|
||||
Arena arena;
|
||||
StringRef digest = computeAuthToken(reinterpret_cast<const unsigned char*>(&cipherDetails.baseCipherId),
|
||||
sizeof(EncryptCipherBaseKeyId),
|
||||
SHA_KEY,
|
||||
AES_256_KEY_LENGTH,
|
||||
arena);
|
||||
return makeReference<BlobCipherKey>(cipherDetails.encryptDomainId,
|
||||
cipherDetails.baseCipherId,
|
||||
digest.begin(),
|
||||
AES_256_KEY_LENGTH,
|
||||
cipherDetails.salt,
|
||||
std::numeric_limits<int64_t>::max() /* refreshAt */,
|
||||
std::numeric_limits<int64_t>::max() /* expireAt */);
|
||||
}
|
||||
|
||||
static constexpr int NUM_CIPHER = 1000;
|
||||
Reference<BlobCipherKey> cipherKeys[NUM_CIPHER];
|
||||
};
|
||||
|
||||
// Key provider which extract tenant id from range key prefixes, and fetch tenant specific encryption keys from
|
||||
// EncryptKeyProxy.
|
||||
class TenantAwareEncryptionKeyProvider : public IPageEncryptionKeyProvider {
|
||||
public:
|
||||
using EncodingHeader = ArenaPage::AESEncryptionV1Encoder::Header;
|
||||
|
||||
TenantAwareEncryptionKeyProvider(Reference<AsyncVar<ServerDBInfo> const> db) : db(db) {}
|
||||
|
||||
virtual ~TenantAwareEncryptionKeyProvider() = default;
|
||||
|
||||
EncodingType expectedEncodingType() const override { return EncodingType::AESEncryptionV1; }
|
||||
|
||||
bool enableEncryption() const override {
|
||||
return isEncryptionOpSupported(EncryptOperationType::STORAGE_SERVER_ENCRYPTION, db->get().client);
|
||||
}
|
||||
|
||||
bool enableEncryptionDomain() const override { return true; }
|
||||
|
||||
ACTOR static Future<EncryptionKey> getEncryptionKey(TenantAwareEncryptionKeyProvider* self, void* encodingHeader) {
|
||||
BlobCipherEncryptHeader* header = reinterpret_cast<EncodingHeader*>(encodingHeader);
|
||||
TextAndHeaderCipherKeys cipherKeys =
|
||||
wait(getEncryptCipherKeys(self->db, *header, BlobCipherMetrics::KV_REDWOOD));
|
||||
EncryptionKey encryptionKey;
|
||||
encryptionKey.aesKey = cipherKeys;
|
||||
return encryptionKey;
|
||||
}
|
||||
|
||||
Future<EncryptionKey> getEncryptionKey(void* encodingHeader) override {
|
||||
return getEncryptionKey(this, encodingHeader);
|
||||
}
|
||||
|
||||
Future<EncryptionKey> getLatestDefaultEncryptionKey() override {
|
||||
return getLatestEncryptionKey(getDefaultEncryptionDomainId());
|
||||
}
|
||||
|
||||
ACTOR static Future<EncryptionKey> getLatestEncryptionKey(TenantAwareEncryptionKeyProvider* self,
|
||||
int64_t domainId) {
|
||||
|
||||
EncryptCipherDomainNameRef domainName = self->getDomainName(domainId);
|
||||
TextAndHeaderCipherKeys cipherKeys =
|
||||
wait(getLatestEncryptCipherKeysForDomain(self->db, domainId, domainName, BlobCipherMetrics::KV_REDWOOD));
|
||||
EncryptionKey encryptionKey;
|
||||
encryptionKey.aesKey = cipherKeys;
|
||||
return encryptionKey;
|
||||
}
|
||||
|
||||
Future<EncryptionKey> getLatestEncryptionKey(int64_t domainId) override {
|
||||
return getLatestEncryptionKey(this, domainId);
|
||||
}
|
||||
|
||||
int64_t getDefaultEncryptionDomainId() const override { return FDB_DEFAULT_ENCRYPT_DOMAIN_ID; }
|
||||
|
||||
std::tuple<int64_t, size_t> getEncryptionDomain(const KeyRef& key, Optional<int64_t> possibleDomainId) override {
|
||||
// System key.
|
||||
if (key.startsWith(LiteralStringRef("\xff\xff"))) {
|
||||
return { SYSTEM_KEYSPACE_ENCRYPT_DOMAIN_ID, 2 };
|
||||
}
|
||||
// Key smaller than tenant prefix in size belongs to the default domain.
|
||||
if (key.size() < TENANT_PREFIX_SIZE) {
|
||||
return { FDB_DEFAULT_ENCRYPT_DOMAIN_ID, 0 };
|
||||
}
|
||||
StringRef prefix = key.substr(0, TENANT_PREFIX_SIZE);
|
||||
int64_t tenantId = TenantMapEntry::prefixToId(prefix);
|
||||
// Tenant id must be non-negative.
|
||||
if (tenantId < 0) {
|
||||
return { FDB_DEFAULT_ENCRYPT_DOMAIN_ID, 0 };
|
||||
}
|
||||
// Optimization: Caller guarantee possibleDomainId is a valid domain id that we previously returned.
|
||||
// We can return immediately without checking with tenant map.
|
||||
if (possibleDomainId.present() && possibleDomainId.get() == tenantId) {
|
||||
return { tenantId, TENANT_PREFIX_SIZE };
|
||||
}
|
||||
if (tenantPrefixIndex.isValid()) {
|
||||
auto view = tenantPrefixIndex->atLatest();
|
||||
auto itr = view.find(prefix);
|
||||
if (itr != view.end()) {
|
||||
// Tenant not found. Tenant must be disabled, or in optional mode.
|
||||
return { tenantId, TENANT_PREFIX_SIZE };
|
||||
}
|
||||
}
|
||||
// The prefix does not belong to any tenant. The key belongs to the default domain.
|
||||
return { FDB_DEFAULT_ENCRYPT_DOMAIN_ID, 0 };
|
||||
}
|
||||
|
||||
int64_t getEncryptionDomain(void* encodingHeader) override {
|
||||
BlobCipherEncryptHeader* header = reinterpret_cast<EncodingHeader*>(encodingHeader);
|
||||
return header->cipherTextDetails.encryptDomainId;
|
||||
}
|
||||
|
||||
void setTenantPrefixIndex(Reference<TenantPrefixIndex> tenantPrefixIndex) override {
|
||||
this->tenantPrefixIndex = tenantPrefixIndex;
|
||||
}
|
||||
|
||||
private:
|
||||
EncryptCipherDomainNameRef getDomainName(int64_t domainId) {
|
||||
if (domainId == SYSTEM_KEYSPACE_ENCRYPT_DOMAIN_ID) {
|
||||
return FDB_SYSTEM_KEYSPACE_ENCRYPT_DOMAIN_NAME;
|
||||
}
|
||||
if (domainId == FDB_DEFAULT_ENCRYPT_DOMAIN_ID) {
|
||||
return FDB_DEFAULT_ENCRYPT_DOMAIN_NAME;
|
||||
}
|
||||
if (tenantPrefixIndex.isValid()) {
|
||||
StringRef prefix = TenantMapEntry::idToPrefix(domainId);
|
||||
auto view = tenantPrefixIndex->atLatest();
|
||||
auto itr = view.find(prefix);
|
||||
if (itr != view.end()) {
|
||||
return *itr;
|
||||
}
|
||||
}
|
||||
TraceEvent(SevWarn, "TenantAwareEncryptionKeyProvider_TenantNotFoundForDomain").detail("DomainId", domainId);
|
||||
throw tenant_not_found();
|
||||
}
|
||||
|
||||
Reference<AsyncVar<ServerDBInfo> const> db;
|
||||
Reference<TenantPrefixIndex> tenantPrefixIndex;
|
||||
};
|
||||
|
||||
#include "flow/unactorcompiler.h"
|
||||
#endif
|
|
@ -19,16 +19,15 @@
|
|||
*/
|
||||
#pragma once
|
||||
|
||||
#include "fdbclient/BlobCipher.h"
|
||||
#ifndef FDBSERVER_IPAGER_H
|
||||
#define FDBSERVER_IPAGER_H
|
||||
#include <cstddef>
|
||||
#include <stdint.h>
|
||||
#include "fdbclient/BlobCipher.h"
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
#include "fdbclient/GetEncryptCipherKeys.actor.h"
|
||||
#include "fdbclient/Tenant.h"
|
||||
#include "fdbserver/IEncryptionKeyProvider.actor.h"
|
||||
#include "fdbserver/IKeyValueStore.h"
|
||||
#include "fdbserver/IClosable.h"
|
||||
#include "flow/Error.h"
|
||||
#include "flow/FastAlloc.h"
|
||||
#include "flow/flow.h"
|
||||
|
@ -221,6 +220,14 @@ public:
|
|||
uint8_t* rawData() { return buffer; }
|
||||
int rawSize() const { return bufferSize; }
|
||||
|
||||
// Encryption key used to encrypt a page. Different encoding types may use different structs to represent
|
||||
// an encryption key, and EncryptionKeyRef is a union of these structs.
|
||||
struct EncryptionKeyRef {
|
||||
TextAndHeaderCipherKeys aesKey; // For AESEncryptionV1
|
||||
uint8_t xorKey; // For XOREncryption_TestOnly
|
||||
};
|
||||
using EncryptionKey = Standalone<EncryptionKeyRef>;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
// The next few structs describe the byte-packed physical structure. The fields of Page
|
||||
|
@ -245,10 +252,7 @@ public:
|
|||
}
|
||||
|
||||
// Get encoding header pointer, casting to its type
|
||||
template <typename T>
|
||||
T* getEncodingHeader() const {
|
||||
return (T*)((uint8_t*)this + encodingHeaderOffset);
|
||||
}
|
||||
void* getEncodingHeader() const { return (uint8_t*)this + encodingHeaderOffset; }
|
||||
|
||||
// Get payload pointer
|
||||
uint8_t* getPayload() const { return (uint8_t*)this + payloadOffset; }
|
||||
|
@ -304,12 +308,18 @@ public:
|
|||
|
||||
// An encoding that validates the payload with an XXHash checksum
|
||||
struct XXHashEncoder {
|
||||
XXH64_hash_t checksum;
|
||||
void encode(uint8_t* payload, int len, PhysicalPageID seed) {
|
||||
checksum = XXH3_64bits_withSeed(payload, len, seed);
|
||||
struct Header {
|
||||
XXH64_hash_t checksum;
|
||||
};
|
||||
|
||||
static void encode(void* header, uint8_t* payload, int len, PhysicalPageID seed) {
|
||||
Header* h = reinterpret_cast<Header*>(header);
|
||||
h->checksum = XXH3_64bits_withSeed(payload, len, seed);
|
||||
}
|
||||
void decode(uint8_t* payload, int len, PhysicalPageID seed) {
|
||||
if (checksum != XXH3_64bits_withSeed(payload, len, seed)) {
|
||||
|
||||
static void decode(void* header, uint8_t* payload, int len, PhysicalPageID seed) {
|
||||
Header* h = reinterpret_cast<Header*>(header);
|
||||
if (h->checksum != XXH3_64bits_withSeed(payload, len, seed)) {
|
||||
throw page_decoding_failed();
|
||||
}
|
||||
}
|
||||
|
@ -318,45 +328,61 @@ public:
|
|||
// A dummy "encrypting" encoding which uses XOR with a 1 byte secret key on
|
||||
// the payload to obfuscate it and protects the payload with an XXHash checksum.
|
||||
struct XOREncryptionEncoder {
|
||||
// Checksum is on unencrypted payload
|
||||
XXH64_hash_t checksum;
|
||||
uint8_t keyID;
|
||||
struct Header {
|
||||
// Checksum is on unencrypted payload
|
||||
XXH64_hash_t checksum;
|
||||
uint8_t xorKey;
|
||||
};
|
||||
|
||||
void encode(uint8_t secret, uint8_t* payload, int len, PhysicalPageID seed) {
|
||||
checksum = XXH3_64bits_withSeed(payload, len, seed);
|
||||
static void encode(void* header,
|
||||
const EncryptionKey& encryptionKey,
|
||||
uint8_t* payload,
|
||||
int len,
|
||||
PhysicalPageID seed) {
|
||||
Header* h = reinterpret_cast<Header*>(header);
|
||||
h->checksum = XXH3_64bits_withSeed(payload, len, seed);
|
||||
h->xorKey = encryptionKey.xorKey;
|
||||
for (int i = 0; i < len; ++i) {
|
||||
payload[i] ^= secret;
|
||||
payload[i] ^= h->xorKey;
|
||||
}
|
||||
}
|
||||
void decode(uint8_t secret, uint8_t* payload, int len, PhysicalPageID seed) {
|
||||
|
||||
static void decode(void* header,
|
||||
const EncryptionKey& encryptionKey,
|
||||
uint8_t* payload,
|
||||
int len,
|
||||
PhysicalPageID seed) {
|
||||
Header* h = reinterpret_cast<Header*>(header);
|
||||
for (int i = 0; i < len; ++i) {
|
||||
payload[i] ^= secret;
|
||||
payload[i] ^= h->xorKey;
|
||||
}
|
||||
if (checksum != XXH3_64bits_withSeed(payload, len, seed)) {
|
||||
if (h->checksum != XXH3_64bits_withSeed(payload, len, seed)) {
|
||||
throw page_decoding_failed();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct AESEncryptionV1Encoder {
|
||||
BlobCipherEncryptHeader header;
|
||||
using Header = BlobCipherEncryptHeader;
|
||||
|
||||
void encode(const TextAndHeaderCipherKeys& cipherKeys, uint8_t* payload, int len) {
|
||||
static void encode(void* header, const TextAndHeaderCipherKeys& cipherKeys, uint8_t* payload, int len) {
|
||||
Header* h = reinterpret_cast<Header*>(header);
|
||||
EncryptBlobCipherAes265Ctr cipher(cipherKeys.cipherTextKey,
|
||||
cipherKeys.cipherHeaderKey,
|
||||
ENCRYPT_HEADER_AUTH_TOKEN_MODE_SINGLE,
|
||||
BlobCipherMetrics::KV_REDWOOD);
|
||||
Arena arena;
|
||||
StringRef ciphertext = cipher.encrypt(payload, len, &header, arena)->toStringRef();
|
||||
StringRef ciphertext = cipher.encrypt(payload, len, h, arena)->toStringRef();
|
||||
ASSERT_EQ(len, ciphertext.size());
|
||||
memcpy(payload, ciphertext.begin(), len);
|
||||
}
|
||||
|
||||
void decode(const TextAndHeaderCipherKeys& cipherKeys, uint8_t* payload, int len) {
|
||||
static void decode(void* header, const TextAndHeaderCipherKeys& cipherKeys, uint8_t* payload, int len) {
|
||||
Header* h = reinterpret_cast<Header*>(header);
|
||||
DecryptBlobCipherAes256Ctr cipher(
|
||||
cipherKeys.cipherTextKey, cipherKeys.cipherHeaderKey, header.iv, BlobCipherMetrics::KV_REDWOOD);
|
||||
cipherKeys.cipherTextKey, cipherKeys.cipherHeaderKey, h->iv, BlobCipherMetrics::KV_REDWOOD);
|
||||
Arena arena;
|
||||
StringRef plaintext = cipher.decrypt(payload, len, header, arena)->toStringRef();
|
||||
StringRef plaintext = cipher.decrypt(payload, len, *h, arena)->toStringRef();
|
||||
ASSERT_EQ(len, plaintext.size());
|
||||
memcpy(payload, plaintext.begin(), len);
|
||||
}
|
||||
|
@ -368,11 +394,11 @@ public:
|
|||
// existing pages, the payload offset is stored in the page.
|
||||
static int encodingHeaderSize(EncodingType t) {
|
||||
if (t == EncodingType::XXHash64) {
|
||||
return sizeof(XXHashEncoder);
|
||||
return sizeof(XXHashEncoder::Header);
|
||||
} else if (t == EncodingType::XOREncryption_TestOnly) {
|
||||
return sizeof(XOREncryptionEncoder);
|
||||
return sizeof(XOREncryptionEncoder::Header);
|
||||
} else if (t == EncodingType::AESEncryptionV1) {
|
||||
return sizeof(AESEncryptionV1Encoder);
|
||||
return sizeof(AESEncryptionV1Encoder::Header);
|
||||
} else {
|
||||
throw page_encoding_not_supported();
|
||||
}
|
||||
|
@ -476,15 +502,11 @@ public:
|
|||
ASSERT(VALGRIND_CHECK_MEM_IS_DEFINED(pPayload, payloadSize) == 0);
|
||||
|
||||
if (page->encodingType == EncodingType::XXHash64) {
|
||||
page->getEncodingHeader<XXHashEncoder>()->encode(pPayload, payloadSize, pageID);
|
||||
XXHashEncoder::encode(page->getEncodingHeader(), pPayload, payloadSize, pageID);
|
||||
} else if (page->encodingType == EncodingType::XOREncryption_TestOnly) {
|
||||
ASSERT(encryptionKey.secret.size() == 1);
|
||||
XOREncryptionEncoder* xh = page->getEncodingHeader<XOREncryptionEncoder>();
|
||||
xh->keyID = encryptionKey.id.orDefault(0);
|
||||
xh->encode(encryptionKey.secret[0], pPayload, payloadSize, pageID);
|
||||
XOREncryptionEncoder::encode(page->getEncodingHeader(), encryptionKey, pPayload, payloadSize, pageID);
|
||||
} else if (page->encodingType == EncodingType::AESEncryptionV1) {
|
||||
AESEncryptionV1Encoder* eh = page->getEncodingHeader<AESEncryptionV1Encoder>();
|
||||
eh->encode(encryptionKey.cipherKeys, pPayload, payloadSize);
|
||||
AESEncryptionV1Encoder::encode(page->getEncodingHeader(), encryptionKey.aesKey, pPayload, payloadSize);
|
||||
} else {
|
||||
throw page_encoding_not_supported();
|
||||
}
|
||||
|
@ -507,14 +529,6 @@ public:
|
|||
pPayload = page->getPayload();
|
||||
payloadSize = logicalSize - (pPayload - buffer);
|
||||
|
||||
// Populate encryption key with relevant fields from page
|
||||
if (page->encodingType == EncodingType::XOREncryption_TestOnly) {
|
||||
encryptionKey.id = page->getEncodingHeader<XOREncryptionEncoder>()->keyID;
|
||||
} else if (page->encodingType == EncodingType::AESEncryptionV1) {
|
||||
AESEncryptionV1Encoder* eh = page->getEncodingHeader<AESEncryptionV1Encoder>();
|
||||
encryptionKey.cipherHeader = eh->header;
|
||||
}
|
||||
|
||||
if (page->headerVersion == 1) {
|
||||
if (verify) {
|
||||
RedwoodHeaderV1* h = page->getMainHeader<RedwoodHeaderV1>();
|
||||
|
@ -532,13 +546,11 @@ public:
|
|||
// Post: Payload has been verified and decrypted if necessary
|
||||
void postReadPayload(PhysicalPageID pageID) {
|
||||
if (page->encodingType == EncodingType::XXHash64) {
|
||||
page->getEncodingHeader<XXHashEncoder>()->decode(pPayload, payloadSize, pageID);
|
||||
XXHashEncoder::decode(page->getEncodingHeader(), pPayload, payloadSize, pageID);
|
||||
} else if (page->encodingType == EncodingType::XOREncryption_TestOnly) {
|
||||
ASSERT(encryptionKey.secret.size() == 1);
|
||||
page->getEncodingHeader<XOREncryptionEncoder>()->decode(
|
||||
encryptionKey.secret[0], pPayload, payloadSize, pageID);
|
||||
XOREncryptionEncoder::decode(page->getEncodingHeader(), encryptionKey, pPayload, payloadSize, pageID);
|
||||
} else if (page->encodingType == EncodingType::AESEncryptionV1) {
|
||||
page->getEncodingHeader<AESEncryptionV1Encoder>()->decode(encryptionKey.cipherKeys, pPayload, payloadSize);
|
||||
AESEncryptionV1Encoder::decode(page->getEncodingHeader(), encryptionKey.aesKey, pPayload, payloadSize);
|
||||
} else {
|
||||
throw page_encoding_not_supported();
|
||||
}
|
||||
|
@ -553,6 +565,8 @@ public:
|
|||
// Returns true if the page's encoding type employs encryption
|
||||
bool isEncrypted() const { return isEncodingTypeEncrypted(getEncodingType()); }
|
||||
|
||||
void* getEncodingHeader() { return page->getEncodingHeader(); }
|
||||
|
||||
private:
|
||||
Arena arena;
|
||||
|
||||
|
|
|
@ -1116,7 +1116,7 @@ ACTOR Future<Void> encryptKeyProxyServer(EncryptKeyProxyInterface ei, Reference<
|
|||
class IKeyValueStore;
|
||||
class ServerCoordinators;
|
||||
class IDiskQueue;
|
||||
class IEncryptionKeyProvider;
|
||||
class IPageEncryptionKeyProvider;
|
||||
ACTOR Future<Void> storageServer(IKeyValueStore* persistentData,
|
||||
StorageServerInterface ssi,
|
||||
Tag seedTag,
|
||||
|
@ -1126,7 +1126,7 @@ ACTOR Future<Void> storageServer(IKeyValueStore* persistentData,
|
|||
ReplyPromise<InitializeStorageReply> recruitReply,
|
||||
Reference<AsyncVar<ServerDBInfo> const> db,
|
||||
std::string folder,
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider);
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider);
|
||||
ACTOR Future<Void> storageServer(
|
||||
IKeyValueStore* persistentData,
|
||||
StorageServerInterface ssi,
|
||||
|
@ -1135,7 +1135,7 @@ ACTOR Future<Void> storageServer(
|
|||
Promise<Void> recovered,
|
||||
Reference<IClusterConnectionRecord>
|
||||
connRecord, // changes pssi->id() to be the recovered ID); // changes pssi->id() to be the recovered ID
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider);
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider);
|
||||
ACTOR Future<Void> masterServer(MasterInterface mi,
|
||||
Reference<AsyncVar<ServerDBInfo> const> db,
|
||||
Reference<AsyncVar<Optional<ClusterControllerFullInterface>> const> ccInterface,
|
||||
|
|
|
@ -705,7 +705,7 @@ public:
|
|||
std::map<Version, std::vector<KeyRange>>
|
||||
pendingRemoveRanges; // Pending requests to remove ranges from physical shards
|
||||
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider;
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider;
|
||||
|
||||
bool shardAware; // True if the storage server is aware of the physical shards.
|
||||
|
||||
|
@ -1250,7 +1250,7 @@ public:
|
|||
StorageServer(IKeyValueStore* storage,
|
||||
Reference<AsyncVar<ServerDBInfo> const> const& db,
|
||||
StorageServerInterface const& ssi,
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider)
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider)
|
||||
: tenantPrefixIndex(makeReference<TenantPrefixIndex>()), encryptionKeyProvider(encryptionKeyProvider),
|
||||
shardAware(false), tlogCursorReadsLatencyHistogram(Histogram::getHistogram(STORAGESERVER_HISTOGRAM_GROUP,
|
||||
TLOG_CURSOR_READS_LATENCY_HISTOGRAM,
|
||||
|
@ -10694,7 +10694,7 @@ ACTOR Future<Void> storageServer(IKeyValueStore* persistentData,
|
|||
ReplyPromise<InitializeStorageReply> recruitReply,
|
||||
Reference<AsyncVar<ServerDBInfo> const> db,
|
||||
std::string folder,
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider) {
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider) {
|
||||
state StorageServer self(persistentData, db, ssi, encryptionKeyProvider);
|
||||
self.shardAware = SERVER_KNOBS->SHARD_ENCODE_LOCATION_METADATA && persistentData->shardAware();
|
||||
state Future<Void> ssCore;
|
||||
|
@ -10785,7 +10785,7 @@ ACTOR Future<Void> storageServer(IKeyValueStore* persistentData,
|
|||
std::string folder,
|
||||
Promise<Void> recovered,
|
||||
Reference<IClusterConnectionRecord> connRecord,
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider) {
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider) {
|
||||
state StorageServer self(persistentData, db, ssi, encryptionKeyProvider);
|
||||
state Future<Void> ssCore;
|
||||
self.folder = folder;
|
||||
|
|
|
@ -1266,7 +1266,7 @@ ACTOR Future<Void> storageServerRollbackRebooter(std::set<std::pair<UID, KeyValu
|
|||
IKeyValueStore* store,
|
||||
bool validateDataFiles,
|
||||
Promise<Void>* rebootKVStore,
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider) {
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider) {
|
||||
state TrackRunningStorage _(id, storeType, runningStorages);
|
||||
loop {
|
||||
ErrorOr<Void> e = wait(errorOr(prevStorageServer));
|
||||
|
@ -1737,7 +1737,7 @@ ACTOR Future<Void> workerServer(Reference<IClusterConnectionRecord> connRecord,
|
|||
if (s.storedComponent == DiskStore::Storage) {
|
||||
LocalLineage _;
|
||||
getCurrentLineage()->modify(&RoleLineage::role) = ProcessClass::ClusterRole::Storage;
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider =
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider =
|
||||
makeReference<TenantAwareEncryptionKeyProvider>(dbInfo);
|
||||
IKeyValueStore* kv = openKVStore(
|
||||
s.storeType,
|
||||
|
@ -2380,7 +2380,7 @@ ACTOR Future<Void> workerServer(Reference<IClusterConnectionRecord> connRecord,
|
|||
folder,
|
||||
isTss ? testingStoragePrefix.toString() : fileStoragePrefix.toString(),
|
||||
recruited.id());
|
||||
Reference<IEncryptionKeyProvider> encryptionKeyProvider =
|
||||
Reference<IPageEncryptionKeyProvider> encryptionKeyProvider =
|
||||
makeReference<TenantAwareEncryptionKeyProvider>(dbInfo);
|
||||
IKeyValueStore* data = openKVStore(
|
||||
req.storeType,
|
||||
|
|
Loading…
Reference in New Issue