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  }