blob: e2db4815bc56ed1dafe7ca4c3635d20a56d18a71 [file]
// 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 "common/auth/obj_credential.h"
#include <gen_cpp/cloud.pb.h>
#include <string_view>
#include "cpp/obj-client/auth/obj_credential.h"
namespace doris {
namespace {
GcpCredentialConfig to_gcp_credential_config(const cloud::GcpCredentialPB& credential) {
GcpCredentialConfig config;
switch (credential.credential_provider_type()) {
case cloud::GcpCredentialPB::DEFAULT:
config.provider_type = GcpCredentialProviderType::Default;
break;
case cloud::GcpCredentialPB::COMPUTE_ENGINE:
config.provider_type = GcpCredentialProviderType::ComputeEngine;
break;
}
config.impersonation_service_account = credential.impersonation_service_account();
return config;
}
} // namespace
void convert_obj_credential(const cloud::ObjectStoreInfoPB& source, CredentialConfig* credential) {
if (!source.has_credential() || !source.credential().has_gcp_credential()) {
*credential = std::monostate {};
return;
}
*credential = to_gcp_credential_config(source.credential().gcp_credential());
}
} // namespace doris
namespace doris::cloud {
namespace {
// Extension checklist for a new passwordless provider:
// 1. add a provider-specific validate_<provider>_obj_credential();
// 2. dispatch it from validate_obj_credential(); and
// 3. reject requests that set more than one provider credential.
// Generic create/alter normalization does not need provider-specific branches.
ObjCredentialProvider to_obj_credential_provider(const ObjectStoreInfoPB& obj) {
if (!obj.has_provider()) {
return ObjCredentialProvider::Unknown;
}
switch (obj.provider()) {
case ObjectStoreInfoPB::S3:
return ObjCredentialProvider::Aws;
case ObjectStoreInfoPB::AZURE:
return ObjCredentialProvider::Azure;
case ObjectStoreInfoPB::BOS:
return ObjCredentialProvider::Bos;
case ObjectStoreInfoPB::COS:
return ObjCredentialProvider::Cos;
case ObjectStoreInfoPB::GCP:
return ObjCredentialProvider::Gcp;
case ObjectStoreInfoPB::OBS:
return ObjCredentialProvider::Obs;
case ObjectStoreInfoPB::OSS:
return ObjCredentialProvider::Oss;
case ObjectStoreInfoPB::TOS:
return ObjCredentialProvider::Tos;
case ObjectStoreInfoPB::UNKONWN:
return ObjCredentialProvider::Unknown;
}
return ObjCredentialProvider::Unknown;
}
ObjCredentialValidationContext credential_validation_context(const ObjectStoreInfoPB& obj) {
return ObjCredentialValidationContext {
.provider = to_obj_credential_provider(obj),
.ak = obj.ak(),
.sk = obj.sk(),
.has_aws_role_arn = !obj.role_arn().empty(),
.has_aws_external_id = !obj.external_id().empty(),
.has_aws_credential_provider = obj.has_cred_provider_type(),
.has_encrypted_access_keys = obj.has_encryption_info(),
};
}
// A provider-native credential supersedes both common static credentials and
// provider-specific authentication fields outside the credential envelope.
void clear_legacy_credentials(ObjectStoreInfoPB* obj) {
obj->clear_ak();
obj->clear_sk();
obj->clear_encryption_info();
obj->clear_cred_provider_type();
obj->clear_role_arn();
obj->clear_external_id();
}
std::optional<std::string> validate_required_storage_fields(const ObjectStoreInfoPB& obj,
std::string_view provider) {
if (obj.bucket().empty() || obj.endpoint().empty() || obj.region().empty()) {
return std::string(provider) + " storage conf requires bucket, endpoint and region";
}
return std::nullopt;
}
std::optional<std::string> validate_gcp_obj_credential(const ObjectStoreInfoPB& obj) {
const auto& credential = obj.credential().gcp_credential();
if (!credential.has_credential_provider_type()) {
return "GCP credential provider type is required";
}
if (auto error = validate_required_storage_fields(obj, "GCP"); error.has_value()) {
return error;
}
CredentialConfig config;
convert_obj_credential(obj, &config);
return validate_obj_credential_config(config, credential_validation_context(obj));
}
} // namespace
bool has_obj_credential(const ObjectStoreInfoPB& obj) {
return obj.has_credential();
}
std::optional<std::string> validate_obj_credential(const ObjectStoreInfoPB& obj) {
if (!obj.has_credential()) {
return "credential is not set";
}
// Add one provider-specific branch for every new field in
// ObjectStoreCredentialPB. Reject multiple fields before dispatch when a
// second provider is introduced. The common visitor validates only the
// normalized credential itself.
if (obj.credential().has_gcp_credential()) {
return validate_gcp_obj_credential(obj);
}
return "unsupported object storage credential";
}
std::optional<std::string> validate_obj_authentication(const ObjectStoreInfoPB& obj) {
if (has_obj_credential(obj)) {
return validate_obj_credential(obj);
}
if (obj.has_role_arn() && obj.role_arn().empty()) {
return "AWS role ARN cannot be empty";
}
const auto context = credential_validation_context(obj);
if (context.has_static_credentials() && context.has_aws_authentication()) {
return "access keys cannot be combined with AWS role or credential provider";
}
if (!context.has_aws_authentication()) {
return std::nullopt;
}
if (!obj.has_provider() || obj.provider() != ObjectStoreInfoPB::S3) {
return "AWS role and credential provider require provider=S3";
}
if (obj.has_external_id() && obj.external_id().size() > 0 && obj.role_arn().empty()) {
return "AWS external ID requires a role ARN";
}
return std::nullopt;
}
std::optional<std::string> validate_and_normalize_obj_credential(ObjectStoreInfoPB* obj) {
if (auto error = validate_obj_credential(*obj); error.has_value()) {
return error;
}
clear_legacy_credentials(obj);
return std::nullopt;
}
std::optional<std::string> apply_obj_credential(const ObjectStoreInfoPB& update,
ObjectStoreInfoPB* target) {
if (!has_obj_credential(update)) {
return "credential is not set";
}
if (credential_validation_context(update).has_conflicting_credentials()) {
return "credentials cannot be combined with other credentials";
}
// Merge into a copy first so a rejected alter never partially mutates target.
ObjectStoreInfoPB candidate = *target;
copy_obj_credential(update, &candidate);
if (update.credential().has_gcp_credential()) {
const auto& patch = update.credential().gcp_credential();
if (!patch.has_credential_provider_type() && !patch.has_impersonation_service_account()) {
return "GCP credential update must specify a provider type or service account";
}
if (!target->credential().has_gcp_credential() && !patch.has_credential_provider_type() &&
patch.impersonation_service_account().empty()) {
return "clearing impersonation requires an existing native GCP credential; "
"specify a provider type to replace HMAC credentials";
}
auto* credential = candidate.mutable_credential()->mutable_gcp_credential();
if (target->credential().has_gcp_credential()) {
credential->CopyFrom(target->credential().gcp_credential());
credential->MergeFrom(patch);
}
// Only a new native credential needs a default; an existing source is
// preserved when the patch changes just the impersonation target.
if (!credential->has_credential_provider_type()) {
credential->set_credential_provider_type(GcpCredentialPB::DEFAULT);
}
if (credential->impersonation_service_account().empty()) {
credential->clear_impersonation_service_account();
}
}
clear_legacy_credentials(&candidate);
if (auto error = validate_obj_credential(candidate); error.has_value()) {
return error;
}
clear_legacy_credentials(target);
copy_obj_credential(candidate, target);
return std::nullopt;
}
void copy_obj_credential(const ObjectStoreInfoPB& source, ObjectStoreInfoPB* target) {
if (&source == target) {
return;
}
target->clear_credential();
if (source.has_credential()) {
target->mutable_credential()->CopyFrom(source.credential());
}
}
} // namespace doris::cloud