common/cpp/obj-client/auth/gcp/gcp_auth.cpp
Line | Count | Source |
1 | | // Licensed to the Apache Software Foundation (ASF) under one |
2 | | // or more contributor license agreements. See the NOTICE file |
3 | | // distributed with this work for additional information |
4 | | // regarding copyright ownership. The ASF licenses this file |
5 | | // to you under the Apache License, Version 2.0 (the |
6 | | // "License"); you may not use this file except in compliance |
7 | | // with the License. You may obtain a copy of the License at |
8 | | // |
9 | | // http://www.apache.org/licenses/LICENSE-2.0 |
10 | | // |
11 | | // Unless required by applicable law or agreed to in writing, |
12 | | // software distributed under the License is distributed on an |
13 | | // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
14 | | // KIND, either express or implied. See the License for the |
15 | | // specific language governing permissions and limitations |
16 | | // under the License. |
17 | | |
18 | | #include "cpp/obj-client/auth/gcp/gcp_auth.h" |
19 | | |
20 | | #include <algorithm> |
21 | | #include <cctype> |
22 | | #include <regex> |
23 | | #include <utility> |
24 | | |
25 | | namespace doris { |
26 | | |
27 | 28 | bool is_valid_gcp_storage_endpoint(std::string_view endpoint) { |
28 | | // Only service-base XML API endpoints: the S3 clients add the bucket themselves. |
29 | 28 | static const std::regex pattern( |
30 | 28 | R"(https://(storage\.googleapis\.com|[a-z0-9-]+-storage\.googleapis\.com|storage\.[a-z0-9-]+\.rep\.googleapis\.com)(:443)?/?)", |
31 | 28 | std::regex::icase); |
32 | 28 | return std::regex_match(endpoint.begin(), endpoint.end(), pattern); |
33 | 28 | } |
34 | | |
35 | 42 | bool is_valid_gcp_service_account_email(std::string_view email) { |
36 | 42 | constexpr std::string_view user_managed_suffix = ".iam.gserviceaccount.com"; |
37 | 42 | constexpr std::string_view compute_engine_domain = "developer.gserviceaccount.com"; |
38 | 42 | constexpr std::string_view app_engine_domain = "appspot.gserviceaccount.com"; |
39 | 42 | auto at = email.find('@'); |
40 | 42 | if (at == std::string_view::npos || at == 0 || |
41 | 42 | email.find('@', at + 1) != std::string_view::npos || |
42 | 1.40k | std::any_of(email.begin(), email.end(), [](unsigned char c) { return std::isspace(c); })) { |
43 | 7 | return false; |
44 | 7 | } |
45 | | |
46 | 35 | auto domain = email.substr(at + 1); |
47 | 35 | return domain == compute_engine_domain || domain == app_engine_domain || |
48 | 35 | (domain.size() > user_managed_suffix.size() && domain.ends_with(user_managed_suffix)); |
49 | 42 | } |
50 | | |
51 | | std::optional<GcpCredentialProviderType> parse_gcp_credential_provider_type( |
52 | 17 | std::string_view provider_type) { |
53 | 17 | std::string normalized(provider_type); |
54 | 17 | normalized.erase(normalized.begin(), |
55 | 17 | std::find_if(normalized.begin(), normalized.end(), |
56 | 17 | [](unsigned char c) { return !std::isspace(c); })); |
57 | 17 | normalized.erase(std::find_if(normalized.rbegin(), normalized.rend(), |
58 | 17 | [](unsigned char c) { return !std::isspace(c); }) |
59 | 17 | .base(), |
60 | 17 | normalized.end()); |
61 | 17 | std::transform(normalized.begin(), normalized.end(), normalized.begin(), |
62 | 129 | [](unsigned char c) { return static_cast<char>(std::toupper(c)); }); |
63 | 17 | if (normalized == "DEFAULT") { |
64 | 11 | return GcpCredentialProviderType::Default; |
65 | 11 | } |
66 | 6 | if (normalized == "COMPUTE_ENGINE") { |
67 | 3 | return GcpCredentialProviderType::ComputeEngine; |
68 | 3 | } |
69 | 3 | return std::nullopt; |
70 | 6 | } |
71 | | |
72 | 31 | std::optional<std::string> validate_gcp_credential(const GcpCredentialConfig& credential) { |
73 | 31 | if (credential.impersonation_service_account.empty()) { |
74 | 22 | return std::nullopt; |
75 | 22 | } |
76 | 9 | if (!is_valid_gcp_service_account_email(credential.impersonation_service_account)) { |
77 | 2 | return "Invalid GCP service account email"; |
78 | 2 | } |
79 | 7 | return std::nullopt; |
80 | 9 | } |
81 | | |
82 | | GcpCredentialParseResult parse_gcp_credential_properties( |
83 | 38 | const ObjCredentialPropertyGetter& get_property) { |
84 | 38 | auto provider_type_value = get_property(GCP_CREDENTIAL_PROVIDER_TYPE); |
85 | 38 | auto impersonation_service_account = get_property(GCP_IMPERSONATION_SERVICE_ACCOUNT); |
86 | 38 | if (!provider_type_value.has_value() && !impersonation_service_account.has_value()) { |
87 | 17 | return {}; |
88 | 17 | } |
89 | 21 | auto provider_type = provider_type_value.has_value() |
90 | 21 | ? parse_gcp_credential_provider_type(*provider_type_value) |
91 | 21 | : GcpCredentialProviderType::Default; |
92 | 21 | if (!provider_type.has_value()) { |
93 | 3 | return {.error = "invalid " + std::string(GCP_CREDENTIAL_PROVIDER_TYPE) + " value \"" + |
94 | 3 | *provider_type_value + |
95 | 3 | "\"; supported values are DEFAULT and COMPUTE_ENGINE"}; |
96 | 3 | } |
97 | | |
98 | 18 | GcpCredentialConfig config {.provider_type = *provider_type}; |
99 | 18 | if (impersonation_service_account.has_value()) { |
100 | 6 | config.impersonation_service_account = std::move(*impersonation_service_account); |
101 | 6 | } |
102 | 18 | if (auto error = validate_gcp_credential(config); error.has_value()) { |
103 | 2 | return {.error = std::move(error)}; |
104 | 2 | } |
105 | 16 | return {.credential = std::move(config)}; |
106 | 18 | } |
107 | | |
108 | | } // namespace doris |