blob: 28740412c04ef729506268bc119d8f723b7bb630 [file]
/*
* Copyright 2026-present Alibaba Inc.
*
* 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 "paimon/common/utils/sensitive_config_utils.h"
#include "paimon/common/utils/string_utils.h"
namespace paimon {
namespace {
// Substring markers of a credential, matched against the normalized form so that the
// separators of a key or a message do not matter.
constexpr const char* kSensitiveMarkers[] = {
"password", "secret", "token", "credential", "accesskey", "accountkey",
"encryptionkey", "authorization", "privatekey", "apikey", "sas"};
// Keys whose value is masked as a whole rather than keeping a trailing hint. Every true
// secret is here; only an identifier-like key keeps a tail. "accessKeySecret" normalizes
// to contain "secret" and is masked as a whole, while "accessKeyId" hits only the
// "accesskey" marker of `kSensitiveMarkers` and keeps its tail. This mirrors AWS/Azure,
// where the access key id is loggable but the secret, the token and the SAS are not.
constexpr const char* kFullMaskMarkers[] = {"password", "token", "authorization", "secret",
"credential", "privatekey", "encryptionkey", "apikey",
"accountkey", "sas"};
// A value shorter than this reveals too much of itself through a four character tail, so
// it is masked as a whole.
constexpr size_t kMinLengthForTail = 12;
constexpr size_t kTailLength = 4;
// Free-form text is scanned for one more marker: in a message "signature" names a
// credential, while in an option key it names a request signature algorithm.
constexpr const char kTextOnlyMarker[] = "signature";
// The Azure SAS "sig" would be ambiguous once separators are removed ("sig" is a
// substring of many words), so it is matched literally instead.
constexpr const char* kLiteralTextMarkers[] = {"sig=", "\"sig\"", "'sig'"};
// Drops every character of an already lower-cased string that is not a letter or a
// digit, so "access-key", "access.key" and "accessKey" all normalize to "accesskey".
std::string StripSeparators(const std::string& lowered) {
std::string normalized;
normalized.reserve(lowered.size());
for (char c : lowered) {
if ((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
normalized.push_back(c);
}
}
return normalized;
}
template <size_t N>
bool ContainsMarker(const std::string& normalized, const char* const (&markers)[N]) {
for (const char* marker : markers) {
if (normalized.find(marker) != std::string::npos) {
return true;
}
}
return false;
}
std::string NormalizeKey(const std::string& key) {
return StripSeparators(StringUtils::ToLowerCase(key));
}
} // namespace
bool SensitiveConfigUtils::IsSensitiveKey(const std::string& key) {
if (key.empty()) {
return false;
}
return ContainsMarker(NormalizeKey(key), kSensitiveMarkers);
}
std::string SensitiveConfigUtils::RedactValue(const std::string& key, const std::string& value) {
if (key.empty()) {
return value;
}
std::string normalized = NormalizeKey(key);
if (!ContainsMarker(normalized, kSensitiveMarkers)) {
return value;
}
if (!ContainsMarker(normalized, kFullMaskMarkers) && value.size() >= kMinLengthForTail) {
return "****" + value.substr(value.size() - kTailLength);
}
return kRedacted;
}
std::map<std::string, std::string> SensitiveConfigUtils::RedactMap(
const std::map<std::string, std::string>& options) {
std::map<std::string, std::string> redacted;
for (const auto& [key, value] : options) {
redacted.emplace(key, RedactValue(key, value));
}
return redacted;
}
std::string SensitiveConfigUtils::RedactText(const std::string& text) {
if (text.empty()) {
return text;
}
std::string lowered = StringUtils::ToLowerCase(text);
for (const char* marker : kLiteralTextMarkers) {
if (lowered.find(marker) != std::string::npos) {
return kRedacted;
}
}
std::string normalized = StripSeparators(lowered);
if (ContainsMarker(normalized, kSensitiveMarkers) ||
normalized.find(kTextOnlyMarker) != std::string::npos) {
return kRedacted;
}
return text;
}
} // namespace paimon