Skip to content

Commit fda5612

Browse files
author
marc
committed
libroach: ensure encryption flags are passed if previously used.
Add some safeguards around the encryption flag. When we have a `COCKROACH_REGISTRY` file, we absolutely must keep using it to keep track of all files. It gets created the first time a file is created when using the file_registry which currently only happens when `--enterprise-encryption` is specified. This means that if we have a registry file, we MUST have encryption options (and be using a CCL build). Also add a simple interactive test to check encryption transition and handling of some bad args (wrong keys, no encryption flag) Release note: None
1 parent 2d8938b commit fda5612

File tree

6 files changed

+211
-39
lines changed

6 files changed

+211
-39
lines changed

c-deps/libroach/ccl/db.cc

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,13 @@ rocksdb::Status DBOpenHookCCL(std::shared_ptr<rocksdb::Logger> info_log, const s
126126
return rocksdb::Status::OK();
127127
}
128128

129+
// The Go code sets the "file_registry" storage version if we specified encryption flags,
130+
// but let's double check anyway.
131+
if (!db_opts.use_file_registry) {
132+
return rocksdb::Status::InvalidArgument(
133+
"on-disk version does not support encryption, but we found encryption flags");
134+
}
135+
129136
// We use a logger at V(0) for encryption status instead of the existing info_log.
130137
// This should only be used to occasional logging (eg: key loading and rotation).
131138
std::shared_ptr<rocksdb::Logger> logger(NewDBLogger(0));
@@ -146,13 +153,6 @@ rocksdb::Status DBOpenHookCCL(std::shared_ptr<rocksdb::Logger> info_log, const s
146153
status.getState());
147154
}
148155

149-
// The Go code sets the "file_registry" storage version if we specified encryption flags,
150-
// but let's double check anyway.
151-
if (!db_opts.use_file_registry) {
152-
return rocksdb::Status::InvalidArgument(
153-
"on-disk version does not support encryption, but we found encryption flags");
154-
}
155-
156156
// Parse extra_options.
157157
cockroach::ccl::baseccl::EncryptionOptions opts;
158158
if (!opts.ParseFromArray(options.data, options.len)) {

c-deps/libroach/ccl/db_test.cc

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,16 @@ TEST_F(CCLTest, DBOpenHook) {
5151
}
5252

5353
TEST_F(CCLTest, DBOpen) {
54+
// Use a real directory, we need to create a file_registry.
55+
TempDirHandler dir;
56+
5457
{
5558
// Empty options: no encryption.
5659
DBOptions db_opts = defaultDBOptions();
5760
DBEngine* db;
5861
db_opts.use_file_registry = true;
5962

60-
EXPECT_STREQ(DBOpen(&db, DBSlice(), db_opts).data, NULL);
63+
EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL);
6164
DBEnvStatsResult stats;
6265
EXPECT_STREQ(DBGetEnvStats(db, &stats).data, NULL);
6366
EXPECT_STREQ(stats.encryption_status.data, NULL);
@@ -68,6 +71,24 @@ TEST_F(CCLTest, DBOpen) {
6871

6972
DBClose(db);
7073
}
74+
{
75+
// No options but file registry exists.
76+
DBOptions db_opts = defaultDBOptions();
77+
DBEngine* db;
78+
db_opts.use_file_registry = true;
79+
80+
// Create bogus file registry.
81+
ASSERT_OK(rocksdb::WriteStringToFile(rocksdb::Env::Default(), "",
82+
dir.Path(kFileRegistryFilename), true));
83+
84+
auto ret = DBOpen(&db, ToDBSlice(dir.Path("")), db_opts);
85+
EXPECT_STREQ(std::string(ret.data, ret.len).c_str(),
86+
"Invalid argument: encryption was used on this store before, but no encryption "
87+
"flags specified. You need a CCL build and must fully specify the "
88+
"--enterprise-encryption flag");
89+
ASSERT_OK(rocksdb::Env::Default()->DeleteFile(dir.Path(kFileRegistryFilename)));
90+
}
91+
7192
{
7293
// Encryption enabled.
7394
DBOptions db_opts = defaultDBOptions();
@@ -84,7 +105,7 @@ TEST_F(CCLTest, DBOpen) {
84105
ASSERT_TRUE(enc_opts.SerializeToString(&tmpstr));
85106
db_opts.extra_options = ToDBSlice(tmpstr);
86107

87-
EXPECT_STREQ(DBOpen(&db, DBSlice(), db_opts).data, NULL);
108+
EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL);
88109
DBEnvStatsResult stats;
89110
EXPECT_STREQ(DBGetEnvStats(db, &stats).data, NULL);
90111
EXPECT_STRNE(stats.encryption_status.data, NULL);

c-deps/libroach/db.cc

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -137,10 +137,9 @@ namespace cockroach {
137137

138138
// DBOpenHookOSS mode only verifies that no extra options are specified.
139139
rocksdb::Status DBOpenHookOSS(std::shared_ptr<rocksdb::Logger> info_log, const std::string& db_dir,
140-
const DBOptions opts, EnvManager* env_mgr) {
141-
if (opts.extra_options.len != 0) {
142-
return rocksdb::Status::InvalidArgument(
143-
"DBOptions has extra_options, but OSS code cannot handle them");
140+
const DBOptions db_opts, EnvManager* env_mgr) {
141+
if (db_opts.extra_options.len != 0) {
142+
return rocksdb::Status::InvalidArgument("encryption options are not supported in OSS builds");
144143
}
145144
return rocksdb::Status::OK();
146145
}
@@ -197,6 +196,23 @@ DBStatus DBOpen(DBEngine** db, DBSlice dir, DBOptions db_opts) {
197196
return ToDBStatus(status);
198197
}
199198

199+
status = file_registry->CheckNoRegistryFile();
200+
if (!status.ok()) {
201+
// We have a file registry, this means we've used encryption flags before
202+
// and are tracking all files on disk. Running without encryption (extra_options empty)
203+
// will bypass the file registry and lose changes.
204+
// In this case, we have multiple possibilities:
205+
// - no extra_options: this fails here
206+
// - extra_options:
207+
// - OSS: this fails in the OSS hook (OSS does not understand extra_options)
208+
// - CCL: fails if the options do not parse properly
209+
if (db_opts.extra_options.len == 0) {
210+
return ToDBStatus(rocksdb::Status::InvalidArgument(
211+
"encryption was used on this store before, but no encryption flags specified. You need "
212+
"a CCL build and must fully specify the --enterprise-encryption flag"));
213+
}
214+
}
215+
200216
// EnvManager takes ownership of the file registry.
201217
env_mgr->file_registry.swap(file_registry);
202218
} else {
@@ -214,12 +230,6 @@ DBStatus DBOpen(DBEngine** db, DBSlice dir, DBOptions db_opts) {
214230
return ToDBStatus(hook_status);
215231
}
216232

217-
// TODO(mberhault):
218-
// - check available ciphers somehow?
219-
// We may have a encrypted files in the registry file but running without encryption flags.
220-
// - pass read-only flag though, we should not be modifying file/key registries (including key
221-
// rotation) in read-only mode.
222-
223233
// Register listener for tracking RocksDB stats.
224234
std::shared_ptr<DBEventListener> event_listener(new DBEventListener);
225235
options.listeners.emplace_back(event_listener);

c-deps/libroach/db_test.cc

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// permissions and limitations under the License.
1414

1515
#include "db.h"
16+
#include "file_registry.h"
1617
#include "include/libroach.h"
1718
#include "status.h"
1819
#include "testutils.h"
@@ -30,29 +31,78 @@ TEST(Libroach, DBOpenHook) {
3031
// Try extra_options with anything at all.
3132
db_opts.extra_options = ToDBSlice("blah");
3233
EXPECT_ERR(DBOpenHookOSS(nullptr, "", db_opts, nullptr),
33-
"DBOptions has extra_options, but OSS code cannot handle them");
34+
"encryption options are not supported in OSS builds");
3435
}
3536

3637
TEST(Libroach, DBOpen) {
37-
DBOptions db_opts = defaultDBOptions();
38-
DBEngine* db;
39-
40-
EXPECT_STREQ(DBOpen(&db, DBSlice(), db_opts).data, NULL);
41-
DBEnvStatsResult stats;
42-
EXPECT_STREQ(DBGetEnvStats(db, &stats).data, NULL);
43-
EXPECT_STREQ(stats.encryption_status.data, NULL);
44-
EXPECT_EQ(stats.total_files, 0);
45-
EXPECT_EQ(stats.total_bytes, 0);
46-
EXPECT_EQ(stats.active_key_files, 0);
47-
EXPECT_EQ(stats.active_key_bytes, 0);
48-
49-
// Fetch registries, parse, and check that they're empty.
50-
DBEncryptionRegistries result;
51-
EXPECT_STREQ(DBGetEncryptionRegistries(db, &result).data, NULL);
52-
EXPECT_STREQ(result.key_registry.data, NULL);
53-
EXPECT_STREQ(result.file_registry.data, NULL);
54-
55-
DBClose(db);
38+
// Use a real directory, we need to create a file_registry.
39+
TempDirHandler dir;
40+
41+
{
42+
DBOptions db_opts = defaultDBOptions();
43+
44+
DBEngine* db;
45+
EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL);
46+
DBEnvStatsResult stats;
47+
EXPECT_STREQ(DBGetEnvStats(db, &stats).data, NULL);
48+
EXPECT_STREQ(stats.encryption_status.data, NULL);
49+
EXPECT_EQ(stats.total_files, 0);
50+
EXPECT_EQ(stats.total_bytes, 0);
51+
EXPECT_EQ(stats.active_key_files, 0);
52+
EXPECT_EQ(stats.active_key_bytes, 0);
53+
54+
// Fetch registries, parse, and check that they're empty.
55+
DBEncryptionRegistries result;
56+
EXPECT_STREQ(DBGetEncryptionRegistries(db, &result).data, NULL);
57+
EXPECT_STREQ(result.key_registry.data, NULL);
58+
EXPECT_STREQ(result.file_registry.data, NULL);
59+
60+
DBClose(db);
61+
}
62+
{
63+
// We're at storage version FileRegistry, but we don't have one.
64+
DBOptions db_opts = defaultDBOptions();
65+
db_opts.use_file_registry = true;
66+
67+
DBEngine* db;
68+
EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL);
69+
DBClose(db);
70+
}
71+
{
72+
// We're at storage version FileRegistry, and the file_registry file exists:
73+
// this is not supported in OSS mode, or without encryption options.
74+
DBOptions db_opts = defaultDBOptions();
75+
db_opts.use_file_registry = true;
76+
77+
// Create bogus file registry.
78+
ASSERT_OK(rocksdb::WriteStringToFile(rocksdb::Env::Default(), "",
79+
dir.Path(kFileRegistryFilename), true));
80+
81+
DBEngine* db;
82+
auto ret = DBOpen(&db, ToDBSlice(dir.Path("")), db_opts);
83+
EXPECT_STREQ(std::string(ret.data, ret.len).c_str(),
84+
"Invalid argument: encryption was used on this store before, but no encryption "
85+
"flags specified. You need a CCL build and must fully specify the "
86+
"--enterprise-encryption flag");
87+
ASSERT_OK(rocksdb::Env::Default()->DeleteFile(dir.Path(kFileRegistryFilename)));
88+
}
89+
{
90+
// We're at storage version FileRegistry, and the file_registry file exists.
91+
// We do have encryption options, but those are not supported in OSS builds.
92+
DBOptions db_opts = defaultDBOptions();
93+
db_opts.use_file_registry = true;
94+
db_opts.extra_options = ToDBSlice("blah");
95+
96+
// Create bogus file registry.
97+
ASSERT_OK(rocksdb::WriteStringToFile(rocksdb::Env::Default(), "",
98+
dir.Path(kFileRegistryFilename), true));
99+
100+
DBEngine* db;
101+
auto ret = DBOpen(&db, ToDBSlice(dir.Path("")), db_opts);
102+
EXPECT_STREQ(std::string(ret.data, ret.len).c_str(),
103+
"Invalid argument: encryption options are not supported in OSS builds");
104+
ASSERT_OK(rocksdb::Env::Default()->DeleteFile(dir.Path(kFileRegistryFilename)));
105+
}
56106
}
57107

58108
TEST(Libroach, BatchSSTablesForCompaction) {

c-deps/libroach/file_registry.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ class FileRegistry {
5555

5656
// Load the file registry. Errors on file reading/parsing issues.
5757
// OK if the file does not exist or is empty.
58+
// This does **not** create a file registry.
5859
rocksdb::Status Load();
5960

6061
// Returns a hash-set of all used env types.
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
#! /usr/bin/env expect -f
2+
#
3+
source [file join [file dirname $argv0] common.tcl]
4+
5+
set storedir "encryption_store"
6+
set keydir "$storedir/keys"
7+
8+
spawn /bin/bash
9+
send "PS1=':''/# '\r"
10+
eexpect ":/# "
11+
12+
proc file_has_size {filepath size} {
13+
if {! [file exist $filepath]} {
14+
report "MISSING EXPECTED FILE: $filepath"
15+
exit 1
16+
}
17+
set fsize [file size $filepath]
18+
if { $fsize != $size } {
19+
report "WRONG FILE SIZE FOR: $filepath. EXPECTED $size, GOT $fsize"
20+
exit 1
21+
}
22+
}
23+
24+
start_test "Generate encryption keys."
25+
send "mkdir -p $keydir\n"
26+
send "$argv gen encryption-key -s 128 $keydir/aes-128.key\r"
27+
eexpect "successfully created AES-128 key: $keydir/aes-128.key"
28+
send "$argv gen encryption-key -s 192 $keydir/aes-192.key\r"
29+
eexpect "successfully created AES-192 key: $keydir/aes-192.key"
30+
send "$argv gen encryption-key -s 256 $keydir/aes-256.key\r"
31+
eexpect "successfully created AES-256 key: $keydir/aes-256.key"
32+
file_has_size "$keydir/aes-128.key" "48"
33+
file_has_size "$keydir/aes-192.key" "56"
34+
file_has_size "$keydir/aes-256.key" "64"
35+
end_test
36+
37+
start_test "Start normal node."
38+
send "$argv start --insecure --store=$storedir\r"
39+
eexpect "node starting"
40+
interrupt
41+
eexpect "shutdown completed"
42+
send "$argv debug encryption-status $storedir\r"
43+
eexpect ""
44+
end_test
45+
46+
start_test "Restart with plaintext."
47+
send "$argv start --insecure --store=$storedir --enterprise-encryption=path=$storedir,key=plain,old-key=plain\r"
48+
eexpect "node starting"
49+
interrupt
50+
eexpect "shutdown completed"
51+
send "$argv debug encryption-status $storedir --enterprise-encryption=path=$storedir,key=plain,old-key=plain\r"
52+
eexpect " \"Active\": true,\r\n \"Type\": \"Plaintext\","
53+
# Try starting without the encryption flag.
54+
send "$argv start --insecure --store=$storedir\r"
55+
eexpect "encryption was used on this store before, but no encryption flags specified."
56+
end_test
57+
58+
start_test "Restart with AES-128."
59+
send "$argv start --insecure --store=$storedir --enterprise-encryption=path=$storedir,key=$keydir/aes-128.key,old-key=plain\r"
60+
eexpect "node starting"
61+
interrupt
62+
eexpect "shutdown completed"
63+
send "$argv debug encryption-status $storedir --enterprise-encryption=path=$storedir,key=$keydir/aes-128.key,old-key=plain\r"
64+
eexpect " \"Active\": true,\r\n \"Type\": \"AES128_CTR\","
65+
# Try starting without the encryption flag.
66+
send "$argv start --insecure --store=$storedir\r"
67+
eexpect "encryption was used on this store before, but no encryption flags specified."
68+
# Try with the wrong key.
69+
send "$argv start --insecure --store=$storedir --enterprise-encryption=path=$storedir,key=$keydir/aes-192.key,old-key=plain\r"
70+
eexpect "key_manager does not have a key with ID"
71+
end_test
72+
73+
start_test "Restart with AES-256."
74+
send "$argv start --insecure --store=$storedir --enterprise-encryption=path=$storedir,key=$keydir/aes-256.key,old-key=$keydir/aes-128.key\r"
75+
eexpect "node starting"
76+
interrupt
77+
eexpect "shutdown completed"
78+
send "$argv debug encryption-status $storedir --enterprise-encryption=path=$storedir,key=$keydir/aes-256.key,old-key=plain\r"
79+
eexpect " \"Active\": true,\r\n \"Type\": \"AES256_CTR\","
80+
# Startup again, but don't specify the old key, it's no longer in use.
81+
send "$argv start --insecure --store=$storedir --enterprise-encryption=path=$storedir,key=$keydir/aes-256.key,old-key=plain\r"
82+
eexpect "node starting"
83+
interrupt
84+
# Try starting without the encryption flag.
85+
send "$argv start --insecure --store=$storedir\r"
86+
eexpect "encryption was used on this store before, but no encryption flags specified."
87+
# Try with the wrong key.
88+
send "$argv start --insecure --store=$storedir --enterprise-encryption=path=$storedir,key=$keydir/aes-192.key,old-key=plain\r"
89+
eexpect "key_manager does not have a key with ID"
90+
end_test

0 commit comments

Comments
 (0)