8000 Encryption key rotation (#11080) · arangodb/arangodb@9ec1e92 · GitHub
[go: up one dir, main page]

Skip to content

Commit 9ec1e92

Browse files
authored
Encryption key rotation (#11080)
1 parent 12ff796 commit 9ec1e92

File tree

18 files changed

+138
-51
lines changed

18 files changed

+138
-51
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
2+
@startDocuBlock post_admin_server_encryption
3+
@brief Rotate encryption at rest key
4+
5+
@RESTHEADER{POST /_admin/server/encryption, Rotate the encryption at rest key, handleEncryption:post}
6+
7+
@RESTDESCRIPTION
8+
Change the user supplied encryption at rest key by sending a request without
9+
payload to this endpoint. The file supplied via `--rocksdb.encryption-keyfile`
10+
will be reloaded and the internal encryption key will be re-encrypted with the
11+
new user key.
12+
13+
This is a protected API and can only be executed with superuser rights.
14+
15+
@RESTRETURNCODES
16+
17+
@RESTRETURNCODE{200}
18+
This API will return HTTP 200 if everything is ok
19+
20+
@RESTRETURNCODE{403}
21+
This API will return HTTP 403 FORBIDDEN if it is not called with
22+
superuser rights.
23+
@endDocuBlock

arangod/Aql/Expression.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -889,7 +889,7 @@ AqlValue Expression::executeSimpleExpressionFCallJS(AstNode const* node,
889889
_ast->query()->prepareV8Context();
890890

891891
std::string jsName;
892-
size_t const n = static_cast<int>(member->numMembers());
892+
size_t const n = member->numMembers();
893893
size_t callArgs = (node->type == NODE_TYPE_FCALL_USER ? 2 : n);
894894
auto args = std::make_unique<v8::Handle<v8::Value>[]>(callArgs);
895895

arangod/GeneralServer/H2CommTask.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ template <SocketType T>
153153
me->_streams.erase(stream_id);
154154

155155
if (error_code != NGHTTP2_NO_ERROR) {
156-
LOG_TOPIC("d04f7", DEBUG, Logger::REQUESTS) << "<http2> closing stream "
156+
LOG_TOPIC("2824d", DEBUG, Logger::REQUESTS) << "<http2> closing stream "
157157
<< stream_id << " with error '" << nghttp2_http2_strerror(error_code) << "' (" << error_code << ")";
158158
}
159159

arangod/RestHandler/RestAdminServerHandler.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ RestStatus RestAdminServerHandler::execute() {
5858
handleTLS();
5959
} else if (suffixes.size() == 1 && suffixes[0] == "jwt") {
6060
handleJWTSecretsReload();
61+
} else if (suffixes.size() == 1 && suffixes[0] == "encryption") {
62+
handleEncryptionKeyRotation();
6163
} else {
6264
generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND);
6365
}
@@ -257,4 +259,8 @@ void RestAdminServerHandler::handleTLS() {
257259
void RestAdminServerHandler::handleJWTSecretsReload() {
258260
generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND);
259261
}
262+
263+
void RestAdminServerHandler::handleEncryptionKeyRotation() {
264+
generateError(rest::ResponseCode::NOT_FOUND, TRI_ERROR_HTTP_NOT_FOUND);
265+
}
260266
#endif

arangod/RestHandler/RestAdminServerHandler.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ class RestAdminServerHandler : public RestBaseHandler {
5454
void writeModeResult(bool);
5555

5656
void handleJWTSecretsReload();
57+
void handleEncryptionKeyRotation();
5758
};
5859
} // namespace arangodb
5960

arangod/RocksDBEngine/RocksDBEngine.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,7 @@ void RocksDBEngine::start() {
396396
auto& databasePathFeature = server().getFeature<DatabasePathFeature>();
397397
_path = databasePathFeature.subdirectoryName("engine-rocksdb");
398398

399+
bool createdEngineDir = false;
399400
if (!basics::FileUtils::isDirectory(_path)) {
400401
std::string systemErrorStr;
401402
long errorNo;
@@ -405,6 +406,7 @@ void RocksDBEngine::start() {
405406
if (res == TRI_ERROR_NO_ERROR) {
406407
LOG_TOPIC("b2958", TRACE, arangodb::Logger::ENGINES)
407408
<< "created RocksDB data directory '" << _path << "'";
409+
createdEngineDir = true;
408410
} else {
409411
LOG_TOPIC("a5ae3", FATAL, arangodb::Logger::ENGINES)
410412
<< "unable to create RocksDB data directory '" << _path
@@ -487,8 +489,9 @@ void RocksDBEngine::start() {
487489
_options.compaction_readahead_size = static_cast<size_t>(opts._compactionReadaheadSize);
488490

489491
#ifdef USE_ENTERPRISE
490-
configureEnterpriseRocksDBOptions(_options);
491-
startEnterprise();
492+
configureEnterpriseRocksDBOptions(_options, createdEngineDir);
493+
#else
494+
((void)createdEngineDir);
492495
#endif
493496

494497
_options.env->SetBackgroundThreads(static_cast<int>(opts._numThreadsHigh),

arangod/RocksDBEngine/RocksDBEngine.h

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -347,14 +347,35 @@ class RocksDBEngine final : public StorageEngine {
347347
void collectEnterpriseOptions(std::shared_ptr<options::ProgramOptions>);
348348
void validateEnterpriseOptions(std::shared_ptr<options::ProgramOptions>);
349349
void prepareEnterprise();
350-
void startEnterprise();
351-
void configureEnterpriseRocksDBOptions(rocksdb::Options& options);
350+
void configureEnterpriseRocksDBOptions(rocksdb::Options& options, bool createdEngineDir);
352351
void validateJournalFiles() const;
352+
353+
Result readUserEncryptionKeys(std::map<std::string, std::string>& outlist) const;
353354

354355
enterprise::RocksDBEngineEEData _eeData;
355356

356357
public:
358+
359+
bool isEncryptionEnabled() const;
360+
357361
std::string const& getEncryptionKey();
362+
363+
std::string getEncryptionTypeFile() const;
364+
365+
std::string getKeyStoreFolder() const;
366+
367+
std::vector<std::string> userEncryptionKeys() const;
368+
369+
/// rotate user-provided keys, writes out the internal key files
370+
Result rotateUserEncryptionKeys();
371+
372+
private:
373+
374+
/// load encryption at rest key from keystore
375+
Result decryptInternalKeystore();
376+
/// encrypt the internal keystore with all user keys
377+
Result encryptInternalKeystore();
378+
358379
#endif
359380
private:
360381
// activate generation of SHA256 files to parallel .sst files

arangod/RocksDBEngine/RocksDBKeyBounds.cpp

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ RocksDBKeyBounds RocksDBKeyBounds::FulltextIndexPrefix(uint64_t objectId,
148148

149149
uint64ToPersistent(internals.buffer(), objectId);
150150
internals.buffer().append(word.data(), word.length());
151-
internals.push_back(0xFFU);
151+
internals.push_back(static_cast<char>(0xFFU));
152152
// 0xFF is higher than any valud utf-8 character
153153
return b;
154154
}
@@ -284,7 +284,7 @@ RocksDBKeyBounds::RocksDBKeyBounds(RocksDBEntryType type) : _type(type) {
284284

285285
_internals.separate();
286286
_internals.push_back(static_cast<char>(_type));
287-
_internals.push_back(0xFFU);
287+
_internals.push_back(static_cast<char>(0xFFU));
288288
break;
289289
}
290290
case RocksDBEntryType::CounterValue:
@@ -325,7 +325,7 @@ RocksDBKeyBounds::RocksDBKeyBounds(RocksDBEntryType type, uint64_t first)
325325
_internals.reserve(2 * sizeof(uint64_t) + min.byteSize() + max.byteSize());
326326

327327
uint64ToPersistent(_internals.buffer(), first);
328-
_internals.buffer().append((char*)(min.begin()), min.byteSize());
328+
_internals.buffer().append((char const*)(min.begin()), min.byteSize());
329329

330330
_internals.separate();
331331

@@ -334,10 +334,10 @@ RocksDBKeyBounds::RocksDBKeyBounds(RocksDBEntryType type, uint64_t first)
334334
// for the upper bound we can use the object id + 1, which will always compare higher in a
335335
// bytewise comparison
336336
uint64ToPersistent(_internals.buffer(), first + 1);
337-
_internals.buffer().append((char*)(min.begin()), min.byteSize());
337+
_internals.buffer().append((char const*)(min.begin()), min.byteSize());
338338
} else {
339339
uint64ToPersistent(_internals.buffer(), first);
340-
_internals.buffer().append((char*)(max.begin()), max.byteSize());
340+
_internals.buffer().append((char const*)(max.begin()), max.byteSize());
341341
}
342342
break;
343343
}

arangod/V8Server/v8-vocbase.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@
6464
#include "RestServer/ConsoleThread.h"
6565
#include "RestServer/DatabaseFeature.h"
6666
#include "RestServer/QueryRegistryFeature.h"
67+
#include "RocksDBEngine/RocksDBEngine.h"
6768
#include "Statistics/StatisticsFeature.h"
6869
#include "StorageEngine/EngineSelectorFeature.h"
6970
#include "StorageEngine/StorageEngine.h"
@@ -1946,6 +1947,36 @@ static void JS_AgencyDump(v8::FunctionCallbackInfo<v8::Value> const& args) {
19461947
TRI_V8_TRY_CATCH_END
19471948
}
19481949

1950+
#ifdef USE_ENTERPRISE
1951+
1952+
////////////////////////////////////////////////////////////////////////////////
1953+
/// @brief this is rotates the encryption keys, only for testing
1954+
////////////////////////////////////////////////////////////////////////////////
1955+
1956+
static void JS_EncryptionKeyReload(v8::FunctionCallbackInfo<v8::Value> const& args) {
1957+
TRI_V8_TRY_CATCH_BEGIN(isolate);
1958+
v8::HandleScope scope(isolate);
1959+
1960+
if (args.Length() != 0) {
1961+
TRI_V8_THROW_EXCEPTION_USAGE("encryptionKeyReload()");
1962+
}
1963+
1964+
if (!EngineSelectorFeature::isRocksDB()) {
1965+
THROW_ARANGO_EXCEPTION(TRI_ERROR_NOT_IMPLEMENTED);
1966+
}
1967+
1968+
auto* engine = EngineSelectorFeature::ENGINE;
1969+
auto res = static_cast<RocksDBEngine*>(engine)->rotateUserEncryptionKeys();
1970+
if (res.fail()) {
1971+
TRI_V8_THROW_EXCEPTION(res);
1972+
}
1973+
1974+
TRI_V8_RETURN_TRUE();
1975+
TRI_V8_TRY_CATCH_END
1976+
}
1977+
1978+
#endif
1979+
19491980
////////////////////////////////////////////////////////////////////////////////
19501981
/// @brief creates a TRI_vocbase_t global context
19511982
////////////////////////////////////////////////////////////////////////////////
@@ -2133,6 +2164,14 @@ void TRI_InitV8VocBridge(v8::Isolate* isolate, v8::Handle<v8::Context> context,
21332164
TRI_AddGlobalFunctionVocbase(isolate,
21342165
TRI_V8_ASCII_STRING(isolate, "AGENCY_DUMP"),
21352166
JS_AgencyDump, true);
2167+
2168+
#ifdef USE_ENTERPRISE
2169+
if (V8DealerFeature::DEALER && V8DealerFeature::DEALER->allowAdminExecute()) {
2170+
TRI_AddGlobalFunctionVocbase(isolate,
2171+
TRI_V8_ASCII_STRING(isolate, "ENCRYPTION_KEY_RELOAD"),
2172+
JS_EncryptionKeyReload, true);
2173+
}
2174+
#endif
21362175

21372176
// .............................................................................
21382177
// create global variables

js/client/modules/@arangodb/testsuites/recovery.js

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,18 @@ function runArangodRecovery (params) {
9292
});
9393

9494
if (useEncryption) {
95-
args['rocksdb.encryption-keyfile'] = tu.pathForTesting('server/recovery/encryption-keyfile');
95+
const key = '01234567890123456789012345678901';
96+
let keyDir = fs.join(fs.getTempPath(), 'arango_encryption');
97+
if (!fs.exists(keyDir)) { // needed on win32
98+
fs.makeDirectory(keyDir);
99+
}
100+
pu.cleanupDBDirectoriesAppend(keyDir);
101+
102+
let keyfile = fs.join(keyDir, 'rocksdb-encryption-keyfile');
103+
fs.write(keyfile, key);
104+
105+
args['rocksdb.encryption-keyfile'] = keyfile;
106+
process.env["rocksdb-encryption-keyfile"] = keyfile;
96107
}
97108

98109
params.args = args;

0 commit comments

Comments
 (0)
0