foundationdb/fdbclient/Tenant.cpp
Ata E Husain Bohra d2b82d2c46
Introduce "default encryption domain" (#8139)
* Introduce "default encryption domain"

Description

In current FDB native encryption data at-rest implementation,
an entity getting encrypted (mutation, KV and/or file) is categorized
into one of following encryption domains:
1. Tenant domain, where, Encryption domain == Tenant boundaries
2. FDB system keyspace - FDB metadata encryption domain
3. FDB Encryption Header domain - used to generate digest for
plaintext EncryptionHeader.

The scheme doesn't support encryption if an entity can't be categorized
into any of above mentioned encryption domains, for instance, non-tenant
mutations are NOT supported.

Patch extend the encryption support for mutations for which corresponding
Tenant information can't be obtained (Key length shorter than TenantPrefix)
and/or mutations do not belong to any valid Tenant
(FDB management cluster data) by mapping such mutations to a
"default encryption domain".

TODO

CommitProxy driven TLog encryption implementation requires every transaction
mutation to contain 1 KV, not crossing Tenant-boundaries. Only exception to
this rule is ClearRange mutations. For now ClearRange mutations are mapped
to 'default encryption domain', in subsequent patch appropriate handling
for ClearRange mutations shall be proposed.

Testing

devRunCorrectness - 100k
2022-09-14 10:58:32 -07:00

216 lines
6.9 KiB
C++

/*
* 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.
*/
#include "fdbclient/NativeAPI.actor.h"
#include "fdbclient/SystemData.h"
#include "fdbclient/Tenant.h"
#include "fdbrpc/TenantInfo.h"
#include "flow/BooleanParam.h"
#include "libb64/encode.h"
#include "flow/ApiVersion.h"
#include "flow/UnitTest.h"
FDB_DEFINE_BOOLEAN_PARAM(EnforceValidTenantId);
Key TenantMapEntry::idToPrefix(int64_t id) {
int64_t swapped = bigEndian64(id);
return StringRef(reinterpret_cast<const uint8_t*>(&swapped), TENANT_PREFIX_SIZE);
}
int64_t TenantMapEntry::prefixToId(KeyRef prefix, EnforceValidTenantId enforceValidTenantId) {
ASSERT(prefix.size() == TENANT_PREFIX_SIZE);
int64_t id = *reinterpret_cast<const int64_t*>(prefix.begin());
id = bigEndian64(id);
if (enforceValidTenantId) {
ASSERT(id >= 0);
} else if (id < 0) {
return TenantInfo::INVALID_TENANT;
}
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";
case TenantState::UPDATING_CONFIGURATION:
return "updating configuration";
case TenantState::RENAMING_FROM:
return "renaming from";
case TenantState::RENAMING_TO:
return "renaming to";
case TenantState::ERROR:
return "error";
default:
UNREACHABLE();
}
}
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;
} else if (stateStr == "updating configuration") {
return TenantState::UPDATING_CONFIGURATION;
} else if (stateStr == "renaming from") {
return TenantState::RENAMING_FROM;
} else if (stateStr == "renaming to") {
return TenantState::RENAMING_TO;
} else if (stateStr == "error") {
return TenantState::ERROR;
}
UNREACHABLE();
}
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();
}
TenantMapEntry::TenantMapEntry() {}
TenantMapEntry::TenantMapEntry(int64_t id, TenantState tenantState, bool encrypted)
: tenantState(tenantState), encrypted(encrypted) {
setId(id);
}
TenantMapEntry::TenantMapEntry(int64_t id,
TenantState tenantState,
Optional<TenantGroupName> tenantGroup,
bool encrypted)
: tenantState(tenantState), tenantGroup(tenantGroup), encrypted(encrypted) {
setId(id);
}
void TenantMapEntry::setId(int64_t id) {
ASSERT(id >= 0);
this->id = id;
prefix = idToPrefix(id);
}
std::string TenantMapEntry::toJson() const {
json_spirit::mObject tenantEntry;
tenantEntry["id"] = id;
tenantEntry["encrypted"] = encrypted;
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;
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;
}
return json_spirit::write_string(json_spirit::mValue(tenantEntry));
}
bool TenantMapEntry::matchesConfiguration(TenantMapEntry const& other) const {
return tenantGroup == other.tenantGroup && encrypted == other.encrypted;
}
void TenantMapEntry::configure(Standalone<StringRef> parameter, Optional<Value> value) {
if (parameter == "tenant_group"_sr) {
tenantGroup = value;
} else {
TraceEvent(SevWarnAlways, "UnknownTenantConfigurationParameter").detail("Parameter", parameter);
throw invalid_tenant_configuration();
}
}
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;
}
TEST_CASE("/fdbclient/TenantMapEntry/Serialization") {
TenantMapEntry entry1(1, TenantState::READY, false);
ASSERT(entry1.prefix == "\x00\x00\x00\x00\x00\x00\x00\x01"_sr);
TenantMapEntry entry2 = TenantMapEntry::decode(entry1.encode());
ASSERT(entry1.id == entry2.id && entry1.prefix == entry2.prefix);
TenantMapEntry entry3(std::numeric_limits<int64_t>::max(), TenantState::READY, false);
ASSERT(entry3.prefix == "\x7f\xff\xff\xff\xff\xff\xff\xff"_sr);
TenantMapEntry entry4 = TenantMapEntry::decode(entry3.encode());
ASSERT(entry3.id == entry4.id && entry3.prefix == entry4.prefix);
for (int i = 0; i < 100; ++i) {
int bits = deterministicRandom()->randomInt(1, 64);
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());
int64_t id = deterministicRandom()->randomInt64(min, maxPlusOne);
TenantMapEntry entry(id, TenantState::READY, false);
int64_t bigEndianId = bigEndian64(id);
ASSERT(entry.id == id && entry.prefix == StringRef(reinterpret_cast<uint8_t*>(&bigEndianId), 8));
TenantMapEntry decodedEntry = TenantMapEntry::decode(entry.encode());
ASSERT(decodedEntry.id == entry.id && decodedEntry.prefix == entry.prefix);
}
return Void();
}