github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/c-deps/libroach/ccl/db_test.cc (about) 1 // Copyright 2017 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 #include <thread> 10 #include "../db.h" 11 #include "../file_registry.h" 12 #include "../testutils.h" 13 #include "../utils.h" 14 #include "ccl/baseccl/encryption_options.pb.h" 15 #include "ccl/storageccl/engineccl/enginepbccl/stats.pb.h" 16 #include "ctr_stream.h" 17 #include "db.h" 18 #include "testutils.h" 19 20 using namespace cockroach; 21 using namespace testutils; 22 23 namespace enginepbccl = cockroach::ccl::storageccl::engineccl::enginepbccl; 24 25 #include <libroach.h> 26 #include "db.h" 27 class CCLTest : public testing::Test { 28 protected: 29 void SetUp() override { DBSetOpenHook((void*)DBOpenHookCCL); } 30 void TearDown() override { DBSetOpenHook((void*)DBOpenHookOSS); } 31 }; 32 33 TEST_F(CCLTest, DBOpenHook) { 34 DBOptions db_opts; 35 db_opts.use_file_registry = false; 36 37 // Try an empty extra_options. 38 db_opts.extra_options = ToDBSlice(""); 39 EXPECT_OK(DBOpenHookCCL(nullptr, "", db_opts, nullptr)); 40 41 // Try without file registry enabled and bogus options. We should fail 42 // because encryption options without file registry is not allowed. 43 db_opts.extra_options = ToDBSlice("blah"); 44 EXPECT_ERR(DBOpenHookCCL(nullptr, "", db_opts, nullptr), 45 "on-disk version does not support encryption, but we found encryption flags"); 46 47 db_opts.use_file_registry = true; 48 // Try with file registry but bogus encryption flags. 49 db_opts.extra_options = ToDBSlice("blah"); 50 EXPECT_ERR(DBOpenHookCCL(nullptr, "", db_opts, nullptr), "failed to parse extra options"); 51 } 52 53 TEST_F(CCLTest, DBOpen) { 54 // Use a real directory, we need to create a file_registry. 55 TempDirHandler dir; 56 57 { 58 // Empty options: no encryption. 59 DBOptions db_opts = defaultDBOptions(); 60 DBEngine* db; 61 db_opts.use_file_registry = true; 62 63 EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL); 64 DBEnvStatsResult stats; 65 EXPECT_STREQ(DBGetEnvStats(db, &stats).data, NULL); 66 EXPECT_STREQ(stats.encryption_status.data, NULL); 67 EXPECT_EQ(stats.encryption_type, enginepbccl::Plaintext); 68 EXPECT_EQ(stats.total_files, 0); 69 EXPECT_EQ(stats.total_bytes, 0); 70 EXPECT_EQ(stats.active_key_files, 0); 71 EXPECT_EQ(stats.active_key_bytes, 0); 72 73 DBClose(db); 74 } 75 { 76 // No options but file registry exists. 77 DBOptions db_opts = defaultDBOptions(); 78 DBEngine* db; 79 db_opts.use_file_registry = true; 80 81 // Create bogus file registry. 82 ASSERT_OK(rocksdb::WriteStringToFile(rocksdb::Env::Default(), "", 83 dir.Path(kFileRegistryFilename), true)); 84 85 auto ret = DBOpen(&db, ToDBSlice(dir.Path("")), db_opts); 86 EXPECT_STREQ(std::string(ret.data, ret.len).c_str(), 87 "Invalid argument: encryption was used on this store before, but no encryption " 88 "flags specified. You need a CCL build and must fully specify the " 89 "--enterprise-encryption flag"); 90 free(ret.data); 91 ASSERT_OK(rocksdb::Env::Default()->DeleteFile(dir.Path(kFileRegistryFilename))); 92 } 93 94 { 95 // Encryption enabled. 96 DBOptions db_opts = defaultDBOptions(); 97 DBEngine* db; 98 db_opts.use_file_registry = true; 99 100 // Enable encryption, but plaintext only, that's enough to get stats going. 101 cockroach::ccl::baseccl::EncryptionOptions enc_opts; 102 enc_opts.set_key_source(cockroach::ccl::baseccl::KeyFiles); 103 enc_opts.mutable_key_files()->set_current_key("plain"); 104 enc_opts.mutable_key_files()->set_old_key("plain"); 105 106 std::string tmpstr; 107 ASSERT_TRUE(enc_opts.SerializeToString(&tmpstr)); 108 db_opts.extra_options = ToDBSlice(tmpstr); 109 110 EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL); 111 DBEnvStatsResult stats; 112 EXPECT_STREQ(DBGetEnvStats(db, &stats).data, NULL); 113 EXPECT_STRNE(stats.encryption_status.data, NULL); 114 EXPECT_EQ(stats.encryption_type, enginepbccl::Plaintext); 115 116 // Now parse the status protobuf. 117 enginepbccl::EncryptionStatus enc_status; 118 ASSERT_TRUE( 119 enc_status.ParseFromArray(stats.encryption_status.data, stats.encryption_status.len)); 120 free(stats.encryption_status.data); 121 EXPECT_STREQ(enc_status.active_store_key().key_id().c_str(), "plain"); 122 EXPECT_STREQ(enc_status.active_data_key().key_id().c_str(), "plain"); 123 124 // Make sure the file/bytes stats are non-zero and all marked as using the active key. 125 EXPECT_NE(stats.total_files, 0); 126 EXPECT_NE(stats.total_bytes, 0); 127 EXPECT_NE(stats.active_key_files, 0); 128 EXPECT_NE(stats.active_key_bytes, 0); 129 130 EXPECT_EQ(stats.total_files, stats.active_key_files); 131 EXPECT_EQ(stats.total_bytes, stats.active_key_bytes); 132 133 DBClose(db); 134 } 135 } 136 137 TEST_F(CCLTest, ReadOnly) { 138 // We need a real directory. 139 TempDirHandler dir; 140 141 { 142 // Write/read a single key. 143 DBEngine* db; 144 DBOptions db_opts = defaultDBOptions(); 145 146 EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL); 147 EXPECT_STREQ(DBPut(db, ToDBKey("foo"), ToDBSlice("foo's value")).data, NULL); 148 DBString value; 149 EXPECT_STREQ(DBGet(db, ToDBKey("foo"), &value).data, NULL); 150 EXPECT_STREQ(ToString(value).c_str(), "foo's value"); 151 free(value.data); 152 153 DBClose(db); 154 } 155 156 { 157 // Re-open read-only without encryption options. 158 DBEngine* db; 159 DBOptions db_opts = defaultDBOptions(); 160 db_opts.read_only = true; 161 162 EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL); 163 // Read the previously-written key. 164 DBString ro_value; 165 EXPECT_STREQ(DBGet(db, ToDBKey("foo"), &ro_value).data, NULL); 166 EXPECT_STREQ(ToString(ro_value).c_str(), "foo's value"); 167 free(ro_value.data); 168 // Try to write it again. 169 auto ret = DBPut(db, ToDBKey("foo"), ToDBSlice("foo's value")); 170 EXPECT_EQ(ToString(ret), "Not implemented: Not supported operation in read only mode."); 171 free(ret.data); 172 173 DBClose(db); 174 } 175 176 { 177 // Re-open read-only with encryption options (plaintext-only). 178 DBEngine* db; 179 DBOptions db_opts = defaultDBOptions(); 180 db_opts.read_only = true; 181 db_opts.use_file_registry = true; 182 auto extra_opts = MakePlaintextExtraOptions(); 183 ASSERT_NE(extra_opts, ""); 184 db_opts.extra_options = ToDBSlice(extra_opts); 185 186 EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL); 187 // Read the previously-written key. 188 DBString ro_value; 189 EXPECT_STREQ(DBGet(db, ToDBKey("foo"), &ro_value).data, NULL); 190 EXPECT_STREQ(ToString(ro_value).c_str(), "foo's value"); 191 free(ro_value.data); 192 // Try to write it again. 193 auto ret = DBPut(db, ToDBKey("foo"), ToDBSlice("foo's value")); 194 EXPECT_EQ(ToString(ret), "Not implemented: Not supported operation in read only mode."); 195 free(ret.data); 196 197 DBClose(db); 198 } 199 } 200 201 TEST_F(CCLTest, EncryptionStats) { 202 // We need a real directory. 203 TempDirHandler dir; 204 205 // Write a key. 206 ASSERT_OK(WriteAES128KeyFile(rocksdb::Env::Default(), dir.Path("aes-128.key"))); 207 208 { 209 // Encryption options specified, but plaintext. 210 DBOptions db_opts = defaultDBOptions(); 211 DBEngine* db; 212 db_opts.use_file_registry = true; 213 214 cockroach::ccl::baseccl::EncryptionOptions enc_opts; 215 enc_opts.set_key_source(cockroach::ccl::baseccl::KeyFiles); 216 enc_opts.mutable_key_files()->set_current_key("plain"); 217 enc_opts.mutable_key_files()->set_old_key("plain"); 218 219 std::string tmpstr; 220 ASSERT_TRUE(enc_opts.SerializeToString(&tmpstr)); 221 db_opts.extra_options = ToDBSlice(tmpstr); 222 223 EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL); 224 DBEnvStatsResult stats; 225 EXPECT_STREQ(DBGetEnvStats(db, &stats).data, NULL); 226 EXPECT_STRNE(stats.encryption_status.data, NULL); 227 EXPECT_EQ(stats.encryption_type, enginepbccl::Plaintext); 228 229 // Write a key. 230 EXPECT_STREQ(DBPut(db, ToDBKey("foo"), ToDBSlice("foo's value")).data, NULL); 231 // Force a compaction. 232 ASSERT_EQ(DBCompact(db).data, nullptr); 233 234 // Now parse the status protobuf. 235 enginepbccl::EncryptionStatus enc_status; 236 ASSERT_TRUE( 237 enc_status.ParseFromArray(stats.encryption_status.data, stats.encryption_status.len)); 238 EXPECT_EQ(enc_status.active_store_key().encryption_type(), enginepbccl::Plaintext); 239 EXPECT_EQ(enc_status.active_data_key().encryption_type(), enginepbccl::Plaintext); 240 241 // Make sure the file/bytes stats are non-zero and all marked as using the active key. 242 EXPECT_NE(stats.total_files, 0); 243 EXPECT_NE(stats.total_bytes, 0); 244 EXPECT_NE(stats.active_key_files, 0); 245 EXPECT_NE(stats.active_key_bytes, 0); 246 247 EXPECT_EQ(stats.total_files, stats.active_key_files); 248 EXPECT_EQ(stats.total_bytes, stats.active_key_bytes); 249 250 // Fetch registries and parse. 251 DBEncryptionRegistries result; 252 EXPECT_STREQ(DBGetEncryptionRegistries(db, &result).data, NULL); 253 254 enginepbccl::DataKeysRegistry key_registry; 255 ASSERT_TRUE(key_registry.ParseFromArray(result.key_registry.data, result.key_registry.len)); 256 257 enginepb::FileRegistry file_registry; 258 ASSERT_TRUE(file_registry.ParseFromArray(result.file_registry.data, result.file_registry.len)); 259 260 // Check some registry contents. 261 EXPECT_STREQ(key_registry.active_store_key_id().c_str(), "plain"); 262 EXPECT_STREQ(key_registry.active_data_key_id().c_str(), "plain"); 263 EXPECT_GT(key_registry.store_keys().size(), 0); 264 EXPECT_GT(key_registry.data_keys().size(), 0); 265 EXPECT_GT(file_registry.files().size(), 0); 266 267 DBClose(db); 268 } 269 270 { 271 // Re-open the DB with AES encryption. 272 DBOptions db_opts = defaultDBOptions(); 273 DBEngine* db; 274 db_opts.use_file_registry = true; 275 276 cockroach::ccl::baseccl::EncryptionOptions enc_opts; 277 enc_opts.set_key_source(cockroach::ccl::baseccl::KeyFiles); 278 enc_opts.set_data_key_rotation_period(3600); 279 enc_opts.mutable_key_files()->set_current_key(dir.Path("aes-128.key")); 280 enc_opts.mutable_key_files()->set_old_key("plain"); 281 282 std::string tmpstr; 283 ASSERT_TRUE(enc_opts.SerializeToString(&tmpstr)); 284 db_opts.extra_options = ToDBSlice(tmpstr); 285 286 EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL); 287 DBEnvStatsResult stats; 288 EXPECT_STREQ(DBGetEnvStats(db, &stats).data, NULL); 289 EXPECT_STRNE(stats.encryption_status.data, NULL); 290 EXPECT_EQ(stats.encryption_type, enginepbccl::AES128_CTR); 291 292 // Now parse the status protobuf. 293 enginepbccl::EncryptionStatus enc_status; 294 ASSERT_TRUE( 295 enc_status.ParseFromArray(stats.encryption_status.data, stats.encryption_status.len)); 296 EXPECT_EQ(enc_status.active_store_key().encryption_type(), enginepbccl::AES128_CTR); 297 EXPECT_EQ(enc_status.active_data_key().encryption_type(), enginepbccl::AES128_CTR); 298 299 // Make sure the file/bytes stats are non-zero. 300 EXPECT_NE(stats.total_files, 0); 301 EXPECT_NE(stats.total_bytes, 0); 302 EXPECT_NE(stats.active_key_files, 0); 303 EXPECT_NE(stats.active_key_bytes, 0); 304 305 // However, we won't be at the total as we have the SST from the plaintext run still around. 306 EXPECT_NE(stats.total_files, stats.active_key_files); 307 EXPECT_NE(stats.total_bytes, stats.active_key_bytes); 308 309 DBClose(db); 310 311 // Sleep for 1 second. Key creation timestamps are in seconds since epoch. 312 ASSERT_EQ(0, sleep(1)); 313 314 // Re-open the DB with exactly the same options and grab stats in a separate object. 315 EXPECT_STREQ(DBOpen(&db, ToDBSlice(dir.Path("")), db_opts).data, NULL); 316 DBEnvStatsResult stats2; 317 EXPECT_STREQ(DBGetEnvStats(db, &stats2).data, NULL); 318 EXPECT_STRNE(stats2.encryption_status.data, NULL); 319 320 // Now parse the status protobuf. 321 enginepbccl::EncryptionStatus enc_status2; 322 ASSERT_TRUE( 323 enc_status2.ParseFromArray(stats2.encryption_status.data, stats2.encryption_status.len)); 324 EXPECT_EQ(enc_status2.active_store_key().encryption_type(), enginepbccl::AES128_CTR); 325 EXPECT_EQ(enc_status2.active_data_key().encryption_type(), enginepbccl::AES128_CTR); 326 327 // Check timestamp equality with the previous stats, we want to make sure we have 328 // the time we first saw the store key, not the second start. 329 EXPECT_EQ(enc_status2.active_store_key().creation_time(), 330 enc_status.active_store_key().creation_time()); 331 332 // Fetch registries and parse. 333 DBEncryptionRegistries result; 334 EXPECT_STREQ(DBGetEncryptionRegistries(db, &result).data, NULL); 335 336 enginepbccl::DataKeysRegistry key_registry; 337 ASSERT_TRUE(key_registry.ParseFromArray(result.key_registry.data, result.key_registry.len)); 338 free(result.key_registry.data); 339 340 enginepb::FileRegistry file_registry; 341 ASSERT_TRUE(file_registry.ParseFromArray(result.file_registry.data, result.file_registry.len)); 342 free(result.file_registry.data); 343 344 // Check some registry contents. 345 EXPECT_STRNE(key_registry.active_store_key_id().c_str(), "plain"); 346 EXPECT_STRNE(key_registry.active_data_key_id().c_str(), "plain"); 347 348 auto iter = key_registry.data_keys().find(key_registry.active_data_key_id()); 349 ASSERT_NE(iter, key_registry.data_keys().end()); 350 // Make sure the key data was cleared. 351 EXPECT_STREQ(iter->second.key().c_str(), ""); 352 EXPECT_EQ(iter->second.info().encryption_type(), enginepbccl::AES128_CTR); 353 354 auto iter2 = key_registry.store_keys().find(key_registry.active_store_key_id()); 355 ASSERT_NE(iter2, key_registry.store_keys().end()); 356 EXPECT_EQ(iter2->second.encryption_type(), enginepbccl::AES128_CTR); 357 358 EXPECT_GT(key_registry.store_keys().size(), 0); 359 EXPECT_GT(key_registry.data_keys().size(), 0); 360 EXPECT_GT(file_registry.files().size(), 0); 361 362 DBClose(db); 363 } 364 }