github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/c-deps/libroach/ccl/db.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 "db.h" 10 #include <iostream> 11 #include <libroachccl.h> 12 #include <memory> 13 #include <rocksdb/comparator.h> 14 #include <rocksdb/iterator.h> 15 #include <rocksdb/utilities/write_batch_with_index.h> 16 #include <rocksdb/write_batch.h> 17 #include "../batch.h" 18 #include "../comparator.h" 19 #include "../encoding.h" 20 #include "../env_manager.h" 21 #include "../options.h" 22 #include "../rocksdbutils/env_encryption.h" 23 #include "../status.h" 24 #include "ccl/baseccl/encryption_options.pb.h" 25 #include "ccl/storageccl/engineccl/enginepbccl/stats.pb.h" 26 #include "crypto_utils.h" 27 #include "ctr_stream.h" 28 #include "key_manager.h" 29 30 using namespace cockroach; 31 32 namespace cockroach { 33 34 class CCLEnvStatsHandler : public EnvStatsHandler { 35 public: 36 explicit CCLEnvStatsHandler(DataKeyManager* data_key_manager) 37 : data_key_manager_(data_key_manager) {} 38 virtual ~CCLEnvStatsHandler() {} 39 40 virtual rocksdb::Status GetEncryptionStats(std::string* serialized_stats) override { 41 if (data_key_manager_ == nullptr) { 42 return rocksdb::Status::OK(); 43 } 44 45 enginepbccl::EncryptionStatus enc_status; 46 47 // GetActiveStoreKeyInfo returns a unique_ptr containing a copy. Transfer ownership from the 48 // unique_ptr to the proto. set_allocated_active_store deletes the existing field, if any. 49 enc_status.set_allocated_active_store_key(data_key_manager_->GetActiveStoreKeyInfo().release()); 50 51 // CurrentKeyInfo returns a unique_ptr containing a copy. Transfer ownership from the 52 // unique_ptr to the proto. set_allocated_active_store deletes the existing field, if any. 53 enc_status.set_allocated_active_data_key(data_key_manager_->CurrentKeyInfo().release()); 54 55 if (!enc_status.SerializeToString(serialized_stats)) { 56 return rocksdb::Status::InvalidArgument("failed to serialize encryption status"); 57 } 58 return rocksdb::Status::OK(); 59 } 60 61 virtual rocksdb::Status GetEncryptionRegistry(std::string* serialized_registry) override { 62 if (data_key_manager_ == nullptr) { 63 return rocksdb::Status::OK(); 64 } 65 66 auto key_registry = data_key_manager_->GetScrubbedRegistry(); 67 if (key_registry == nullptr) { 68 return rocksdb::Status::OK(); 69 } 70 71 if (!key_registry->SerializeToString(serialized_registry)) { 72 return rocksdb::Status::InvalidArgument("failed to serialize data keys registry"); 73 } 74 75 return rocksdb::Status::OK(); 76 } 77 78 virtual std::string GetActiveDataKeyID() override { 79 // Look up the current data key. 80 if (data_key_manager_ == nullptr) { 81 // No data key manager: plaintext. 82 return kPlainKeyID; 83 } 84 85 auto active_key_info = data_key_manager_->CurrentKeyInfo(); 86 if (active_key_info == nullptr) { 87 // Plaintext. 88 return kPlainKeyID; 89 } 90 return active_key_info->key_id(); 91 } 92 93 virtual int32_t GetActiveStoreKeyType() override { 94 if (data_key_manager_ == nullptr) { 95 return enginepbccl::Plaintext; 96 } 97 98 auto store_key_info = data_key_manager_->GetActiveStoreKeyInfo(); 99 if (store_key_info == nullptr) { 100 return enginepbccl::Plaintext; 101 } 102 103 return store_key_info->encryption_type(); 104 } 105 106 virtual rocksdb::Status GetFileEntryKeyID(const enginepb::FileEntry* entry, 107 std::string* id) override { 108 if (entry == nullptr) { 109 // No file entry: file written in plaintext before the file registry was used. 110 *id = kPlainKeyID; 111 return rocksdb::Status::OK(); 112 } 113 114 enginepbccl::EncryptionSettings enc_settings; 115 if (!enc_settings.ParseFromString(entry->encryption_settings())) { 116 return rocksdb::Status::InvalidArgument("failed to parse encryption settings"); 117 } 118 119 if (enc_settings.encryption_type() == enginepbccl::Plaintext) { 120 *id = kPlainKeyID; 121 } else { 122 *id = enc_settings.key_id(); 123 } 124 125 return rocksdb::Status::OK(); 126 } 127 128 private: 129 // The DataKeyManager is needed to get key information but is not owned by the StatsHandler. 130 DataKeyManager* data_key_manager_; 131 }; 132 133 // DBOpenHookCCL parses the extra_options field of DBOptions and initializes 134 // encryption objects if needed. 135 rocksdb::Status DBOpenHookCCL(std::shared_ptr<rocksdb::Logger> info_log, const std::string& db_dir, 136 const DBOptions db_opts, EnvManager* env_mgr) { 137 DBSlice options = db_opts.extra_options; 138 if (options.len == 0) { 139 return rocksdb::Status::OK(); 140 } 141 142 // The Go code sets the "file_registry" storage version if we specified encryption flags, 143 // but let's double check anyway. 144 if (!db_opts.use_file_registry) { 145 return rocksdb::Status::InvalidArgument( 146 "on-disk version does not support encryption, but we found encryption flags"); 147 } 148 149 // We log to the primary CockroachDB log for encryption status 150 // instead of the RocksDB specific log. This should only be used to 151 // occasional logging (eg: key loading and rotation). 152 std::shared_ptr<rocksdb::Logger> logger(NewDBLogger(true /* use_primary_log */)); 153 154 // We have encryption options. Check whether the AES instruction set is supported. 155 if (!UsesAESNI()) { 156 rocksdb::Warn( 157 logger, "*** WARNING*** Encryption requested, but no AES instruction set detected: expect " 158 "significant performance degradation!"); 159 } 160 161 // Attempt to disable core dumps. 162 auto status = DisableCoreFile(); 163 if (!status.ok()) { 164 rocksdb::Warn(logger, 165 "*** WARNING*** Encryption requested, but could not disable core dumps: %s. Keys " 166 "may be leaked in core dumps!", 167 status.getState()); 168 } 169 170 // Parse extra_options. 171 cockroach::ccl::baseccl::EncryptionOptions opts; 172 if (!opts.ParseFromArray(options.data, options.len)) { 173 return rocksdb::Status::InvalidArgument("failed to parse extra options"); 174 } 175 176 if (opts.key_source() != cockroach::ccl::baseccl::KeyFiles) { 177 return rocksdb::Status::InvalidArgument("unknown encryption key source"); 178 } 179 180 // Initialize store key manager. 181 // NOTE: FileKeyManager uses the default env as the MemEnv can never have pre-populated files. 182 FileKeyManager* store_key_manager = new FileKeyManager( 183 rocksdb::Env::Default(), logger, opts.key_files().current_key(), opts.key_files().old_key()); 184 status = store_key_manager->LoadKeys(); 185 if (!status.ok()) { 186 delete store_key_manager; 187 return status; 188 } 189 190 // Create a cipher stream creator using the store_key_manager. 191 auto store_stream = new CTRCipherStreamCreator(store_key_manager, enginepb::Store); 192 193 // Construct an EncryptedEnv using this stream creator and the base_env (Default or Mem). 194 // It takes ownership of the stream. 195 rocksdb::Env* store_keyed_env = 196 rocksdb_utils::NewEncryptedEnv(env_mgr->base_env, env_mgr->file_registry.get(), store_stream); 197 // Transfer ownership to the env manager. 198 env_mgr->TakeEnvOwnership(store_keyed_env); 199 200 // Initialize data key manager using the stored-keyed-env. 201 DataKeyManager* data_key_manager = new DataKeyManager( 202 store_keyed_env, logger, db_dir, opts.data_key_rotation_period(), db_opts.read_only); 203 status = data_key_manager->LoadKeys(); 204 if (!status.ok()) { 205 delete data_key_manager; 206 return status; 207 } 208 209 // Create a cipher stream creator using the data_key_manager. 210 auto data_stream = new CTRCipherStreamCreator(data_key_manager, enginepb::Data); 211 212 // Construct an EncryptedEnv using this stream creator and the base_env (Default or Mem). 213 // It takes ownership of the stream. 214 rocksdb::Env* data_keyed_env = 215 rocksdb_utils::NewEncryptedEnv(env_mgr->base_env, env_mgr->file_registry.get(), data_stream); 216 // Transfer ownership to the env manager and make it as the db_env. 217 env_mgr->TakeEnvOwnership(data_keyed_env); 218 env_mgr->db_env = data_keyed_env; 219 220 // Fetch the current store key info. 221 std::unique_ptr<enginepbccl::KeyInfo> store_key = store_key_manager->CurrentKeyInfo(); 222 assert(store_key != nullptr); 223 224 if (!db_opts.read_only) { 225 // Generate a new data key if needed by giving the active store key info to the data key 226 // manager. 227 status = data_key_manager->SetActiveStoreKeyInfo(std::move(store_key)); 228 if (!status.ok()) { 229 return status; 230 } 231 } 232 233 // Everything's ok: initialize a stats handler. 234 env_mgr->SetStatsHandler(new CCLEnvStatsHandler(data_key_manager)); 235 236 return rocksdb::Status::OK(); 237 } 238 239 } // namespace cockroach 240 241 void* DBOpenHookCCL = (void*)cockroach::DBOpenHookCCL; 242 243 DBStatus DBBatchReprVerify(DBSlice repr, DBKey start, DBKey end, int64_t now_nanos, 244 MVCCStatsResult* stats) { 245 // TODO(dan): Inserting into a batch just to iterate over it is unfortunate. 246 // Consider replacing this with WriteBatch's Iterate/Handler mechanism and 247 // computing MVCC stats on the post-ApplyBatchRepr engine. splitTrigger does 248 // the latter and it's a headache for propEvalKV, so wait to see how that 249 // settles out before doing it that way. 250 rocksdb::WriteBatchWithIndex batch(&kComparator, 0, true); 251 rocksdb::WriteBatch b(ToString(repr)); 252 std::unique_ptr<rocksdb::WriteBatch::Handler> inserter(GetDBBatchInserter(&batch)); 253 rocksdb::Status status = b.Iterate(inserter.get()); 254 if (!status.ok()) { 255 return ToDBStatus(status); 256 } 257 std::unique_ptr<rocksdb::Iterator> iter; 258 iter.reset(batch.NewIteratorWithBase(rocksdb::NewEmptyIterator())); 259 260 iter->SeekToFirst(); 261 if (iter->Valid() && kComparator.Compare(iter->key(), EncodeKey(start)) < 0) { 262 return FmtStatus("key not in request range"); 263 } 264 iter->SeekToLast(); 265 if (iter->Valid() && kComparator.Compare(iter->key(), EncodeKey(end)) >= 0) { 266 return FmtStatus("key not in request range"); 267 } 268 269 *stats = MVCCComputeStatsInternal(iter.get(), start, end, now_nanos); 270 271 return kSuccess; 272 }