github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/c-deps/libroach/ccl/encrypted_env_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 "../file_registry.h"
    11  #include "../fmt.h"
    12  #include "../testutils.h"
    13  #include "ccl/baseccl/encryption_options.pb.h"
    14  #include "ctr_stream.h"
    15  #include "testutils.h"
    16  
    17  using namespace cockroach;
    18  using namespace testutils;
    19  
    20  TEST(EncryptedEnv, ConcurrentAccess) {
    21    // This test creates a standalone encrypted env, verifies that what goes
    22    // in comes out, and that concurrent accesses to the same file handle are ok.
    23    std::unique_ptr<rocksdb::Env> env(rocksdb::NewMemEnv(rocksdb::Env::Default()));
    24    auto key_manager = new MemKeyManager(MakeAES128Key(env.get()));
    25    auto stream = new CTRCipherStreamCreator(key_manager, enginepb::Data);
    26  
    27    auto file_registry =
    28        std::unique_ptr<FileRegistry>(new FileRegistry(env.get(), "/", false /* read-only */));
    29    EXPECT_OK(file_registry->Load());
    30  
    31    std::unique_ptr<rocksdb::Env> encrypted_env(
    32        rocksdb_utils::NewEncryptedEnv(env.get(), file_registry.get(), stream));
    33  
    34    std::string filename("/foo");
    35    std::string contents("this is the string stored inside the file!");
    36    size_t kContentsLength = 42;
    37    ASSERT_EQ(kContentsLength, contents.size());
    38  
    39    // Write the file.
    40    EXPECT_OK(
    41        rocksdb::WriteStringToFile(encrypted_env.get(), contents, filename, false /* should_sync */));
    42  
    43    // Read the file using the mem env (no encryption).
    44    std::string result_plain;
    45    EXPECT_OK(rocksdb::ReadFileToString(env.get(), filename, &result_plain));
    46    EXPECT_STRNE(contents.c_str(), result_plain.c_str());
    47  
    48    // Read the file back using the encrypted env.
    49    std::string result_encrypted;
    50    EXPECT_OK(rocksdb::ReadFileToString(encrypted_env.get(), filename, &result_encrypted));
    51    EXPECT_STREQ(contents.c_str(), result_encrypted.c_str());
    52  
    53    // Open as a random access file.
    54    std::unique_ptr<rocksdb::RandomAccessFile> file;
    55    EXPECT_OK(encrypted_env->NewRandomAccessFile(filename, &file, rocksdb::EnvOptions()));
    56  
    57    // Reader thread. Captures all useful variables.
    58    auto read_file = [&]() {
    59      char scratch[kContentsLength];  // needs to be at least len(contents).
    60      rocksdb::Slice result_read;
    61  
    62      for (int i = 0; i < 100; i++) {
    63        EXPECT_OK(file->Read(0, kContentsLength, &result_read, scratch));
    64        EXPECT_EQ(kContentsLength, result_read.size());
    65  
    66        // We need to go through Slice.ToString as .data() does not have a null terminator.
    67        EXPECT_STREQ(contents.c_str(), result_read.ToString().c_str());
    68      }
    69    };
    70  
    71    // Call it once by itself.
    72    read_file();
    73  
    74    // Run two at the same time. We're not using rocksdb thread utilities as they don't support
    75    // lambda functions with variable capture, everything has to done through args.
    76    auto t1 = std::thread(read_file);
    77    auto t2 = std::thread(read_file);
    78    t1.join();
    79    t2.join();
    80  }
    81  
    82  namespace {
    83  
    84  rocksdb::Status checkFileEntry(FileRegistry& registry, const std::string& filename,
    85                                 enginepbccl::EncryptionType enc_type) {
    86    auto entry = registry.GetFileEntry(filename);
    87    if (entry == nullptr) {
    88      return rocksdb::Status::InvalidArgument(
    89          fmt::StringPrintf("file %s has no entry", filename.c_str()));
    90    }
    91  
    92    enginepbccl::EncryptionSettings enc_settings;
    93    if (!enc_settings.ParseFromString(entry->encryption_settings())) {
    94      return rocksdb::Status::InvalidArgument(
    95          fmt::StringPrintf("failed to parse encryption settings for file %s", filename.c_str()));
    96    }
    97  
    98    return rocksdb::Status::OK();
    99  }
   100  
   101  rocksdb::Status checkNoFileEntry(FileRegistry& registry, const std::string& filename) {
   102    auto entry = registry.GetFileEntry(filename);
   103    if (entry != nullptr) {
   104      return rocksdb::Status::InvalidArgument(
   105          fmt::StringPrintf("file %s has an unexpected entry", filename.c_str()));
   106    }
   107  
   108    return rocksdb::Status::OK();
   109  }
   110  
   111  rocksdb::Status checkFileContents(rocksdb::Env* env, const std::string& filename,
   112                                    const std::string& contents) {
   113    std::string result;
   114    auto status = rocksdb::ReadFileToString(env, filename, &result);
   115    if (!status.ok()) {
   116      return status;
   117    }
   118  
   119    if (contents != result) {
   120      return rocksdb::Status::InvalidArgument(
   121          fmt::StringPrintf("file %s contents mismatch, expected %s, got %s", filename.c_str(),
   122                            contents.c_str(), result.c_str()));
   123    }
   124  
   125    return rocksdb::Status::OK();
   126  }
   127  
   128  };  // anonymous namespace
   129  
   130  TEST(EncryptedEnv, BasicOps) {
   131    // Check various file operations against an encrypted env.
   132    // This exercises the env/file registry interaction.
   133    // We need to use a real underlying env as the MemEnv takes a **lot**
   134    // of shortcuts.
   135    rocksdb::Env* env = rocksdb::Env::Default();
   136    auto key_manager = new MemKeyManager(MakeAES128Key(env));
   137    auto stream = new CTRCipherStreamCreator(key_manager, enginepb::Data);
   138  
   139    auto tmpdir = std::unique_ptr<TempDirHandler>(new TempDirHandler());
   140  
   141    auto file_registry =
   142        std::unique_ptr<FileRegistry>(new FileRegistry(env, tmpdir->Path(""), false /* read-only */));
   143    EXPECT_OK(file_registry->Load());
   144  
   145    std::unique_ptr<rocksdb::Env> encrypted_env(
   146        rocksdb_utils::NewEncryptedEnv(env, file_registry.get(), stream));
   147  
   148    auto file1 = tmpdir->Path("foo1");
   149    auto file2 = tmpdir->Path("foo2");
   150    auto file3 = tmpdir->Path("foo3");
   151  
   152    std::string contents("this is the first file!");
   153    std::string contents2("this is the second file!");
   154    ASSERT_OK(rocksdb::WriteStringToFile(encrypted_env.get(), contents, file1, false));
   155  
   156    // Check its presence in the file registry.
   157    EXPECT_OK(checkFileEntry(*file_registry, file1, enginepbccl::AES128_CTR));
   158  
   159    // Rename file.
   160    ASSERT_OK(encrypted_env->RenameFile(file1, file2));
   161    EXPECT_OK(checkNoFileEntry(*file_registry, file1));
   162    EXPECT_OK(checkFileEntry(*file_registry, file2, enginepbccl::AES128_CTR));
   163  
   164    // Link file (hard link).
   165    ASSERT_OK(encrypted_env->LinkFile(file2, file3));
   166    EXPECT_OK(checkFileEntry(*file_registry, file2, enginepbccl::AES128_CTR));
   167    EXPECT_OK(checkFileEntry(*file_registry, file3, enginepbccl::AES128_CTR));
   168  
   169    // Delete file.
   170    ASSERT_OK(encrypted_env->DeleteFile(file2));
   171    EXPECT_OK(checkNoFileEntry(*file_registry, file2));
   172    EXPECT_OK(checkFileEntry(*file_registry, file3, enginepbccl::AES128_CTR));
   173  
   174    ASSERT_OK(encrypted_env->DeleteFile(file3));
   175    EXPECT_OK(checkNoFileEntry(*file_registry, file2));
   176    EXPECT_OK(checkNoFileEntry(*file_registry, file3));
   177  
   178    /***** Odd cases *****/
   179    ASSERT_OK(rocksdb::WriteStringToFile(encrypted_env.get(), contents, file1, false));
   180    ASSERT_OK(rocksdb::WriteStringToFile(encrypted_env.get(), contents2, file2, false));
   181    EXPECT_OK(checkFileEntry(*file_registry, file1, enginepbccl::AES128_CTR));
   182    EXPECT_OK(checkFileEntry(*file_registry, file2, enginepbccl::AES128_CTR));
   183    EXPECT_OK(checkFileContents(encrypted_env.get(), file1, contents));
   184    EXPECT_OK(checkFileContents(encrypted_env.get(), file2, contents2));
   185  
   186    // Rename to existing file (replace).
   187    ASSERT_OK(encrypted_env->RenameFile(file1, file2));
   188    EXPECT_OK(checkNoFileEntry(*file_registry, file1));
   189    EXPECT_OK(checkFileEntry(*file_registry, file2, enginepbccl::AES128_CTR));
   190    EXPECT_OK(checkFileContents(encrypted_env.get(), file2, contents));
   191  
   192    ASSERT_OK(rocksdb::WriteStringToFile(encrypted_env.get(), contents2, file3, false));
   193    EXPECT_OK(checkFileContents(encrypted_env.get(), file3, contents2));
   194  
   195    // Link to an existing file: fails with "file exists".
   196    ASSERT_ERR(encrypted_env->LinkFile(file2, file3), ".* File exists");
   197    EXPECT_OK(checkFileContents(encrypted_env.get(), file2, contents));
   198    EXPECT_OK(checkFileContents(encrypted_env.get(), file3, contents2));
   199  
   200    // Let's check that the plain env can't read the contents.
   201    EXPECT_PARTIAL_ERR(checkFileContents(env, file2, contents),
   202                       "file .*foo2 contents mismatch, expected this is the first file!, got ");
   203  
   204    /***** Switch to plaintext *****/
   205    key_manager->set_key(nullptr);
   206  
   207    // Rename.
   208    ASSERT_OK(encrypted_env->RenameFile(file2, file1));
   209    EXPECT_OK(checkNoFileEntry(*file_registry, file2));
   210    EXPECT_OK(checkFileEntry(*file_registry, file1, enginepbccl::AES128_CTR));
   211  
   212    // Link.
   213    ASSERT_OK(encrypted_env->LinkFile(file1, file2));
   214    EXPECT_OK(checkFileEntry(*file_registry, file1, enginepbccl::AES128_CTR));
   215    EXPECT_OK(checkFileEntry(*file_registry, file2, enginepbccl::AES128_CTR));
   216  
   217    // Create a new file. This should overwrite the previous file entry.
   218    std::string contents3("we're in plaintext!");
   219    ASSERT_OK(rocksdb::WriteStringToFile(encrypted_env.get(), contents3, file1, false));
   220    EXPECT_OK(checkFileEntry(*file_registry, file1, enginepbccl::Plaintext));
   221    EXPECT_OK(checkFileEntry(*file_registry, file2, enginepbccl::AES128_CTR));
   222    EXPECT_OK(checkFileContents(encrypted_env.get(), file1, contents3));
   223    // Check with the plain env.
   224    EXPECT_OK(checkFileContents(env, file1, contents3));
   225    EXPECT_OK(checkFileContents(env, file2, contents3));
   226  }
   227  
   228  TEST(EncryptedEnv, ReuseWritableFileUnderlyingFailure) {
   229    // Ensure all WALs are either empty or decryptable no matter when/whether
   230    // `EncryptedEnv::ReuseWritableFile` fails. See comment above
   231    // `EncryptedEnv::ReuseWritableFile`'s definition for details on why this
   232    // guarantee is necessary for crash-safety.
   233  
   234    // `ReuseWritableFileInjectionEnv` is used as the env underlying an
   235    // `EncryptedEnv`. It injects faults in the operations used by
   236    // `EncryptedEnv::ReuseWritableFile()`.
   237    class ReuseWritableFileInjectionEnv : public rocksdb::EnvWrapper {
   238     public:
   239      enum class Mode {
   240        kNone,
   241        kFailCreation,
   242        kFailDeletion,
   243        kEnd,
   244      };
   245  
   246      explicit ReuseWritableFileInjectionEnv(Env* target) : EnvWrapper(target), mode_(Mode::kNone) {}
   247  
   248      void set_mode(Mode mode) { mode_ = mode; }
   249  
   250      rocksdb::Status NewWritableFile(const std::string& fname,
   251                                      std::unique_ptr<rocksdb::WritableFile>* result,
   252                                      const rocksdb::EnvOptions& options) override {
   253        if (mode_ == Mode::kFailCreation) {
   254          return rocksdb::Status::IOError("injected error");
   255        }
   256        return rocksdb::EnvWrapper::NewWritableFile(fname, result, options);
   257      }
   258  
   259      rocksdb::Status DeleteFile(const std::string& fname) override {
   260        if (mode_ == Mode::kFailDeletion) {
   261          return rocksdb::Status::IOError("injected error");
   262        }
   263        return rocksdb::EnvWrapper::DeleteFile(fname);
   264      }
   265  
   266     private:
   267      Mode mode_;
   268    };
   269  
   270    for (int i = 0; i < static_cast<int>(ReuseWritableFileInjectionEnv::Mode::kEnd); ++i) {
   271      auto mode = static_cast<ReuseWritableFileInjectionEnv::Mode>(i);
   272      ReuseWritableFileInjectionEnv env(rocksdb::Env::Default());
   273      auto key_manager = new MemKeyManager(MakeAES128Key(&env));
   274      auto stream = new CTRCipherStreamCreator(key_manager, enginepb::Data);
   275  
   276      auto tmpdir = std::unique_ptr<TempDirHandler>(new TempDirHandler());
   277  
   278      auto file_registry = std::unique_ptr<FileRegistry>(
   279          new FileRegistry(&env, tmpdir->Path(""), false /* read-only */));
   280      EXPECT_OK(file_registry->Load());
   281  
   282      std::unique_ptr<rocksdb::Env> encrypted_env(
   283          rocksdb_utils::NewEncryptedEnv(&env, file_registry.get(), stream));
   284  
   285      auto old_file = tmpdir->Path("foo1");
   286      std::string contents("this is a file!");
   287      ASSERT_OK(rocksdb::WriteStringToFile(encrypted_env.get(), contents, old_file, false));
   288  
   289      auto new_file = tmpdir->Path("foo2");
   290      env.set_mode(mode);
   291      {
   292        std::unique_ptr<rocksdb::WritableFile> res;
   293        if (mode == ReuseWritableFileInjectionEnv::Mode::kNone) {
   294          EXPECT_OK(
   295              encrypted_env->ReuseWritableFile(new_file, old_file, &res, rocksdb::EnvOptions()));
   296        } else {
   297          EXPECT_TRUE(
   298              encrypted_env->ReuseWritableFile(new_file, old_file, &res, rocksdb::EnvOptions())
   299                  .IsIOError());
   300        }
   301      }
   302  
   303      switch (mode) {
   304      case ReuseWritableFileInjectionEnv::Mode::kNone: {
   305        // In success scenario, the new file should be empty and the old file should be deleted.
   306        std::string new_file_contents;
   307        EXPECT_OK(rocksdb::ReadFileToString(encrypted_env.get(), new_file, &new_file_contents));
   308        EXPECT_STREQ("", new_file_contents.c_str());
   309        EXPECT_TRUE(encrypted_env->FileExists(old_file).IsNotFound());
   310        break;
   311      }
   312      case ReuseWritableFileInjectionEnv::Mode::kFailCreation:
   313      case ReuseWritableFileInjectionEnv::Mode::kFailDeletion: {
   314        // In failure scenarios, all existing files must be empty or readable.
   315        for (const auto& file : {old_file, new_file}) {
   316          auto exists_status = encrypted_env->FileExists(file);
   317          if (exists_status.ok()) {
   318            std::string file_contents;
   319            rocksdb::ReadFileToString(encrypted_env.get(), file, &file_contents);
   320            if (!file_contents.empty()) {
   321              EXPECT_STREQ(contents.c_str(), file_contents.c_str());
   322            }
   323          } else {
   324            EXPECT_TRUE(exists_status.IsNotFound());
   325          }
   326        }
   327        break;
   328      }
   329      case ReuseWritableFileInjectionEnv::Mode::kEnd:
   330        assert(false);
   331        break;
   332      }
   333    }
   334  }