diff --git a/Makefile b/Makefile index 8cad1049db22..abbe4cc73994 100644 --- a/Makefile +++ b/Makefile @@ -462,7 +462,7 @@ UI_JS := $(UI_ROOT)/src/js/protos.js UI_TS := $(UI_ROOT)/src/js/protos.d.ts UI_PROTOS := $(UI_JS) $(UI_TS) -CPP_PROTOS := $(filter %/roachpb/metadata.proto %/roachpb/data.proto %/roachpb/internal.proto %/engine/enginepb/mvcc.proto %/engine/enginepb/mvcc3.proto %/engine/enginepb/rocksdb.proto %/hlc/legacy_timestamp.proto %/hlc/timestamp.proto %/unresolved_addr.proto,$(GO_PROTOS)) +CPP_PROTOS := $(filter %/roachpb/metadata.proto %/roachpb/data.proto %/roachpb/internal.proto %/storage/preamble.proto %/engine/enginepb/mvcc.proto %/engine/enginepb/mvcc3.proto %/engine/enginepb/rocksdb.proto %/hlc/legacy_timestamp.proto %/hlc/timestamp.proto %/unresolved_addr.proto,$(GO_PROTOS)) CPP_HEADERS := $(subst $(PKG_ROOT),$(CPP_PROTO_ROOT),$(CPP_PROTOS:%.proto=%.pb.h)) CPP_SOURCES := $(subst $(PKG_ROOT),$(CPP_PROTO_ROOT),$(CPP_PROTOS:%.proto=%.pb.cc)) diff --git a/c-deps/libroach/CMakeLists.txt b/c-deps/libroach/CMakeLists.txt index 31245aed8424..39953c426655 100644 --- a/c-deps/libroach/CMakeLists.txt +++ b/c-deps/libroach/CMakeLists.txt @@ -24,9 +24,11 @@ add_library(roach db.cc encoding.cc eventlistener.cc + preamble.cc protos/roachpb/data.pb.cc protos/roachpb/internal.pb.cc protos/roachpb/metadata.pb.cc + protos/storage/preamble.pb.cc protos/storage/engine/enginepb/mvcc.pb.cc protos/storage/engine/enginepb/mvcc3.pb.cc protos/storage/engine/enginepb/rocksdb.pb.cc diff --git a/c-deps/libroach/db.cc b/c-deps/libroach/db.cc index 2ffb2d59b21d..339076761edd 100644 --- a/c-deps/libroach/db.cc +++ b/c-deps/libroach/db.cc @@ -35,6 +35,7 @@ #include "encoding.h" #include "eventlistener.h" #include "keys.h" +#include "preamble.h" extern "C" { static void __attribute__((noreturn)) die_missing_symbol(const char* name) { @@ -89,6 +90,7 @@ struct DBEngine { }; struct DBImpl : public DBEngine { + std::unique_ptr preamble_handler; std::unique_ptr memenv; std::unique_ptr rep_deleter; std::shared_ptr block_cache; @@ -98,8 +100,9 @@ struct DBImpl : public DBEngine { // and Env will be deleted when the DBImpl is deleted. It is ok to // pass NULL for the Env. DBImpl(rocksdb::DB* r, rocksdb::Env* m, std::shared_ptr bc, - std::shared_ptr event_listener) + std::shared_ptr event_listener, PreambleHandler* preamble) : DBEngine(r), + preamble_handler(preamble), memenv(m), rep_deleter(r), block_cache(bc), @@ -1697,6 +1700,14 @@ DBStatus DBOpen(DBEngine **db, DBSlice dir, DBOptions db_opts) { options.env = memenv.get(); } + PreambleHandler* preamble = nullptr; + if (db_opts.use_preamble) { + // The caller makes sure we're not an in-memory DB. + assert(dir.len != 0); + preamble = new PreambleHandler(); + options.env = preamble->GetEnv(options.env); + } + rocksdb::DB *db_ptr; rocksdb::Status status = rocksdb::DB::Open(options, ToString(dir), &db_ptr); if (!status.ok()) { @@ -1704,7 +1715,7 @@ DBStatus DBOpen(DBEngine **db, DBSlice dir, DBOptions db_opts) { } *db = new DBImpl(db_ptr, memenv.release(), db_opts.cache != nullptr ? db_opts.cache->rep : nullptr, - event_listener); + event_listener, preamble); return kSuccess; } diff --git a/c-deps/libroach/include/libroach.h b/c-deps/libroach/include/libroach.h index f7f36417b449..576426b2fa64 100644 --- a/c-deps/libroach/include/libroach.h +++ b/c-deps/libroach/include/libroach.h @@ -71,6 +71,7 @@ typedef struct { bool logging_enabled; int num_cpu; int max_open_files; + bool use_preamble; } DBOptions; // Create a new cache with the specified size. diff --git a/c-deps/libroach/preamble.cc b/c-deps/libroach/preamble.cc new file mode 100644 index 000000000000..dd8887847d9e --- /dev/null +++ b/c-deps/libroach/preamble.cc @@ -0,0 +1,120 @@ +// Copyright 2017 The Cockroach Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. + +#include +#include "preamble.h" +#include "protos/storage/preamble.pb.h" + +// Preamble length. +// WARNING: changing this will result in incompatible on-disk format. +// The preamble length must fit in a uint16_t. +const static size_t kPreambleLength = 4096; + +// Blocksize for the plaintext cipher stream. +// TODO(mberhault): we need to benchmark this for a good value, but for now we use the AES::BlockSize. +const static size_t kPlaintextBlockSize = 16; + +// PlaintextCipherStream implements BlockAccessCipherStream with +// no-op encrypt/decrypt operations. +class PlaintextCipherStream final : public rocksdb::BlockAccessCipherStream { + public: + PlaintextCipherStream() {} + virtual ~PlaintextCipherStream() {} + + // BlockSize returns the size of each block supported by this cipher stream. + virtual size_t BlockSize() override { return kPlaintextBlockSize; } + + // Encrypt blocks of data. This is a noop. + virtual rocksdb::Status Encrypt(uint64_t fileOffset, char *data, size_t dataSize) override { + return rocksdb::Status::OK(); + } + + // Decrypt blocks of data. This is a noop. + virtual rocksdb::Status Decrypt(uint64_t fileOffset, char *data, size_t dataSize) override { + return rocksdb::Status::OK(); + } + protected: + // No-op required methods. + virtual void AllocateScratch(std::string&) override {} + virtual rocksdb::Status EncryptBlock(uint64_t blockIndex, char *data, char* scratch) override { + return rocksdb::Status::OK(); + } + virtual rocksdb::Status DecryptBlock(uint64_t blockIndex, char *data, char* scratch) override { + return rocksdb::Status::OK(); + } +}; + +size_t PreambleHandler::GetPrefixLength() { + return kPreambleLength; +} + +rocksdb::Env* PreambleHandler::GetEnv(rocksdb::Env* base_env) { + return rocksdb::NewEncryptedEnv(base_env ? base_env : rocksdb::Env::Default(), this); +} + +rocksdb::Status PreambleHandler::CreateNewPrefix(const std::string& fname, char *prefix, size_t prefixLength) { + // Zero-out the prefix. + memset(prefix, 0, prefixLength); + + // Create a preamble proto with encryption settings. + cockroach::storage::Preamble preamble; + // Everything is plaintext for now. + preamble.set_encryption_type(cockroach::storage::Plaintext); + + // Check the byte size before encoding. + int byte_size = preamble.ByteSize(); + + // Determine the serialized length and size of the length prefix. + assert(byte_size < UINT16_MAX); + uint16_t encoded_size = htons(byte_size); + auto num_length_bytes = sizeof(encoded_size); + + if ((byte_size + num_length_bytes) > prefixLength ) { + return rocksdb::Status::Corruption("new preamble exceeds max preamble length"); + } + + // Write length prefix. + memcpy(prefix, &encoded_size, num_length_bytes); + + // Write it to the prefix. + if (!preamble.SerializeToArray(prefix + num_length_bytes, byte_size)) { + return rocksdb::Status::Corruption("unable to write prefix"); + } + + return rocksdb::Status::OK(); +} + +rocksdb::Status PreambleHandler::CreateCipherStream(const std::string& fname, const rocksdb::EnvOptions& options, rocksdb::Slice &prefix, std::unique_ptr* result) { + // Read length prefix. + uint16_t encoded_size; + auto num_length_bytes = sizeof(encoded_size); + memcpy(&encoded_size, prefix.data(), num_length_bytes); + + // Convert length prefix from network byte order. + int byte_size = ntohs(encoded_size); + + // Parse prefix + cockroach::storage::Preamble preamble; + if (!preamble.ParseFromArray(prefix.data() + num_length_bytes, byte_size)) { + return rocksdb::Status::Corruption("unable to parse prefix"); + } + + if (preamble.encryption_type() == cockroach::storage::Plaintext) { + (*result) = std::unique_ptr(new PlaintextCipherStream()); + } else { + return rocksdb::Status::NotSupported("unknown encryption type"); + } + + return rocksdb::Status::OK(); +} diff --git a/c-deps/libroach/preamble.h b/c-deps/libroach/preamble.h new file mode 100644 index 000000000000..50a7d0d75554 --- /dev/null +++ b/c-deps/libroach/preamble.h @@ -0,0 +1,41 @@ +// Copyright 2017 The Cockroach Authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or +// implied. See the License for the specific language governing +// permissions and limitations under the License. + +#ifndef ROACHLIB_PREAMBLE_H +#define ROACHLIB_PREAMBLE_H + +#include +#include + +class PreambleHandler : rocksdb::EncryptionProvider { + public: + + PreambleHandler() {} + virtual ~PreambleHandler() {} + + // GetEnv returns an EncryptionEnv wrapped around base_env. + rocksdb::Env* GetEnv(rocksdb::Env* base_env); + + // GetPrefixLength returns the preamble length. + virtual size_t GetPrefixLength() override; + + // CreateNewPrefix initializes an allocated block of prefix memory for a new file. + virtual rocksdb::Status CreateNewPrefix(const std::string& fname, char *prefix, size_t prefixLength) override; + + // CreateCipherStream creates a block access cipher stream for a file given name and options. + virtual rocksdb::Status CreateCipherStream(const std::string& fname, const rocksdb::EnvOptions& options, + rocksdb::Slice& prefix, std::unique_ptr* result) override; +}; + +#endif // ROACHLIB_PREAMBLE_H diff --git a/c-deps/libroach/protos/storage/preamble.pb.cc b/c-deps/libroach/protos/storage/preamble.pb.cc new file mode 100644 index 000000000000..a93f483331fd --- /dev/null +++ b/c-deps/libroach/protos/storage/preamble.pb.cc @@ -0,0 +1,296 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: storage/preamble.proto + +#define INTERNAL_SUPPRESS_PROTOBUF_FIELD_DEPRECATION +#include "storage/preamble.pb.h" + +#include + +#include +#include +#include +#include +#include +#include +// @@protoc_insertion_point(includes) + +namespace cockroach { +namespace storage { +class PreambleDefaultTypeInternal { +public: + ::google::protobuf::internal::ExplicitlyConstructed + _instance; +} _Preamble_default_instance_; + +namespace protobuf_storage_2fpreamble_2eproto { + +PROTOBUF_CONSTEXPR_VAR ::google::protobuf::internal::ParseTableField + const TableStruct::entries[] GOOGLE_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = { + {0, 0, 0, ::google::protobuf::internal::kInvalidMask, 0, 0}, +}; + +PROTOBUF_CONSTEXPR_VAR ::google::protobuf::internal::AuxillaryParseTableField + const TableStruct::aux[] GOOGLE_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = { + ::google::protobuf::internal::AuxillaryParseTableField(), +}; +PROTOBUF_CONSTEXPR_VAR ::google::protobuf::internal::ParseTable const + TableStruct::schema[] GOOGLE_ATTRIBUTE_SECTION_VARIABLE(protodesc_cold) = { + { NULL, NULL, 0, -1, -1, -1, -1, NULL, false }, +}; + +void TableStruct::InitDefaultsImpl() { + GOOGLE_PROTOBUF_VERIFY_VERSION; + + ::google::protobuf::internal::InitProtobufDefaults(); + ::google::protobuf::protobuf_google_2fprotobuf_2ftimestamp_2eproto::InitDefaults(); + _Preamble_default_instance_._instance.DefaultConstruct(); + ::google::protobuf::internal::OnShutdownDestroyMessage( + &_Preamble_default_instance_);} + +void InitDefaults() { + static GOOGLE_PROTOBUF_DECLARE_ONCE(once); + ::google::protobuf::GoogleOnceInit(&once, &TableStruct::InitDefaultsImpl); +} +namespace { +void AddDescriptorsImpl() { + InitDefaults(); + ::google::protobuf::protobuf_google_2fprotobuf_2ftimestamp_2eproto::AddDescriptors(); +} +} // anonymous namespace + +void AddDescriptors() { + static GOOGLE_PROTOBUF_DECLARE_ONCE(once); + ::google::protobuf::GoogleOnceInit(&once, &AddDescriptorsImpl); +} + +} // namespace protobuf_storage_2fpreamble_2eproto + +bool EncryptionType_IsValid(int value) { + switch (value) { + case 0: + return true; + default: + return false; + } +} + + +// =================================================================== + +#if !defined(_MSC_VER) || _MSC_VER >= 1900 +const int Preamble::kEncryptionTypeFieldNumber; +#endif // !defined(_MSC_VER) || _MSC_VER >= 1900 + +Preamble::Preamble() + : ::google::protobuf::MessageLite(), _internal_metadata_(NULL) { + if (GOOGLE_PREDICT_TRUE(this != internal_default_instance())) { + protobuf_storage_2fpreamble_2eproto::InitDefaults(); + } + SharedCtor(); + // @@protoc_insertion_point(constructor:cockroach.storage.Preamble) +} +Preamble::Preamble(const Preamble& from) + : ::google::protobuf::MessageLite(), + _internal_metadata_(NULL), + _cached_size_(0) { + _internal_metadata_.MergeFrom(from._internal_metadata_); + encryption_type_ = from.encryption_type_; + // @@protoc_insertion_point(copy_constructor:cockroach.storage.Preamble) +} + +void Preamble::SharedCtor() { + encryption_type_ = 0; + _cached_size_ = 0; +} + +Preamble::~Preamble() { + // @@protoc_insertion_point(destructor:cockroach.storage.Preamble) + SharedDtor(); +} + +void Preamble::SharedDtor() { +} + +void Preamble::SetCachedSize(int size) const { + GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN(); + _cached_size_ = size; + GOOGLE_SAFE_CONCURRENT_WRITES_END(); +} +const Preamble& Preamble::default_instance() { + protobuf_storage_2fpreamble_2eproto::InitDefaults(); + return *internal_default_instance(); +} + +Preamble* Preamble::New(::google::protobuf::Arena* arena) const { + Preamble* n = new Preamble; + if (arena != NULL) { + arena->Own(n); + } + return n; +} + +void Preamble::Clear() { +// @@protoc_insertion_point(message_clear_start:cockroach.storage.Preamble) + ::google::protobuf::uint32 cached_has_bits = 0; + // Prevent compiler warnings about cached_has_bits being unused + (void) cached_has_bits; + + encryption_type_ = 0; + _internal_metadata_.Clear(); +} + +bool Preamble::MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input) { +#define DO_(EXPRESSION) if (!GOOGLE_PREDICT_TRUE(EXPRESSION)) goto failure + ::google::protobuf::uint32 tag; + ::google::protobuf::io::LazyStringOutputStream unknown_fields_string( + ::google::protobuf::NewPermanentCallback(&_internal_metadata_, + &::google::protobuf::internal::InternalMetadataWithArenaLite:: + mutable_unknown_fields)); + ::google::protobuf::io::CodedOutputStream unknown_fields_stream( + &unknown_fields_string, false); + // @@protoc_insertion_point(parse_start:cockroach.storage.Preamble) + for (;;) { + ::std::pair< ::google::protobuf::uint32, bool> p = input->ReadTagWithCutoffNoLastTag(127u); + tag = p.first; + if (!p.second) goto handle_unusual; + switch (::google::protobuf::internal::WireFormatLite::GetTagFieldNumber(tag)) { + // .cockroach.storage.EncryptionType encryption_type = 1; + case 1: { + if (static_cast< ::google::protobuf::uint8>(tag) == + static_cast< ::google::protobuf::uint8>(8u /* 8 & 0xFF */)) { + int value; + DO_((::google::protobuf::internal::WireFormatLite::ReadPrimitive< + int, ::google::protobuf::internal::WireFormatLite::TYPE_ENUM>( + input, &value))); + set_encryption_type(static_cast< ::cockroach::storage::EncryptionType >(value)); + } else { + goto handle_unusual; + } + break; + } + + default: { + handle_unusual: + if (tag == 0) { + goto success; + } + DO_(::google::protobuf::internal::WireFormatLite::SkipField( + input, tag, &unknown_fields_stream)); + break; + } + } + } +success: + // @@protoc_insertion_point(parse_success:cockroach.storage.Preamble) + return true; +failure: + // @@protoc_insertion_point(parse_failure:cockroach.storage.Preamble) + return false; +#undef DO_ +} + +void Preamble::SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const { + // @@protoc_insertion_point(serialize_start:cockroach.storage.Preamble) + ::google::protobuf::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + // .cockroach.storage.EncryptionType encryption_type = 1; + if (this->encryption_type() != 0) { + ::google::protobuf::internal::WireFormatLite::WriteEnum( + 1, this->encryption_type(), output); + } + + output->WriteRaw((::google::protobuf::internal::GetProto3PreserveUnknownsDefault() ? _internal_metadata_.unknown_fields() : _internal_metadata_.default_instance()).data(), + static_cast((::google::protobuf::internal::GetProto3PreserveUnknownsDefault() ? _internal_metadata_.unknown_fields() : _internal_metadata_.default_instance()).size())); + // @@protoc_insertion_point(serialize_end:cockroach.storage.Preamble) +} + +size_t Preamble::ByteSizeLong() const { +// @@protoc_insertion_point(message_byte_size_start:cockroach.storage.Preamble) + size_t total_size = 0; + + total_size += (::google::protobuf::internal::GetProto3PreserveUnknownsDefault() ? _internal_metadata_.unknown_fields() : _internal_metadata_.default_instance()).size(); + + // .cockroach.storage.EncryptionType encryption_type = 1; + if (this->encryption_type() != 0) { + total_size += 1 + + ::google::protobuf::internal::WireFormatLite::EnumSize(this->encryption_type()); + } + + int cached_size = ::google::protobuf::internal::ToCachedSize(total_size); + GOOGLE_SAFE_CONCURRENT_WRITES_BEGIN(); + _cached_size_ = cached_size; + GOOGLE_SAFE_CONCURRENT_WRITES_END(); + return total_size; +} + +void Preamble::CheckTypeAndMergeFrom( + const ::google::protobuf::MessageLite& from) { + MergeFrom(*::google::protobuf::down_cast(&from)); +} + +void Preamble::MergeFrom(const Preamble& from) { +// @@protoc_insertion_point(class_specific_merge_from_start:cockroach.storage.Preamble) + GOOGLE_DCHECK_NE(&from, this); + _internal_metadata_.MergeFrom(from._internal_metadata_); + ::google::protobuf::uint32 cached_has_bits = 0; + (void) cached_has_bits; + + if (from.encryption_type() != 0) { + set_encryption_type(from.encryption_type()); + } +} + +void Preamble::CopyFrom(const Preamble& from) { +// @@protoc_insertion_point(class_specific_copy_from_start:cockroach.storage.Preamble) + if (&from == this) return; + Clear(); + MergeFrom(from); +} + +bool Preamble::IsInitialized() const { + return true; +} + +void Preamble::Swap(Preamble* other) { + if (other == this) return; + InternalSwap(other); +} +void Preamble::InternalSwap(Preamble* other) { + using std::swap; + swap(encryption_type_, other->encryption_type_); + _internal_metadata_.Swap(&other->_internal_metadata_); + swap(_cached_size_, other->_cached_size_); +} + +::std::string Preamble::GetTypeName() const { + return "cockroach.storage.Preamble"; +} + +#if PROTOBUF_INLINE_NOT_IN_HEADERS +// Preamble + +// .cockroach.storage.EncryptionType encryption_type = 1; +void Preamble::clear_encryption_type() { + encryption_type_ = 0; +} +::cockroach::storage::EncryptionType Preamble::encryption_type() const { + // @@protoc_insertion_point(field_get:cockroach.storage.Preamble.encryption_type) + return static_cast< ::cockroach::storage::EncryptionType >(encryption_type_); +} +void Preamble::set_encryption_type(::cockroach::storage::EncryptionType value) { + + encryption_type_ = value; + // @@protoc_insertion_point(field_set:cockroach.storage.Preamble.encryption_type) +} + +#endif // PROTOBUF_INLINE_NOT_IN_HEADERS + +// @@protoc_insertion_point(namespace_scope) + +} // namespace storage +} // namespace cockroach + +// @@protoc_insertion_point(global_scope) diff --git a/c-deps/libroach/protos/storage/preamble.pb.h b/c-deps/libroach/protos/storage/preamble.pb.h new file mode 100644 index 000000000000..b640acc06344 --- /dev/null +++ b/c-deps/libroach/protos/storage/preamble.pb.h @@ -0,0 +1,212 @@ +// Generated by the protocol buffer compiler. DO NOT EDIT! +// source: storage/preamble.proto + +#ifndef PROTOBUF_storage_2fpreamble_2eproto__INCLUDED +#define PROTOBUF_storage_2fpreamble_2eproto__INCLUDED + +#include + +#include + +#if GOOGLE_PROTOBUF_VERSION < 3004000 +#error This file was generated by a newer version of protoc which is +#error incompatible with your Protocol Buffer headers. Please update +#error your headers. +#endif +#if 3004000 < GOOGLE_PROTOBUF_MIN_PROTOC_VERSION +#error This file was generated by an older version of protoc which is +#error incompatible with your Protocol Buffer headers. Please +#error regenerate this file with a newer version of protoc. +#endif + +#include +#include +#include +#include +#include +#include +#include +#include // IWYU pragma: export +#include // IWYU pragma: export +#include +#include +// @@protoc_insertion_point(includes) +namespace cockroach { +namespace storage { +class Preamble; +class PreambleDefaultTypeInternal; +extern PreambleDefaultTypeInternal _Preamble_default_instance_; +} // namespace storage +} // namespace cockroach + +namespace cockroach { +namespace storage { + +namespace protobuf_storage_2fpreamble_2eproto { +// Internal implementation detail -- do not call these. +struct TableStruct { + static const ::google::protobuf::internal::ParseTableField entries[]; + static const ::google::protobuf::internal::AuxillaryParseTableField aux[]; + static const ::google::protobuf::internal::ParseTable schema[]; + static const ::google::protobuf::uint32 offsets[]; + static const ::google::protobuf::internal::FieldMetadata field_metadata[]; + static const ::google::protobuf::internal::SerializationTable serialization_table[]; + static void InitDefaultsImpl(); +}; +void AddDescriptors(); +void InitDefaults(); +} // namespace protobuf_storage_2fpreamble_2eproto + +enum EncryptionType { + Plaintext = 0, + EncryptionType_INT_MIN_SENTINEL_DO_NOT_USE_ = ::google::protobuf::kint32min, + EncryptionType_INT_MAX_SENTINEL_DO_NOT_USE_ = ::google::protobuf::kint32max +}; +bool EncryptionType_IsValid(int value); +const EncryptionType EncryptionType_MIN = Plaintext; +const EncryptionType EncryptionType_MAX = Plaintext; +const int EncryptionType_ARRAYSIZE = EncryptionType_MAX + 1; + +// =================================================================== + +class Preamble : public ::google::protobuf::MessageLite /* @@protoc_insertion_point(class_definition:cockroach.storage.Preamble) */ { + public: + Preamble(); + virtual ~Preamble(); + + Preamble(const Preamble& from); + + inline Preamble& operator=(const Preamble& from) { + CopyFrom(from); + return *this; + } + #if LANG_CXX11 + Preamble(Preamble&& from) noexcept + : Preamble() { + *this = ::std::move(from); + } + + inline Preamble& operator=(Preamble&& from) noexcept { + if (GetArenaNoVirtual() == from.GetArenaNoVirtual()) { + if (this != &from) InternalSwap(&from); + } else { + CopyFrom(from); + } + return *this; + } + #endif + static const Preamble& default_instance(); + + static inline const Preamble* internal_default_instance() { + return reinterpret_cast( + &_Preamble_default_instance_); + } + static PROTOBUF_CONSTEXPR int const kIndexInFileMessages = + 0; + + void Swap(Preamble* other); + friend void swap(Preamble& a, Preamble& b) { + a.Swap(&b); + } + + // implements Message ---------------------------------------------- + + inline Preamble* New() const PROTOBUF_FINAL { return New(NULL); } + + Preamble* New(::google::protobuf::Arena* arena) const PROTOBUF_FINAL; + void CheckTypeAndMergeFrom(const ::google::protobuf::MessageLite& from) + PROTOBUF_FINAL; + void CopyFrom(const Preamble& from); + void MergeFrom(const Preamble& from); + void Clear() PROTOBUF_FINAL; + bool IsInitialized() const PROTOBUF_FINAL; + + size_t ByteSizeLong() const PROTOBUF_FINAL; + bool MergePartialFromCodedStream( + ::google::protobuf::io::CodedInputStream* input) PROTOBUF_FINAL; + void SerializeWithCachedSizes( + ::google::protobuf::io::CodedOutputStream* output) const PROTOBUF_FINAL; + void DiscardUnknownFields(); + int GetCachedSize() const PROTOBUF_FINAL { return _cached_size_; } + private: + void SharedCtor(); + void SharedDtor(); + void SetCachedSize(int size) const; + void InternalSwap(Preamble* other); + private: + inline ::google::protobuf::Arena* GetArenaNoVirtual() const { + return NULL; + } + inline void* MaybeArenaPtr() const { + return NULL; + } + public: + + ::std::string GetTypeName() const PROTOBUF_FINAL; + + // nested types ---------------------------------------------------- + + // accessors ------------------------------------------------------- + + // .cockroach.storage.EncryptionType encryption_type = 1; + void clear_encryption_type(); + static const int kEncryptionTypeFieldNumber = 1; + ::cockroach::storage::EncryptionType encryption_type() const; + void set_encryption_type(::cockroach::storage::EncryptionType value); + + // @@protoc_insertion_point(class_scope:cockroach.storage.Preamble) + private: + + ::google::protobuf::internal::InternalMetadataWithArenaLite _internal_metadata_; + int encryption_type_; + mutable int _cached_size_; + friend struct protobuf_storage_2fpreamble_2eproto::TableStruct; +}; +// =================================================================== + + +// =================================================================== + +#if !PROTOBUF_INLINE_NOT_IN_HEADERS +#ifdef __GNUC__ + #pragma GCC diagnostic push + #pragma GCC diagnostic ignored "-Wstrict-aliasing" +#endif // __GNUC__ +// Preamble + +// .cockroach.storage.EncryptionType encryption_type = 1; +inline void Preamble::clear_encryption_type() { + encryption_type_ = 0; +} +inline ::cockroach::storage::EncryptionType Preamble::encryption_type() const { + // @@protoc_insertion_point(field_get:cockroach.storage.Preamble.encryption_type) + return static_cast< ::cockroach::storage::EncryptionType >(encryption_type_); +} +inline void Preamble::set_encryption_type(::cockroach::storage::EncryptionType value) { + + encryption_type_ = value; + // @@protoc_insertion_point(field_set:cockroach.storage.Preamble.encryption_type) +} + +#ifdef __GNUC__ + #pragma GCC diagnostic pop +#endif // __GNUC__ +#endif // !PROTOBUF_INLINE_NOT_IN_HEADERS + +// @@protoc_insertion_point(namespace_scope) + + +} // namespace storage +} // namespace cockroach + +namespace google { +namespace protobuf { + +template <> struct is_proto_enum< ::cockroach::storage::EncryptionType> : ::google::protobuf::internal::true_type {}; + +} // namespace protobuf +} // namespace google + +// @@protoc_insertion_point(global_scope) + +#endif // PROTOBUF_storage_2fpreamble_2eproto__INCLUDED diff --git a/pkg/base/store_spec.go b/pkg/base/store_spec.go index 4bf4c1b04f0e..11f7a50779f0 100644 --- a/pkg/base/store_spec.go +++ b/pkg/base/store_spec.go @@ -47,10 +47,11 @@ type StoreSpec struct { // SizeInBytes is used for calculating free space and making rebalancing // decisions. Zero indicates that there is no maximum size. This value is not // actually used by the engine and thus not enforced. - SizeInBytes int64 - SizePercent float64 - InMemory bool - Attributes roachpb.Attributes + SizeInBytes int64 + SizePercent float64 + InMemory bool + Attributes roachpb.Attributes + PreambleFormat bool } // String returns a fully parsable version of the store spec. @@ -68,6 +69,9 @@ func (ss StoreSpec) String() string { if ss.SizePercent > 0 { fmt.Fprintf(&buffer, "size=%s%%,", humanize.Ftoa(ss.SizePercent)) } + if ss.PreambleFormat { + fmt.Fprintf(&buffer, "format=preamble,") + } if len(ss.Attributes.Attrs) > 0 { fmt.Fprint(&buffer, "attrs=") for i, attr := range ss.Attributes.Attrs { @@ -209,6 +213,12 @@ func NewStoreSpec(value string) (StoreSpec, error) { } else { return StoreSpec{}, fmt.Errorf("%s is not a valid store type", value) } + case "format": + if value == "preamble" { + ss.PreambleFormat = true + } else if value != "classic" { + return StoreSpec{}, fmt.Errorf("%s is not a valid store format", value) + } default: return StoreSpec{}, fmt.Errorf("%s is not a valid store field", field) } @@ -221,6 +231,9 @@ func NewStoreSpec(value string) (StoreSpec, error) { if ss.SizePercent == 0 && ss.SizeInBytes == 0 { return StoreSpec{}, fmt.Errorf("size must be specified for an in memory store") } + if ss.PreambleFormat { + return StoreSpec{}, fmt.Errorf("format must be set to classic for an in memory store") + } } else if ss.Path == "" { return StoreSpec{}, fmt.Errorf("no path specified") } diff --git a/pkg/base/store_spec_test.go b/pkg/base/store_spec_test.go index 24111072ed44..f0f56ac79b97 100644 --- a/pkg/base/store_spec_test.go +++ b/pkg/base/store_spec_test.go @@ -34,49 +34,49 @@ func TestNewStoreSpec(t *testing.T) { expected StoreSpec }{ // path - {"path=/mnt/hda1", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}}}, - {",path=/mnt/hda1", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}}}, - {"path=/mnt/hda1,", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}}}, - {",,,path=/mnt/hda1,,,", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}}}, - {"/mnt/hda1", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}}}, + {"path=/mnt/hda1", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}, false}}, + {",path=/mnt/hda1", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}, false}}, + {",,,path=/mnt/hda1,,,", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}, false}}, + {"/mnt/hda1", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}, false}}, {"path=", "no value specified for path", StoreSpec{}}, {"path=/mnt/hda1,path=/mnt/hda2", "path field was used twice in store definition", StoreSpec{}}, {"/mnt/hda1,path=/mnt/hda2", "path field was used twice in store definition", StoreSpec{}}, // attributes - {"path=/mnt/hda1,attrs=ssd", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{Attrs: []string{"ssd"}}}}, - {"path=/mnt/hda1,attrs=ssd:hdd", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}}}, - {"path=/mnt/hda1,attrs=hdd:ssd", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}}}, - {"attrs=ssd:hdd,path=/mnt/hda1", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}}}, - {"attrs=hdd:ssd,path=/mnt/hda1,", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}}}, + {"path=/mnt/hda1,attrs=ssd", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{Attrs: []string{"ssd"}}, false}}, + {"path=/mnt/hda1,attrs=ssd:hdd", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}, false}}, + {"path=/mnt/hda1,attrs=hdd:ssd", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}, false}}, + {"attrs=ssd:hdd,path=/mnt/hda1", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}, false}}, + {"attrs=hdd:ssd,path=/mnt/hda1,", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}, false}}, {"attrs=hdd:ssd", "no path specified", StoreSpec{}}, {"path=/mnt/hda1,attrs=", "no value specified for attrs", StoreSpec{}}, {"path=/mnt/hda1,attrs=hdd:hdd", "duplicate attribute given for store: hdd", StoreSpec{}}, {"path=/mnt/hda1,attrs=hdd,attrs=ssd", "attrs field was used twice in store definition", StoreSpec{}}, // size - {"path=/mnt/hda1,size=671088640", "", StoreSpec{"/mnt/hda1", 671088640, 0, false, roachpb.Attributes{}}}, - {"path=/mnt/hda1,size=20GB", "", StoreSpec{"/mnt/hda1", 20000000000, 0, false, roachpb.Attributes{}}}, - {"size=20GiB,path=/mnt/hda1", "", StoreSpec{"/mnt/hda1", 21474836480, 0, false, roachpb.Attributes{}}}, - {"size=0.1TiB,path=/mnt/hda1", "", StoreSpec{"/mnt/hda1", 109951162777, 0, false, roachpb.Attributes{}}}, - {"path=/mnt/hda1,size=.1TiB", "", StoreSpec{"/mnt/hda1", 109951162777, 0, false, roachpb.Attributes{}}}, - {"path=/mnt/hda1,size=123TB", "", StoreSpec{"/mnt/hda1", 123000000000000, 0, false, roachpb.Attributes{}}}, - {"path=/mnt/hda1,size=123TiB", "", StoreSpec{"/mnt/hda1", 135239930216448, 0, false, roachpb.Attributes{}}}, + {"path=/mnt/hda1,size=671088640", "", StoreSpec{"/mnt/hda1", 671088640, 0, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,size=20GB", "", StoreSpec{"/mnt/hda1", 20000000000, 0, false, roachpb.Attributes{}, false}}, + {"size=20GiB,path=/mnt/hda1", "", StoreSpec{"/mnt/hda1", 21474836480, 0, false, roachpb.Attributes{}, false}}, + {"size=0.1TiB,path=/mnt/hda1", "", StoreSpec{"/mnt/hda1", 109951162777, 0, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,size=.1TiB", "", StoreSpec{"/mnt/hda1", 109951162777, 0, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,size=123TB", "", StoreSpec{"/mnt/hda1", 123000000000000, 0, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,size=123TiB", "", StoreSpec{"/mnt/hda1", 135239930216448, 0, false, roachpb.Attributes{}, false}}, // % - {"path=/mnt/hda1,size=50.5%", "", StoreSpec{"/mnt/hda1", 0, 50.5, false, roachpb.Attributes{}}}, - {"path=/mnt/hda1,size=100%", "", StoreSpec{"/mnt/hda1", 0, 100, false, roachpb.Attributes{}}}, - {"path=/mnt/hda1,size=1%", "", StoreSpec{"/mnt/hda1", 0, 1, false, roachpb.Attributes{}}}, + {"path=/mnt/hda1,size=50.5%", "", StoreSpec{"/mnt/hda1", 0, 50.5, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,size=100%", "", StoreSpec{"/mnt/hda1", 0, 100, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,size=1%", "", StoreSpec{"/mnt/hda1", 0, 1, false, roachpb.Attributes{}, false}}, {"path=/mnt/hda1,size=0.999999%", "store size (0.999999%) must be between 1% and 100%", StoreSpec{}}, {"path=/mnt/hda1,size=100.0001%", "store size (100.0001%) must be between 1% and 100%", StoreSpec{}}, // 0.xxx - {"path=/mnt/hda1,size=0.99", "", StoreSpec{"/mnt/hda1", 0, 99, false, roachpb.Attributes{}}}, - {"path=/mnt/hda1,size=0.5000000", "", StoreSpec{"/mnt/hda1", 0, 50, false, roachpb.Attributes{}}}, - {"path=/mnt/hda1,size=0.01", "", StoreSpec{"/mnt/hda1", 0, 1, false, roachpb.Attributes{}}}, + {"path=/mnt/hda1,size=0.99", "", StoreSpec{"/mnt/hda1", 0, 99, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,size=0.5000000", "", StoreSpec{"/mnt/hda1", 0, 50, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,size=0.01", "", StoreSpec{"/mnt/hda1", 0, 1, false, roachpb.Attributes{}, false}}, {"path=/mnt/hda1,size=0.009999", "store size (0.009999) must be between 1% and 100%", StoreSpec{}}, // .xxx - {"path=/mnt/hda1,size=.999", "", StoreSpec{"/mnt/hda1", 0, 99.9, false, roachpb.Attributes{}}}, - {"path=/mnt/hda1,size=.5000000", "", StoreSpec{"/mnt/hda1", 0, 50, false, roachpb.Attributes{}}}, - {"path=/mnt/hda1,size=.01", "", StoreSpec{"/mnt/hda1", 0, 1, false, roachpb.Attributes{}}}, + {"path=/mnt/hda1,size=.999", "", StoreSpec{"/mnt/hda1", 0, 99.9, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,size=.5000000", "", StoreSpec{"/mnt/hda1", 0, 50, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,size=.01", "", StoreSpec{"/mnt/hda1", 0, 1, false, roachpb.Attributes{}, false}}, {"path=/mnt/hda1,size=.009999", "store size (.009999) must be between 1% and 100%", StoreSpec{}}, // errors {"path=/mnt/hda1,size=0", "store size (0) must be larger than 640 MiB", StoreSpec{}}, @@ -86,10 +86,10 @@ func TestNewStoreSpec(t *testing.T) { {"size=123TB", "no path specified", StoreSpec{}}, // type - {"type=mem,size=20GiB", "", StoreSpec{"", 21474836480, 0, true, roachpb.Attributes{}}}, - {"size=20GiB,type=mem", "", StoreSpec{"", 21474836480, 0, true, roachpb.Attributes{}}}, - {"size=20.5GiB,type=mem", "", StoreSpec{"", 22011707392, 0, true, roachpb.Attributes{}}}, - {"size=20GiB,type=mem,attrs=mem", "", StoreSpec{"", 21474836480, 0, true, roachpb.Attributes{Attrs: []string{"mem"}}}}, + {"type=mem,size=20GiB", "", StoreSpec{"", 21474836480, 0, true, roachpb.Attributes{}, false}}, + {"size=20GiB,type=mem", "", StoreSpec{"", 21474836480, 0, true, roachpb.Attributes{}, false}}, + {"size=20.5GiB,type=mem", "", StoreSpec{"", 22011707392, 0, true, roachpb.Attributes{}, false}}, + {"size=20GiB,type=mem,attrs=mem", "", StoreSpec{"", 21474836480, 0, true, roachpb.Attributes{Attrs: []string{"mem"}}, false}}, {"type=mem,size=20", "store size (20) must be larger than 640 MiB", StoreSpec{}}, {"type=mem,size=", "no value specified for size", StoreSpec{}}, {"type=mem,attrs=ssd", "size must be specified for an in memory store", StoreSpec{}}, @@ -97,9 +97,18 @@ func TestNewStoreSpec(t *testing.T) { {"path=/mnt/hda1,type=other", "other is not a valid store type", StoreSpec{}}, {"path=/mnt/hda1,type=mem,size=20GiB", "path specified for in memory store", StoreSpec{}}, + // format + {"format=", "no value specified for format", StoreSpec{}}, + {"format=blah", "blah is not a valid store format", StoreSpec{}}, + {"/mnt/hda1", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,format=classic", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}, false}}, + {"path=/mnt/hda1,format=preamble", "", StoreSpec{"/mnt/hda1", 0, 0, false, roachpb.Attributes{}, true}}, + {"type=mem,size=20GiB,format=classic", "", StoreSpec{"", 21474836480, 0, true, roachpb.Attributes{}, false}}, + {"type=mem,size=20GiB,format=preamble", "format must be set to classic for an in memory store", StoreSpec{}}, + // all together - {"path=/mnt/hda1,attrs=hdd:ssd,size=20GiB", "", StoreSpec{"/mnt/hda1", 21474836480, 0, false, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}}}, - {"type=mem,attrs=hdd:ssd,size=20GiB", "", StoreSpec{"", 21474836480, 0, true, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}}}, + {"path=/mnt/hda1,attrs=hdd:ssd,size=20GiB", "", StoreSpec{"/mnt/hda1", 21474836480, 0, false, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}, false}}, + {"type=mem,attrs=hdd:ssd,size=20GiB", "", StoreSpec{"", 21474836480, 0, true, roachpb.Attributes{Attrs: []string{"hdd", "ssd"}}, false}}, // other error cases {"", "no value specified", StoreSpec{}}, diff --git a/pkg/cli/cliflags/flags.go b/pkg/cli/cliflags/flags.go index a3fe0fbb7507..8fe5bbb22ba7 100644 --- a/pkg/cli/cliflags/flags.go +++ b/pkg/cli/cliflags/flags.go @@ -407,13 +407,13 @@ each storage device, for example: --store=/mnt/ssd01 --store=/mnt/ssd02 --store=/mnt/hda1 -For each store, the "attrs" and "size" fields can be used to specify device -attributes and a maximum store size (see below). When one or both of these +For each store, the "attrs", "size", and "format" fields can be used to specify device +attributes, a maximum store size, and a format. When one or more of these fields are set, the "path" field label must be used for the path to the storage device, for example:
 
-  --store=path=/mnt/ssd01,attrs=ssd,size=20GiB
+  --store=path=/mnt/ssd01,attrs=ssd,size=20GiB,format=preamble
 
 
In most cases, node-level attributes are preferable to store-level attributes. @@ -444,10 +444,26 @@ for example: --store=path=/mnt/ssd01,size=.2 -> 20% of available space -For an in-memory store, the "type" and "size" fields are required, and the -"path" field is forbidden. The "type" field must be set to "mem", and the -"size" field must be set to the true maximum bytes or percentage of available -memory that the store may consume, for example: +The "format" field specifies the underlying file format for the store. +Allowed values are "classic" (default if omitted), or "preamble". Preamble +enables the use of encryption on the affected store. +The format specified at store creation time must remain the same throughout the +lifetime of the store. +Examples: +
+
+  --store=/mnt/ssd01                           -> "classic" file format
+  --store=path=/mnt/ssd01                      -> "classic" file format
+  --store=path=/mnt/ssd01,format=classic       -> "classic" file format
+  --store=path=/mnt/ssd01,format=preamble      -> "preamble" file format
+
+
+ +For an in-memory store, the "type" and "size" fields are required, the format +field must be "classic", and the "path" field is forbidden. +The "type" field must be set to "mem", and the "size" field must be set to +the true maximum bytes or percentage of available memory that the store may consume, +for example:
 
   --store=type=mem,size=20GiB
diff --git a/pkg/cli/flags.go b/pkg/cli/flags.go
index e0a675d843ce..95cf9f351f07 100644
--- a/pkg/cli/flags.go
+++ b/pkg/cli/flags.go
@@ -197,8 +197,6 @@ func init() {
 	// special severity value DEFAULT instead.
 	pf.Lookup(logflags.LogToStderrName).NoOptDefVal = log.Severity_DEFAULT.String()
 
-	// Security flags.
-
 	{
 		f := startCmd.Flags()
 
diff --git a/pkg/server/config.go b/pkg/server/config.go
index 8e149562e25d..227bad7cd885 100644
--- a/pkg/server/config.go
+++ b/pkg/server/config.go
@@ -499,6 +499,7 @@ func (cfg *Config) CreateEngines(ctx context.Context) (Engines, error) {
 				MaxOpenFiles:            openFileLimitPerStore,
 				WarnLargeBatchThreshold: 500 * time.Millisecond,
 				Settings:                cfg.Settings,
+				PreambleFormat:          spec.PreambleFormat,
 			}
 
 			eng, err := engine.NewRocksDB(rocksDBConfig, cache)
diff --git a/pkg/storage/api.pb.go b/pkg/storage/api.pb.go
index 7671a7a67e82..58c8559d48e1 100644
--- a/pkg/storage/api.pb.go
+++ b/pkg/storage/api.pb.go
@@ -9,6 +9,7 @@
 		storage/lease_status.proto
 		storage/liveness.proto
 		storage/log.proto
+		storage/preamble.proto
 		storage/raft.proto
 
 	It has these top-level messages:
@@ -18,6 +19,7 @@
 		LeaseStatus
 		Liveness
 		RangeLogEvent
+		Preamble
 		RaftHeartbeat
 		RaftMessageRequest
 		RaftMessageRequestBatch
diff --git a/pkg/storage/engine/rocksdb.go b/pkg/storage/engine/rocksdb.go
index 003146b0bb07..7ed900cc9b31 100644
--- a/pkg/storage/engine/rocksdb.go
+++ b/pkg/storage/engine/rocksdb.go
@@ -21,6 +21,7 @@ import (
 	"math"
 	"os"
 	"path/filepath"
+	"reflect"
 	"runtime"
 	"runtime/debug"
 	"sort"
@@ -307,6 +308,9 @@ type RocksDBConfig struct {
 	WarnLargeBatchThreshold time.Duration
 	// Settings instance for cluster-wide knobs.
 	Settings *cluster.Settings
+	// PreambleFormat is true if we want to use the "preamble" rocksdb file format.
+	// This cannot be changed once the store has been created.
+	PreambleFormat bool
 }
 
 // RocksDB is a wrapper around a RocksDB database instance.
@@ -401,20 +405,49 @@ func (r *RocksDB) String() string {
 }
 
 func (r *RocksDB) open() error {
-	var ver storageVersion
+	var originalVer, newVer Version
 	if len(r.cfg.Dir) != 0 {
 		log.Infof(context.TODO(), "opening rocksdb instance at %q", r.cfg.Dir)
 
 		// Check the version number.
 		var err error
-		if ver, err = getVersion(r.cfg.Dir); err != nil {
+		if originalVer, err = getVersion(r.cfg.Dir); err != nil {
 			return err
 		}
-		if ver < versionMinimum || ver > versionCurrent {
+
+		// Check for compatible version.
+		if originalVer.Version < versionMinimum || originalVer.Version > versionCurrent {
 			// Instead of an error, we should call a migration if possible when
 			// one is needed immediately following the DBOpen call.
 			return fmt.Errorf("incompatible rocksdb data version, current:%d, on disk:%d, minimum:%d",
-				versionCurrent, ver, versionMinimum)
+				versionCurrent, originalVer.Version, versionMinimum)
+		}
+
+		if originalVer.Version != versionNoFile {
+			// We have a version file, check data format.
+			existingPreambleFormat := (originalVer.Format == formatPreamble)
+			if existingPreambleFormat != r.cfg.PreambleFormat {
+				return fmt.Errorf("incompatible rocksdb data format, requested preamble:%t, on disk preamble:%t",
+					r.cfg.PreambleFormat, existingPreambleFormat)
+			}
+		}
+
+		// Setup the new version.
+		if r.cfg.PreambleFormat {
+			// Preamble format sets the current version and the format string.
+			newVer.Version = versionCurrent
+			newVer.Format = formatPreamble
+		} else {
+			// In classic format, we set the storage version to versionBeta20160331 to allow
+			// downgrades from classic formats created with new binaries to classic formats
+			// read by old binaries.
+			// TODO(marc): once enough release versions have been released,
+			// set the version to versionCurrent and drop this whole 'else' branch.
+			if versionCurrent > versionPreambleFormat {
+				log.Fatalf(context.TODO(), "current version is beyond PreambleFormat, this transition isn't handled")
+			}
+			newVer.Version = versionPreambleFormat - 1
+			newVer.Format = formatClassic
 		}
 	} else {
 		if log.V(2) {
@@ -422,7 +455,7 @@ func (r *RocksDB) open() error {
 		}
 
 		// In memory dbs are always current.
-		ver = versionCurrent
+		originalVer.Version = versionCurrent
 	}
 
 	blockSize := envutil.EnvOrDefaultBytes("COCKROACH_ROCKSDB_BLOCK_SIZE", defaultBlockSize)
@@ -440,14 +473,15 @@ func (r *RocksDB) open() error {
 			logging_enabled: C.bool(log.V(3)),
 			num_cpu:         C.int(runtime.NumCPU()),
 			max_open_files:  C.int(maxOpenFiles),
+			use_preamble:    C.bool(r.cfg.PreambleFormat),
 		})
 	if err := statusToError(status); err != nil {
 		return errors.Wrap(err, "could not open rocksdb instance")
 	}
 
-	// Update or add the version file if needed.
-	if ver < versionCurrent {
-		if err := writeVersionFile(r.cfg.Dir); err != nil {
+	// Write the version file if it changed and we're a disk-based store.
+	if len(r.cfg.Dir) != 0 && !reflect.DeepEqual(originalVer, newVer) {
+		if err := writeVersionFile(r.cfg.Dir, newVer); err != nil {
 			return err
 		}
 	}
diff --git a/pkg/storage/engine/rocksdb_test.go b/pkg/storage/engine/rocksdb_test.go
index 438b008b4aa0..cc9d1459c423 100644
--- a/pkg/storage/engine/rocksdb_test.go
+++ b/pkg/storage/engine/rocksdb_test.go
@@ -20,6 +20,7 @@ import (
 	"io/ioutil"
 	"math/rand"
 	"os"
+	"reflect"
 	"sort"
 	"strconv"
 	"testing"
@@ -217,29 +218,54 @@ func benchmarkIterOnReadWriter(
 func TestRocksDBOpenWithVersions(t *testing.T) {
 	defer leaktest.AfterTest(t)()
 
+	// Versions we can end up with after NewRocksDB() rewrites them.
+	classicVersion := Version{Version: versionBeta20160331, Format: formatClassic}
+	preambleVersion := Version{Version: versionCurrent, Format: formatPreamble}
+
 	testCases := []struct {
-		hasFile     bool
-		ver         Version
-		expectedErr string
+		hasFile      bool
+		ver          Version
+		wantPreamble bool // Whether preamble format is requested.
+		expectedErr  string
+		expectedVer  Version // The expected version file after opening the DB. Not checked on errors.
 	}{
-		{false, Version{}, ""},
-		{true, Version{versionCurrent}, ""},
-		{true, Version{versionMinimum}, ""},
-		{true, Version{-1}, "incompatible rocksdb data version, current:1, on disk:-1, minimum:0"},
-		{true, Version{2}, "incompatible rocksdb data version, current:1, on disk:2, minimum:0"},
+		{false, Version{}, false, "", classicVersion},
+		{true, Version{Version: versionCurrent}, false, "", classicVersion},
+		{true, Version{Version: versionMinimum}, false, "", classicVersion},
+		{true, Version{Version: -1}, false, "incompatible rocksdb data version, current:2, on disk:-1, minimum:0", Version{}},
+		{true, Version{Version: 3}, false, "incompatible rocksdb data version, current:2, on disk:3, minimum:0", Version{}},
+		// Test preamble format.
+		{false, Version{}, true, "", preambleVersion},
+		{true, Version{Version: versionBeta20160331}, true, "incompatible rocksdb data format", Version{}},
+		{true, Version{Version: versionBeta20160331, Format: formatClassic}, true, "incompatible rocksdb data format", Version{}},
+		{true, Version{Version: versionBeta20160331, Format: formatClassic}, false, "", classicVersion},
+		{true, Version{Version: versionBeta20160331, Format: formatPreamble}, true, "", preambleVersion},
+		{true, Version{Version: versionBeta20160331, Format: formatPreamble}, false, "incompatible rocksdb data format", Version{}},
+		{true, Version{Version: versionCurrent, Format: formatClassic}, true, "incompatible rocksdb data format", Version{}},
+		{true, Version{Version: versionCurrent, Format: formatPreamble}, false, "incompatible rocksdb data format", Version{}},
+		{true, Version{Version: versionCurrent, Format: formatClassic}, false, "", classicVersion},
+		{true, Version{Version: versionCurrent, Format: formatPreamble}, true, "", preambleVersion},
 	}
 
 	for i, testCase := range testCases {
-		err := openRocksDBWithVersion(t, testCase.hasFile, testCase.ver)
+		newVersion, err := openRocksDBWithVersion(t, testCase.hasFile, testCase.ver, testCase.wantPreamble)
 		if !testutils.IsError(err, testCase.expectedErr) {
 			t.Errorf("%d: expected error '%s', actual '%v'", i, testCase.expectedErr, err)
 		}
+		if err != nil {
+			continue
+		}
+		if !reflect.DeepEqual(newVersion, testCase.expectedVer) {
+			t.Errorf("%d: expected new version file %+v, actual %+v", i, testCase.expectedVer, newVersion)
+		}
 	}
 }
 
 // openRocksDBWithVersion attempts to open a rocks db instance, optionally with
 // the supplied Version struct.
-func openRocksDBWithVersion(t *testing.T, hasVersionFile bool, ver Version) error {
+func openRocksDBWithVersion(
+	t *testing.T, hasVersionFile bool, ver Version, wantPreambleFormat bool,
+) (Version, error) {
 	dir, err := ioutil.TempDir("", "testing")
 	if err != nil {
 		t.Fatal(err)
@@ -262,15 +288,29 @@ func openRocksDBWithVersion(t *testing.T, hasVersionFile bool, ver Version) erro
 
 	rocksdb, err := NewRocksDB(
 		RocksDBConfig{
-			Settings: cluster.MakeTestingClusterSettings(),
-			Dir:      dir,
+			Settings:       cluster.MakeTestingClusterSettings(),
+			Dir:            dir,
+			PreambleFormat: wantPreambleFormat,
 		},
 		RocksDBCache{},
 	)
 	if err == nil {
 		rocksdb.Close()
+	} else {
+		// Return early on errors.
+		return Version{}, err
+	}
+
+	// Reread version file to check rewritten fields.
+	b, err := ioutil.ReadFile(getVersionFilename(dir))
+	if err != nil {
+		t.Fatal(err)
+	}
+	var retVer Version
+	if err := json.Unmarshal(b, &retVer); err != nil {
+		t.Fatal(err)
 	}
-	return err
+	return retVer, nil
 }
 
 func TestSSTableInfosString(t *testing.T) {
diff --git a/pkg/storage/engine/version.go b/pkg/storage/engine/version.go
index 0d6cd2dd780c..4fadf507918f 100644
--- a/pkg/storage/engine/version.go
+++ b/pkg/storage/engine/version.go
@@ -23,23 +23,34 @@ import (
 )
 
 type storageVersion int
+type formatVersion int
 
 const (
 	versionNoFile storageVersion = iota
 	versionBeta20160331
+	versionPreambleFormat
+)
+
+const (
+	// It's important to keep formatClassic at zero, meaning an omitted
+	// json field means "classic".
+	formatClassic formatVersion = iota
+	formatPreamble
 )
 
 const (
 	versionFilename     = "COCKROACHDB_VERSION"
 	versionFilenameTemp = "COCKROACHDB_VERSION_TEMP"
 	versionMinimum      = versionNoFile
-	versionCurrent      = versionBeta20160331
+	versionCurrent      = versionPreambleFormat
 )
 
 // Version stores all the version information for all stores and is used as
 // the format for the version file.
 type Version struct {
 	Version storageVersion
+	// Format is unknown prior to versionPreambleFormat and defaults to formatClassic.
+	Format formatVersion
 }
 
 // getVersionFilename returns the filename for the version file stored in the
@@ -50,28 +61,32 @@ func getVersionFilename(dir string) string {
 
 // getVersion returns the current on disk cockroach version from the version
 // file in the passed in directory. If there is no version file yet, it
-// returns 0.
-func getVersion(dir string) (storageVersion, error) {
+// returns a version with a zero (NoFile) storage version.
+func getVersion(dir string) (Version, error) {
 	filename := getVersionFilename(dir)
 	b, err := ioutil.ReadFile(filename)
 	if err != nil {
 		if os.IsNotExist(err) {
-			return versionNoFile, nil
+			return Version{Version: versionNoFile}, nil
 		}
-		return 0, err
+		return Version{Version: versionNoFile}, err
 	}
 	var ver Version
 	if err := json.Unmarshal(b, &ver); err != nil {
-		return 0, fmt.Errorf("version file %s is not formatted correctly; %s", filename, err)
+		return Version{Version: versionNoFile}, fmt.Errorf("version file %s is not formatted correctly; %s", filename, err)
 	}
-	return ver.Version, nil
+	return ver, nil
 }
 
 // writeVersionFile overwrites the version file to contain the latest version.
-func writeVersionFile(dir string) error {
+func writeVersionFile(dir string, ver Version) error {
+	if ver.Version == versionNoFile {
+		return fmt.Errorf("writing version %d is not allowed", ver.Version)
+	}
+
 	tempFilename := filepath.Join(dir, versionFilenameTemp)
 	filename := getVersionFilename(dir)
-	b, err := json.Marshal(Version{versionCurrent})
+	b, err := json.Marshal(ver)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/storage/engine/version_test.go b/pkg/storage/engine/version_test.go
index a8a437e69604..10f09dd9f7ef 100644
--- a/pkg/storage/engine/version_test.go
+++ b/pkg/storage/engine/version_test.go
@@ -15,6 +15,7 @@
 package engine
 
 import (
+	"encoding/json"
 	"io/ioutil"
 	"os"
 	"testing"
@@ -43,20 +44,27 @@ func TestVersions(t *testing.T) {
 	if err != nil {
 		t.Fatal(err)
 	}
-	if ver != versionNoFile {
-		t.Errorf("no version file version should be %d, got %d", versionNoFile, ver)
+	if ver.Version != versionNoFile {
+		t.Errorf("no version file version should be %d, got %+v", versionNoFile, ver)
 	}
 
-	// Write the current versions to the file.
-	if err := writeVersionFile(dir); err != nil {
+	// Write the same version to the file: not allowed.
+	if err := writeVersionFile(dir, ver); !testutils.IsError(err, "writing version 0 is not allowed") {
+		t.Errorf("expected error '%s', got '%v'", "writing version 0 is not allowed", err)
+	}
+
+	// Try again with current version.
+	ver.Version = versionCurrent
+	if err := writeVersionFile(dir, ver); err != nil {
 		t.Fatal(err)
 	}
+
 	ver, err = getVersion(dir)
 	if err != nil {
 		t.Fatal(err)
 	}
-	if ver != versionCurrent {
-		t.Errorf("current versions do not match, expected %d got %d", versionCurrent, ver)
+	if ver.Version != versionCurrent {
+		t.Errorf("current versions do not match, expected %d got %+v", versionCurrent, ver)
 	}
 
 	// Write gibberish to the file.
@@ -71,3 +79,66 @@ func TestVersions(t *testing.T) {
 		t.Errorf("expected error contains '%s', got '%v'", "is not formatted correctly", err)
 	}
 }
+
+// TestOldVersionFormat verifies that new version format (not numbers, but actual json fields)
+// parses properly when using the older version data structures.
+func TestOldVersionFormat(t *testing.T) {
+	defer leaktest.AfterTest(t)()
+
+	dir, err := ioutil.TempDir("", "testing")
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer func() {
+		if err := os.RemoveAll(dir); err != nil {
+			t.Fatal(err)
+		}
+	}()
+
+	newVersion := Version{Version: versionPreambleFormat, Format: formatPreamble}
+	if err := writeVersionFile(dir, newVersion); err != nil {
+		t.Fatal(err)
+	}
+
+	// We can't use the getVersion function as we need to use a custom data structure to parse into.
+	b, err := ioutil.ReadFile(getVersionFilename(dir))
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	// This is the version data structure in use before versionPreambleFormat was introduced.
+	var oldFormatVersion struct {
+		Version storageVersion
+	}
+
+	if err := json.Unmarshal(b, &oldFormatVersion); err != nil {
+		t.Errorf("could not parse new version file using old version format: %v", err)
+	}
+
+	if oldFormatVersion.Version != newVersion.Version {
+		t.Errorf("mismatched version number: got %d, expected %d", oldFormatVersion.Version, newVersion.Version)
+	}
+
+	// Now write a version using the old format.
+	oldFormatVersion.Version = versionBeta20160331
+	b, err = json.Marshal(oldFormatVersion)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err := ioutil.WriteFile(getVersionFilename(dir), b, 0644); err != nil {
+		t.Fatal(err)
+	}
+
+	// Read it back using the current data structure.
+	newVersion, err = getVersion(dir)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if newVersion.Version != oldFormatVersion.Version {
+		t.Errorf("mismatched version number: got %d, expected %d", newVersion.Version, oldFormatVersion.Version)
+	}
+	if newVersion.Format != formatClassic {
+		t.Errorf("mismatched format number: got %d, expected %d", newVersion.Version, formatClassic)
+	}
+}
diff --git a/pkg/storage/preamble.pb.go b/pkg/storage/preamble.pb.go
new file mode 100644
index 000000000000..a68cae8eb9ac
--- /dev/null
+++ b/pkg/storage/preamble.pb.go
@@ -0,0 +1,297 @@
+// Code generated by protoc-gen-gogo. DO NOT EDIT.
+// source: storage/preamble.proto
+
+package storage
+
+import proto "github.com/gogo/protobuf/proto"
+import fmt "fmt"
+import math "math"
+
+import io "io"
+
+// Reference imports to suppress errors if they are not otherwise used.
+var _ = proto.Marshal
+var _ = fmt.Errorf
+var _ = math.Inf
+
+// EncryptionType describes the type of encryption used.
+type EncryptionType int32
+
+const (
+	// No encryption applied.
+	EncryptionType_Plaintext EncryptionType = 0
+)
+
+var EncryptionType_name = map[int32]string{
+	0: "Plaintext",
+}
+var EncryptionType_value = map[string]int32{
+	"Plaintext": 0,
+}
+
+func (x EncryptionType) String() string {
+	return proto.EnumName(EncryptionType_name, int32(x))
+}
+func (EncryptionType) EnumDescriptor() ([]byte, []int) { return fileDescriptorPreamble, []int{0} }
+
+type Preamble struct {
+	// The type of encryption applied.
+	EncryptionType EncryptionType `protobuf:"varint,1,opt,name=encryption_type,json=encryptionType,proto3,enum=cockroach.storage.EncryptionType" json:"encryption_type,omitempty"`
+}
+
+func (m *Preamble) Reset()                    { *m = Preamble{} }
+func (m *Preamble) String() string            { return proto.CompactTextString(m) }
+func (*Preamble) ProtoMessage()               {}
+func (*Preamble) Descriptor() ([]byte, []int) { return fileDescriptorPreamble, []int{0} }
+
+func init() {
+	proto.RegisterType((*Preamble)(nil), "cockroach.storage.Preamble")
+	proto.RegisterEnum("cockroach.storage.EncryptionType", EncryptionType_name, EncryptionType_value)
+}
+func (m *Preamble) Marshal() (dAtA []byte, err error) {
+	size := m.Size()
+	dAtA = make([]byte, size)
+	n, err := m.MarshalTo(dAtA)
+	if err != nil {
+		return nil, err
+	}
+	return dAtA[:n], nil
+}
+
+func (m *Preamble) MarshalTo(dAtA []byte) (int, error) {
+	var i int
+	_ = i
+	var l int
+	_ = l
+	if m.EncryptionType != 0 {
+		dAtA[i] = 0x8
+		i++
+		i = encodeVarintPreamble(dAtA, i, uint64(m.EncryptionType))
+	}
+	return i, nil
+}
+
+func encodeVarintPreamble(dAtA []byte, offset int, v uint64) int {
+	for v >= 1<<7 {
+		dAtA[offset] = uint8(v&0x7f | 0x80)
+		v >>= 7
+		offset++
+	}
+	dAtA[offset] = uint8(v)
+	return offset + 1
+}
+func (m *Preamble) Size() (n int) {
+	var l int
+	_ = l
+	if m.EncryptionType != 0 {
+		n += 1 + sovPreamble(uint64(m.EncryptionType))
+	}
+	return n
+}
+
+func sovPreamble(x uint64) (n int) {
+	for {
+		n++
+		x >>= 7
+		if x == 0 {
+			break
+		}
+	}
+	return n
+}
+func sozPreamble(x uint64) (n int) {
+	return sovPreamble(uint64((x << 1) ^ uint64((int64(x) >> 63))))
+}
+func (m *Preamble) Unmarshal(dAtA []byte) error {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		preIndex := iNdEx
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return ErrIntOverflowPreamble
+			}
+			if iNdEx >= l {
+				return io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		fieldNum := int32(wire >> 3)
+		wireType := int(wire & 0x7)
+		if wireType == 4 {
+			return fmt.Errorf("proto: Preamble: wiretype end group for non-group")
+		}
+		if fieldNum <= 0 {
+			return fmt.Errorf("proto: Preamble: illegal tag %d (wire type %d)", fieldNum, wire)
+		}
+		switch fieldNum {
+		case 1:
+			if wireType != 0 {
+				return fmt.Errorf("proto: wrong wireType = %d for field EncryptionType", wireType)
+			}
+			m.EncryptionType = 0
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return ErrIntOverflowPreamble
+				}
+				if iNdEx >= l {
+					return io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				m.EncryptionType |= (EncryptionType(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+		default:
+			iNdEx = preIndex
+			skippy, err := skipPreamble(dAtA[iNdEx:])
+			if err != nil {
+				return err
+			}
+			if skippy < 0 {
+				return ErrInvalidLengthPreamble
+			}
+			if (iNdEx + skippy) > l {
+				return io.ErrUnexpectedEOF
+			}
+			iNdEx += skippy
+		}
+	}
+
+	if iNdEx > l {
+		return io.ErrUnexpectedEOF
+	}
+	return nil
+}
+func skipPreamble(dAtA []byte) (n int, err error) {
+	l := len(dAtA)
+	iNdEx := 0
+	for iNdEx < l {
+		var wire uint64
+		for shift := uint(0); ; shift += 7 {
+			if shift >= 64 {
+				return 0, ErrIntOverflowPreamble
+			}
+			if iNdEx >= l {
+				return 0, io.ErrUnexpectedEOF
+			}
+			b := dAtA[iNdEx]
+			iNdEx++
+			wire |= (uint64(b) & 0x7F) << shift
+			if b < 0x80 {
+				break
+			}
+		}
+		wireType := int(wire & 0x7)
+		switch wireType {
+		case 0:
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowPreamble
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				iNdEx++
+				if dAtA[iNdEx-1] < 0x80 {
+					break
+				}
+			}
+			return iNdEx, nil
+		case 1:
+			iNdEx += 8
+			return iNdEx, nil
+		case 2:
+			var length int
+			for shift := uint(0); ; shift += 7 {
+				if shift >= 64 {
+					return 0, ErrIntOverflowPreamble
+				}
+				if iNdEx >= l {
+					return 0, io.ErrUnexpectedEOF
+				}
+				b := dAtA[iNdEx]
+				iNdEx++
+				length |= (int(b) & 0x7F) << shift
+				if b < 0x80 {
+					break
+				}
+			}
+			iNdEx += length
+			if length < 0 {
+				return 0, ErrInvalidLengthPreamble
+			}
+			return iNdEx, nil
+		case 3:
+			for {
+				var innerWire uint64
+				var start int = iNdEx
+				for shift := uint(0); ; shift += 7 {
+					if shift >= 64 {
+						return 0, ErrIntOverflowPreamble
+					}
+					if iNdEx >= l {
+						return 0, io.ErrUnexpectedEOF
+					}
+					b := dAtA[iNdEx]
+					iNdEx++
+					innerWire |= (uint64(b) & 0x7F) << shift
+					if b < 0x80 {
+						break
+					}
+				}
+				innerWireType := int(innerWire & 0x7)
+				if innerWireType == 4 {
+					break
+				}
+				next, err := skipPreamble(dAtA[start:])
+				if err != nil {
+					return 0, err
+				}
+				iNdEx = start + next
+			}
+			return iNdEx, nil
+		case 4:
+			return iNdEx, nil
+		case 5:
+			iNdEx += 4
+			return iNdEx, nil
+		default:
+			return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
+		}
+	}
+	panic("unreachable")
+}
+
+var (
+	ErrInvalidLengthPreamble = fmt.Errorf("proto: negative length found during unmarshaling")
+	ErrIntOverflowPreamble   = fmt.Errorf("proto: integer overflow")
+)
+
+func init() { proto.RegisterFile("storage/preamble.proto", fileDescriptorPreamble) }
+
+var fileDescriptorPreamble = []byte{
+	// 210 bytes of a gzipped FileDescriptorProto
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x2b, 0x2e, 0xc9, 0x2f,
+	0x4a, 0x4c, 0x4f, 0xd5, 0x2f, 0x28, 0x4a, 0x4d, 0xcc, 0x4d, 0xca, 0x49, 0xd5, 0x2b, 0x28, 0xca,
+	0x2f, 0xc9, 0x17, 0x12, 0x4c, 0xce, 0x4f, 0xce, 0x2e, 0xca, 0x4f, 0x4c, 0xce, 0xd0, 0x83, 0xaa,
+	0x90, 0x12, 0x49, 0xcf, 0x4f, 0xcf, 0x07, 0xcb, 0xea, 0x83, 0x58, 0x10, 0x85, 0x52, 0xf2, 0xe9,
+	0xf9, 0xf9, 0xe9, 0x39, 0x20, 0xfd, 0xf9, 0x25, 0xf9, 0x49, 0xa5, 0x69, 0xfa, 0x25, 0x99, 0xb9,
+	0xa9, 0xc5, 0x25, 0x89, 0xb9, 0x05, 0x10, 0x05, 0x4a, 0x61, 0x5c, 0x1c, 0x01, 0x50, 0xb3, 0x85,
+	0xbc, 0xb8, 0xf8, 0x53, 0xf3, 0x92, 0x8b, 0x2a, 0x0b, 0x4a, 0x32, 0xf3, 0xf3, 0xe2, 0x4b, 0x2a,
+	0x0b, 0x52, 0x25, 0x18, 0x15, 0x18, 0x35, 0xf8, 0x8c, 0x14, 0xf5, 0x30, 0xec, 0xd3, 0x73, 0x85,
+	0xab, 0x0c, 0xa9, 0x2c, 0x48, 0x0d, 0xe2, 0x4b, 0x45, 0xe1, 0x6b, 0xc9, 0x73, 0xf1, 0xa1, 0xaa,
+	0x10, 0xe2, 0xe5, 0xe2, 0x0c, 0xc8, 0x49, 0xcc, 0xcc, 0x2b, 0x49, 0xad, 0x28, 0x11, 0x60, 0x70,
+	0x52, 0x3c, 0xf1, 0x50, 0x8e, 0xe1, 0xc4, 0x23, 0x39, 0xc6, 0x0b, 0x8f, 0xe4, 0x18, 0x6f, 0x3c,
+	0x92, 0x63, 0x7c, 0xf0, 0x48, 0x8e, 0x71, 0xc2, 0x63, 0x39, 0x86, 0x28, 0x76, 0xa8, 0x15, 0x49,
+	0x6c, 0x60, 0x27, 0x1a, 0x03, 0x02, 0x00, 0x00, 0xff, 0xff, 0x4b, 0xf5, 0xc6, 0xfa, 0x06, 0x01,
+	0x00, 0x00,
+}
diff --git a/pkg/storage/preamble.proto b/pkg/storage/preamble.proto
new file mode 100644
index 000000000000..bb97e75c75f0
--- /dev/null
+++ b/pkg/storage/preamble.proto
@@ -0,0 +1,31 @@
+// Copyright 2017 The Cockroach Authors.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+// implied. See the License for the specific language governing
+// permissions and limitations under the License.
+
+syntax = "proto3";
+package cockroach.storage;
+option go_package = "storage";
+
+import "gogoproto/gogo.proto";
+import "google/protobuf/timestamp.proto";
+
+// EncryptionType describes the type of encryption used.
+enum EncryptionType {
+  // No encryption applied.
+  Plaintext = 0;
+}
+
+message Preamble {
+  // The type of encryption applied.
+  EncryptionType encryption_type = 1;
+}