2022-02-24 00:06:39 +08:00
|
|
|
/*
|
|
|
|
* Tenant.cpp
|
|
|
|
*
|
|
|
|
* 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.
|
|
|
|
*/
|
|
|
|
|
2022-07-19 02:24:49 +08:00
|
|
|
#include "fdbclient/NativeAPI.actor.h"
|
2022-02-24 00:06:39 +08:00
|
|
|
#include "fdbclient/SystemData.h"
|
|
|
|
#include "fdbclient/Tenant.h"
|
2022-09-15 01:58:32 +08:00
|
|
|
#include "fdbrpc/TenantInfo.h"
|
|
|
|
#include "flow/BooleanParam.h"
|
2022-09-17 02:20:34 +08:00
|
|
|
#include "flow/IRandom.h"
|
|
|
|
#include "libb64/decode.h"
|
2022-07-26 08:08:32 +08:00
|
|
|
#include "libb64/encode.h"
|
2022-09-02 15:28:13 +08:00
|
|
|
#include "flow/ApiVersion.h"
|
2022-02-24 00:06:39 +08:00
|
|
|
#include "flow/UnitTest.h"
|
|
|
|
|
2022-09-15 01:58:32 +08:00
|
|
|
FDB_DEFINE_BOOLEAN_PARAM(EnforceValidTenantId);
|
|
|
|
|
2022-05-27 03:49:22 +08:00
|
|
|
Key TenantMapEntry::idToPrefix(int64_t id) {
|
|
|
|
int64_t swapped = bigEndian64(id);
|
Support Redwood encryption (#7376)
A new knob `ENABLE_STORAGE_SERVER_ENCRYPTION` is added, which despite its name, currently only Redwood supports it. The knob is mean to be only used in tests to test encryption in individual components, and otherwise enabling encryption should be done through the general `ENABLE_ENCRYPTION` knob.
Under the hood, a new `Encryption` encoding type is added to `IPager`, which use AES-256 to encrypt a page. With this encoding, `BlobCipherEncryptHeader` is inserted into page header for encryption metadata. Moreover, since we compute and store an SHA-256 auth token with the encryption header, we rely on it to checksum the data (and the encryption header), and skip the standard xxhash checksum.
`EncryptionKeyProvider` implements the `IEncryptionKeyProvider` interface to provide encryption keys, which utilizes the existing `getLatestEncryptCipherKey` and `getEncryptCipherKey` actors to fetch encryption keys from either local cache or EKP server. If multi-tenancy is used, for writing a new page, `EncryptionKeyProvider` checks if a page contain only data for a single tenant, if so, fetches tenant specific encryption key; otherwise system encryption key is used. The tenant check is done by extracting tenant id from page bound key prefixes. `EncryptionKeyProvider` also holds a reference of the `tenantPrefixIndex` map maintained by storage server, which is used to check if a tenant do exists, and getting the tenant name in order to get the encryption key.
2022-09-01 03:19:55 +08:00
|
|
|
return StringRef(reinterpret_cast<const uint8_t*>(&swapped), TENANT_PREFIX_SIZE);
|
2022-05-27 03:49:22 +08:00
|
|
|
}
|
|
|
|
|
2022-09-15 01:58:32 +08:00
|
|
|
int64_t TenantMapEntry::prefixToId(KeyRef prefix, EnforceValidTenantId enforceValidTenantId) {
|
Support Redwood encryption (#7376)
A new knob `ENABLE_STORAGE_SERVER_ENCRYPTION` is added, which despite its name, currently only Redwood supports it. The knob is mean to be only used in tests to test encryption in individual components, and otherwise enabling encryption should be done through the general `ENABLE_ENCRYPTION` knob.
Under the hood, a new `Encryption` encoding type is added to `IPager`, which use AES-256 to encrypt a page. With this encoding, `BlobCipherEncryptHeader` is inserted into page header for encryption metadata. Moreover, since we compute and store an SHA-256 auth token with the encryption header, we rely on it to checksum the data (and the encryption header), and skip the standard xxhash checksum.
`EncryptionKeyProvider` implements the `IEncryptionKeyProvider` interface to provide encryption keys, which utilizes the existing `getLatestEncryptCipherKey` and `getEncryptCipherKey` actors to fetch encryption keys from either local cache or EKP server. If multi-tenancy is used, for writing a new page, `EncryptionKeyProvider` checks if a page contain only data for a single tenant, if so, fetches tenant specific encryption key; otherwise system encryption key is used. The tenant check is done by extracting tenant id from page bound key prefixes. `EncryptionKeyProvider` also holds a reference of the `tenantPrefixIndex` map maintained by storage server, which is used to check if a tenant do exists, and getting the tenant name in order to get the encryption key.
2022-09-01 03:19:55 +08:00
|
|
|
ASSERT(prefix.size() == TENANT_PREFIX_SIZE);
|
2022-05-27 03:49:22 +08:00
|
|
|
int64_t id = *reinterpret_cast<const int64_t*>(prefix.begin());
|
|
|
|
id = bigEndian64(id);
|
2022-09-15 01:58:32 +08:00
|
|
|
if (enforceValidTenantId) {
|
|
|
|
ASSERT(id >= 0);
|
|
|
|
} else if (id < 0) {
|
|
|
|
return TenantInfo::INVALID_TENANT;
|
|
|
|
}
|
2022-05-27 03:49:22 +08:00
|
|
|
return id;
|
|
|
|
}
|
|
|
|
|
|
|
|
std::string TenantMapEntry::tenantStateToString(TenantState tenantState) {
|
|
|
|
switch (tenantState) {
|
|
|
|
case TenantState::REGISTERING:
|
|
|
|
return "registering";
|
|
|
|
case TenantState::READY:
|
|
|
|
return "ready";
|
|
|
|
case TenantState::REMOVING:
|
|
|
|
return "removing";
|
2022-07-19 02:24:49 +08:00
|
|
|
case TenantState::UPDATING_CONFIGURATION:
|
|
|
|
return "updating configuration";
|
2022-08-14 03:49:54 +08:00
|
|
|
case TenantState::RENAMING_FROM:
|
|
|
|
return "renaming from";
|
|
|
|
case TenantState::RENAMING_TO:
|
|
|
|
return "renaming to";
|
2022-05-27 03:49:22 +08:00
|
|
|
case TenantState::ERROR:
|
|
|
|
return "error";
|
|
|
|
default:
|
2022-07-15 04:36:59 +08:00
|
|
|
UNREACHABLE();
|
2022-05-27 03:49:22 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TenantState TenantMapEntry::stringToTenantState(std::string stateStr) {
|
|
|
|
if (stateStr == "registering") {
|
|
|
|
return TenantState::REGISTERING;
|
|
|
|
} else if (stateStr == "ready") {
|
|
|
|
return TenantState::READY;
|
|
|
|
} else if (stateStr == "removing") {
|
|
|
|
return TenantState::REMOVING;
|
2022-07-19 02:24:49 +08:00
|
|
|
} else if (stateStr == "updating configuration") {
|
|
|
|
return TenantState::UPDATING_CONFIGURATION;
|
2022-08-14 03:49:54 +08:00
|
|
|
} else if (stateStr == "renaming from") {
|
|
|
|
return TenantState::RENAMING_FROM;
|
|
|
|
} else if (stateStr == "renaming to") {
|
|
|
|
return TenantState::RENAMING_TO;
|
2022-05-27 03:49:22 +08:00
|
|
|
} else if (stateStr == "error") {
|
|
|
|
return TenantState::ERROR;
|
|
|
|
}
|
|
|
|
|
2022-07-15 04:36:59 +08:00
|
|
|
UNREACHABLE();
|
2022-05-27 03:49:22 +08:00
|
|
|
}
|
|
|
|
|
2022-07-30 03:54:40 +08:00
|
|
|
std::string TenantMapEntry::tenantLockStateToString(TenantLockState tenantState) {
|
|
|
|
switch (tenantState) {
|
|
|
|
case TenantLockState::UNLOCKED:
|
|
|
|
return "unlocked";
|
|
|
|
case TenantLockState::READ_ONLY:
|
|
|
|
return "read only";
|
|
|
|
case TenantLockState::LOCKED:
|
|
|
|
return "locked";
|
|
|
|
default:
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
TenantLockState TenantMapEntry::stringToTenantLockState(std::string stateStr) {
|
|
|
|
if (stateStr == "unlocked") {
|
|
|
|
return TenantLockState::UNLOCKED;
|
|
|
|
} else if (stateStr == "read only") {
|
|
|
|
return TenantLockState::READ_ONLY;
|
|
|
|
} else if (stateStr == "locked") {
|
|
|
|
return TenantLockState::LOCKED;
|
|
|
|
}
|
|
|
|
|
|
|
|
UNREACHABLE();
|
|
|
|
}
|
|
|
|
|
2022-05-27 03:49:22 +08:00
|
|
|
TenantMapEntry::TenantMapEntry() {}
|
2022-07-26 08:19:14 +08:00
|
|
|
TenantMapEntry::TenantMapEntry(int64_t id, TenantState tenantState, bool encrypted)
|
|
|
|
: tenantState(tenantState), encrypted(encrypted) {
|
2022-07-21 03:56:00 +08:00
|
|
|
setId(id);
|
|
|
|
}
|
2022-07-26 08:19:14 +08:00
|
|
|
TenantMapEntry::TenantMapEntry(int64_t id,
|
|
|
|
TenantState tenantState,
|
|
|
|
Optional<TenantGroupName> tenantGroup,
|
|
|
|
bool encrypted)
|
|
|
|
: tenantState(tenantState), tenantGroup(tenantGroup), encrypted(encrypted) {
|
2022-07-21 03:56:00 +08:00
|
|
|
setId(id);
|
|
|
|
}
|
|
|
|
|
|
|
|
void TenantMapEntry::setId(int64_t id) {
|
|
|
|
ASSERT(id >= 0);
|
|
|
|
this->id = id;
|
|
|
|
prefix = idToPrefix(id);
|
|
|
|
}
|
2022-05-27 03:49:22 +08:00
|
|
|
|
2022-09-08 23:22:36 +08:00
|
|
|
std::string TenantMapEntry::toJson() const {
|
2022-07-19 02:24:49 +08:00
|
|
|
json_spirit::mObject tenantEntry;
|
|
|
|
tenantEntry["id"] = id;
|
2022-07-26 08:19:14 +08:00
|
|
|
tenantEntry["encrypted"] = encrypted;
|
2022-07-19 02:24:49 +08:00
|
|
|
|
2022-09-08 23:22:36 +08:00
|
|
|
json_spirit::mObject prefixObject;
|
|
|
|
std::string encodedPrefix = base64::encoder::from_string(prefix.toString());
|
|
|
|
// Remove trailing newline
|
|
|
|
encodedPrefix.resize(encodedPrefix.size() - 1);
|
|
|
|
|
|
|
|
prefixObject["base64"] = encodedPrefix;
|
|
|
|
prefixObject["printable"] = printable(prefix);
|
|
|
|
tenantEntry["prefix"] = prefixObject;
|
2022-07-19 02:24:49 +08:00
|
|
|
|
|
|
|
tenantEntry["tenant_state"] = TenantMapEntry::tenantStateToString(tenantState);
|
|
|
|
if (assignedCluster.present()) {
|
|
|
|
tenantEntry["assigned_cluster"] = assignedCluster.get().toString();
|
|
|
|
}
|
|
|
|
if (tenantGroup.present()) {
|
|
|
|
json_spirit::mObject tenantGroupObject;
|
|
|
|
std::string encodedTenantGroup = base64::encoder::from_string(tenantGroup.get().toString());
|
|
|
|
// Remove trailing newline
|
|
|
|
encodedTenantGroup.resize(encodedTenantGroup.size() - 1);
|
|
|
|
|
|
|
|
tenantGroupObject["base64"] = encodedTenantGroup;
|
|
|
|
tenantGroupObject["printable"] = printable(tenantGroup.get());
|
|
|
|
tenantEntry["tenant_group"] = tenantGroupObject;
|
|
|
|
}
|
|
|
|
|
2022-07-23 03:51:24 +08:00
|
|
|
return json_spirit::write_string(json_spirit::mValue(tenantEntry));
|
|
|
|
}
|
2022-07-09 06:56:22 +08:00
|
|
|
|
2022-07-23 03:51:24 +08:00
|
|
|
bool TenantMapEntry::matchesConfiguration(TenantMapEntry const& other) const {
|
2022-08-14 03:49:54 +08:00
|
|
|
return tenantGroup == other.tenantGroup && encrypted == other.encrypted;
|
2022-07-23 03:51:24 +08:00
|
|
|
}
|
2022-07-23 02:38:23 +08:00
|
|
|
|
2022-07-23 03:51:24 +08:00
|
|
|
void TenantMapEntry::configure(Standalone<StringRef> parameter, Optional<Value> value) {
|
|
|
|
if (parameter == "tenant_group"_sr) {
|
|
|
|
tenantGroup = value;
|
2022-09-10 06:03:59 +08:00
|
|
|
} else if (parameter == "assigned_cluster"_sr) {
|
|
|
|
assignedCluster = value;
|
2022-07-23 03:51:24 +08:00
|
|
|
} else {
|
|
|
|
TraceEvent(SevWarnAlways, "UnknownTenantConfigurationParameter").detail("Parameter", parameter);
|
|
|
|
throw invalid_tenant_configuration();
|
|
|
|
}
|
2022-07-19 02:24:49 +08:00
|
|
|
}
|
|
|
|
|
2022-09-23 04:24:21 +08:00
|
|
|
json_spirit::mObject TenantGroupEntry::toJson() const {
|
|
|
|
json_spirit::mObject tenantGroupEntry;
|
|
|
|
if (assignedCluster.present()) {
|
|
|
|
tenantGroupEntry["assigned_cluster"] = assignedCluster.get().toString();
|
|
|
|
}
|
|
|
|
|
|
|
|
return tenantGroupEntry;
|
|
|
|
}
|
|
|
|
|
2022-08-08 23:29:49 +08:00
|
|
|
TenantMetadataSpecification& TenantMetadata::instance() {
|
|
|
|
static TenantMetadataSpecification _instance = TenantMetadataSpecification("\xff/"_sr);
|
|
|
|
return _instance;
|
|
|
|
}
|
|
|
|
|
|
|
|
Key TenantMetadata::tenantMapPrivatePrefix() {
|
|
|
|
static Key _prefix = "\xff"_sr.withSuffix(tenantMap().subspace.begin);
|
|
|
|
return _prefix;
|
|
|
|
}
|
|
|
|
|
2022-09-17 02:20:34 +08:00
|
|
|
TEST_CASE("/fdbclient/libb64/base64decoder") {
|
|
|
|
Standalone<StringRef> buf = makeString(100);
|
|
|
|
for (int i = 0; i < 1000; ++i) {
|
|
|
|
int length = deterministicRandom()->randomInt(0, 100);
|
|
|
|
deterministicRandom()->randomBytes(mutateString(buf), length);
|
|
|
|
|
|
|
|
StringRef str = buf.substr(0, length);
|
|
|
|
std::string encodedStr = base64::encoder::from_string(str.toString());
|
|
|
|
// Remove trailing newline
|
|
|
|
encodedStr.resize(encodedStr.size() - 1);
|
|
|
|
|
|
|
|
std::string decodedStr = base64::decoder::from_string(encodedStr);
|
|
|
|
ASSERT(decodedStr == str.toString());
|
|
|
|
}
|
|
|
|
|
|
|
|
return Void();
|
|
|
|
}
|
|
|
|
|
2022-02-24 00:06:39 +08:00
|
|
|
TEST_CASE("/fdbclient/TenantMapEntry/Serialization") {
|
2022-07-26 08:19:14 +08:00
|
|
|
TenantMapEntry entry1(1, TenantState::READY, false);
|
2022-02-24 00:06:39 +08:00
|
|
|
ASSERT(entry1.prefix == "\x00\x00\x00\x00\x00\x00\x00\x01"_sr);
|
2022-06-30 01:58:58 +08:00
|
|
|
TenantMapEntry entry2 = TenantMapEntry::decode(entry1.encode());
|
2022-02-24 00:06:39 +08:00
|
|
|
ASSERT(entry1.id == entry2.id && entry1.prefix == entry2.prefix);
|
|
|
|
|
2022-07-26 08:19:14 +08:00
|
|
|
TenantMapEntry entry3(std::numeric_limits<int64_t>::max(), TenantState::READY, false);
|
2022-07-20 05:32:05 +08:00
|
|
|
ASSERT(entry3.prefix == "\x7f\xff\xff\xff\xff\xff\xff\xff"_sr);
|
2022-06-30 01:58:58 +08:00
|
|
|
TenantMapEntry entry4 = TenantMapEntry::decode(entry3.encode());
|
2022-02-24 00:06:39 +08:00
|
|
|
ASSERT(entry3.id == entry4.id && entry3.prefix == entry4.prefix);
|
|
|
|
|
|
|
|
for (int i = 0; i < 100; ++i) {
|
|
|
|
int bits = deterministicRandom()->randomInt(1, 64);
|
2022-05-25 05:58:47 +08:00
|
|
|
int64_t min = bits == 1 ? 0 : (UINT64_C(1) << (bits - 1));
|
|
|
|
int64_t maxPlusOne = std::min<uint64_t>(UINT64_C(1) << bits, std::numeric_limits<int64_t>::max());
|
2022-02-24 00:06:39 +08:00
|
|
|
int64_t id = deterministicRandom()->randomInt64(min, maxPlusOne);
|
|
|
|
|
2022-07-26 08:19:14 +08:00
|
|
|
TenantMapEntry entry(id, TenantState::READY, false);
|
2022-02-24 00:06:39 +08:00
|
|
|
int64_t bigEndianId = bigEndian64(id);
|
2022-07-20 05:32:05 +08:00
|
|
|
ASSERT(entry.id == id && entry.prefix == StringRef(reinterpret_cast<uint8_t*>(&bigEndianId), 8));
|
2022-02-24 00:06:39 +08:00
|
|
|
|
2022-06-30 01:58:58 +08:00
|
|
|
TenantMapEntry decodedEntry = TenantMapEntry::decode(entry.encode());
|
2022-05-25 05:58:47 +08:00
|
|
|
ASSERT(decodedEntry.id == entry.id && decodedEntry.prefix == entry.prefix);
|
2022-02-24 00:06:39 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
return Void();
|
|
|
|
}
|