blob: dbf94cb92ddf7aab7f98b6e5df66a4a946ef94c8 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 "http/action/check_encryption_action.h"
#include <gen_cpp/olap_file.pb.h>
#include <glog/logging.h>
#include <google/protobuf/util/json_util.h>
#include <json2pb/pb_to_json.h>
#include <cstdint>
#include <exception>
#include <memory>
#include <shared_mutex>
#include <string>
#include <string_view>
#include "cloud/cloud_tablet.h"
#include "cloud/config.h"
#include "common/status.h"
#include "http/http_channel.h"
#include "http/http_headers.h"
#include "http/http_status.h"
#include "io/fs/file_reader.h"
#include "io/fs/file_system.h"
#include "io/fs/path.h"
#include "olap/rowset/rowset_fwd.h"
#include "olap/tablet_fwd.h"
#include "runtime/exec_env.h"
namespace doris {
const std::string TABLET_ID = "tablet_id";
const std::string GET_FOOTER = "get_footer";
CheckEncryptionAction::CheckEncryptionAction(ExecEnv* exec_env, TPrivilegeHier::type hier,
TPrivilegeType::type type)
: HttpHandlerWithAuth(exec_env, hier, type) {}
Result<bool> is_tablet_encrypted(const BaseTabletSPtr& tablet) {
auto tablet_meta = tablet->tablet_meta();
if (tablet_meta->encryption_algorithm() == EncryptionAlgorithmPB::PLAINTEXT) {
return false;
}
Status st;
bool is_encrypted = true;
tablet->traverse_rowsets([&st, &tablet, &is_encrypted](const RowsetSharedPtr& rs) {
if (!st) {
return;
}
auto rs_meta = rs->rowset_meta();
if (config::is_cloud_mode() && rs_meta->start_version() == 0 &&
rs_meta->end_version() == 1) {
return;
}
auto fs = rs_meta->physical_fs();
if (fs == nullptr) {
st = Status::InternalError("failed to get fs for rowset: tablet={}, rs={}",
tablet->tablet_id(), rs->rowset_id().to_string());
return;
}
if (rs->num_segments() == 0) {
return;
}
auto maybe_seg_path = rs->segment_path(0);
if (!maybe_seg_path) {
st = std::move(maybe_seg_path.error());
return;
}
std::vector<std::string_view> file_paths;
const auto& first_seg_path = maybe_seg_path.value();
file_paths.emplace_back(first_seg_path);
if (tablet->tablet_schema()->has_inverted_index() &&
tablet->tablet_schema()->get_inverted_index_storage_format() == V2) {
std::string inverted_index_file_path = InvertedIndexDescriptor::get_index_file_path_v2(
InvertedIndexDescriptor::get_index_file_path_prefix(first_seg_path));
file_paths.emplace_back(inverted_index_file_path);
}
for (const auto path : file_paths) {
io::FileReaderSPtr reader;
st = fs->open_file(path, &reader);
if (!st) {
return;
}
std::vector<uint8_t> magic_code_buf;
magic_code_buf.resize(sizeof(uint64_t));
Slice magic_code(magic_code_buf.data(), sizeof(uint64_t));
size_t bytes_read;
st = reader->read_at(reader->size() - sizeof(uint64_t), magic_code, &bytes_read);
if (!st) {
return;
}
std::vector<uint8_t> answer = {'A', 'B', 'C', 'D', 'E', 'A', 'B', 'C'};
is_encrypted &= Slice::mem_equal(answer.data(), magic_code.data, magic_code.size);
if (!is_encrypted) {
LOG(INFO) << "found not encrypted segment, path=" << first_seg_path;
}
}
});
if (st) {
return is_encrypted;
}
return st;
}
Result<std::string> get_last_encrypt_footer(const BaseTabletSPtr& tablet) {
std::shared_lock l(tablet->get_header_lock());
auto rs = tablet->get_rowset_with_max_version();
if (rs->num_segments() == 0) {
return "{}";
}
auto maybe_seg_path = rs->segment_path(0);
if (!maybe_seg_path) {
return ResultError(maybe_seg_path.error());
}
auto rs_meta = rs->rowset_meta();
if (config::is_cloud_mode() && rs_meta->start_version() == 0 && rs_meta->end_version() == 1) {
return "{}";
}
auto fs = rs_meta->physical_fs();
io::FileReaderSPtr reader;
RETURN_IF_ERROR_RESULT(fs->open_file(maybe_seg_path.value(), &reader));
std::vector<uint8_t> pb_len_buf;
pb_len_buf.reserve(sizeof(uint64_t));
Slice pb_len_slice(pb_len_buf.data(), sizeof(uint64_t));
size_t bytes_read;
RETURN_IF_ERROR_RESULT(
reader->read_at(reader->size() - 256 + sizeof(uint8_t), pb_len_slice, &bytes_read));
auto info_pb_size = decode_fixed64_le(pb_len_buf.data());
std::vector<uint8_t> info_pb_buf;
info_pb_buf.resize(info_pb_size);
Slice pb_slice(info_pb_buf.data(), info_pb_size);
RETURN_IF_ERROR_RESULT(reader->read_at(
reader->size() - 256 + sizeof(uint8_t) + sizeof(uint64_t), pb_slice, &bytes_read));
FileEncryptionInfoPB info_pb;
if (!info_pb.ParseFromArray(info_pb_buf.data(), static_cast<int>(info_pb_buf.size()))) {
return ResultError(Status::Corruption("parse encryption info failed"));
}
std::string json;
google::protobuf::util::JsonPrintOptions opts;
opts.add_whitespace = false;
opts.preserve_proto_field_names = true;
auto st = google::protobuf::util::MessageToJsonString(info_pb, &json, opts);
return json;
}
Status sync_meta(const CloudTabletSPtr& tablet) {
RETURN_IF_ERROR(tablet->sync_meta());
RETURN_IF_ERROR(tablet->sync_rowsets());
return Status::OK();
}
void CheckEncryptionAction::handle(HttpRequest* req) {
req->add_output_header(HttpHeaders::CONTENT_TYPE, HttpHeaders::JSON_TYPE.data());
auto tablet_id_str = req->param(TABLET_ID);
if (tablet_id_str.empty()) {
HttpChannel::send_reply(req, HttpStatus::BAD_REQUEST,
"tablet id should be set in request params");
return;
}
int64_t tablet_id = -1;
try {
tablet_id = std::stoll(tablet_id_str);
} catch (const std::exception& e) {
LOG(WARNING) << "convert tablet id to i64 failed:" << e.what();
auto msg = fmt::format("invalid argument: tablet_id={}", tablet_id_str);
HttpChannel::send_reply(req, HttpStatus::BAD_REQUEST, msg);
return;
}
bool is_get_footer = false;
if (auto get_footer_flag = req->param(GET_FOOTER); get_footer_flag == "true") {
is_get_footer = true;
} else if (get_footer_flag != "false") {
HttpChannel::send_reply(req, HttpStatus::BAD_REQUEST,
"param `get_footer` must be a boolean type");
return;
}
auto maybe_tablet = ExecEnv::get_tablet(tablet_id);
if (!maybe_tablet) {
HttpChannel::send_reply(req, HttpStatus::BAD_REQUEST, maybe_tablet.error().to_string());
return;
}
auto tablet = maybe_tablet.value();
if (config::is_cloud_mode()) {
auto cloud_tablet = std::dynamic_pointer_cast<CloudTablet>(tablet);
DCHECK_NE(cloud_tablet, nullptr);
auto st = sync_meta(cloud_tablet);
if (!st) {
HttpChannel::send_reply(req, HttpStatus::INTERNAL_SERVER_ERROR, st.to_json());
return;
}
}
auto maybe_is_encrypted = is_tablet_encrypted(tablet);
if (maybe_is_encrypted.has_value()) {
req->add_output_header(HttpHeaders::CONTENT_TYPE, HttpHeaders::JSON_TYPE.data());
std::string result = R"({"status":)";
result += maybe_is_encrypted.value() ? R"("all encrypted")" : R"("some are not encrypted")";
if (is_get_footer) {
auto maybe_footer = get_last_encrypt_footer(tablet);
if (!maybe_footer) {
HttpChannel::send_reply(req, HttpStatus::INTERNAL_SERVER_ERROR,
maybe_footer.error().to_json());
return;
}
result += R"(,"footer":)";
result += maybe_footer.value();
}
result += "}";
HttpChannel::send_reply(req, HttpStatus::OK, result);
return;
}
HttpChannel::send_reply(req, HttpStatus::INTERNAL_SERVER_ERROR,
maybe_is_encrypted.error().to_json());
}
} // namespace doris