From 41b563bf607917fdee284fc87019784a7dfc16e8 Mon Sep 17 00:00:00 2001 From: Bala FA Date: Thu, 14 Jul 2022 11:55:23 +0530 Subject: [PATCH] Add more credential providers (#34) * ChainedProvider * EnvAwsProvider * EnvMinioProvider * AwsConfigProvider * MinioClientConfigProvider * AssumeRoleProvider * ClientGrantsProvider * WebIdentityProvider * IamAwsProvider * LdapIdentityProvider * CertificateIdentityProvider Signed-off-by: Bala.FA --- CMakeLists.txt | 11 + cmake/modules/FindInih.cmake | 31 ++ include/credentials.h | 75 +++++ include/creds.h | 70 ---- include/http.h | 4 +- include/providers.h | 616 +++++++++++++++++++++++++++++++++++ include/request.h | 3 +- src/CMakeLists.txt | 2 +- src/baseclient.cc | 17 +- src/creds.cc | 59 ---- src/http.cc | 6 + src/request.cc | 8 +- vcpkg.json | 1 + 13 files changed, 756 insertions(+), 147 deletions(-) create mode 100644 cmake/modules/FindInih.cmake create mode 100644 include/credentials.h delete mode 100644 include/creds.h create mode 100644 include/providers.h delete mode 100644 src/creds.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index ca93dad..b1ebc22 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -48,6 +48,7 @@ ENDIF() SET(requiredlibs) include("${CMAKE_CURRENT_LIST_DIR}/cmake/modules/FindPugiXML.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/cmake/modules/FindInih.cmake") find_package(CURL REQUIRED) IF(CURL_FOUND) @@ -63,6 +64,9 @@ SET(requiredlibs ${requiredlibs} unofficial::curlpp::curlpp) find_package(nlohmann_json CONFIG REQUIRED) SET(requiredlibs ${requiredlibs} nlohmann_json::nlohmann_json) +find_package(nlohmann_json CONFIG REQUIRED) +SET(requiredlibs ${requiredlibs} nlohmann_json::nlohmann_json) + find_package(OpenSSL REQUIRED) IF(OPENSSL_FOUND) INCLUDE_DIRECTORIES(${OPENSSL_INCLUDE_DIR}) @@ -79,6 +83,13 @@ ELSE(PUGIXML_FOUND) MESSAGE(FATAL_ERROR "Could not find the pugixml library and development files.") ENDIF(PUGIXML_FOUND) +IF(INIH_FOUND) + INCLUDE_DIRECTORIES(${INIH_INCLUDE_DIR}) + SET(requiredlibs ${requiredlibs} inih) +ELSE(INIH_FOUND) + MESSAGE(FATAL_ERROR "Could not find the inih library and development files.") +ENDIF(INIH_FOUND) + message(STATUS "Found required libs: ${requiredlibs}") INCLUDE (CheckIncludeFiles) diff --git a/cmake/modules/FindInih.cmake b/cmake/modules/FindInih.cmake new file mode 100644 index 0000000..8de3364 --- /dev/null +++ b/cmake/modules/FindInih.cmake @@ -0,0 +1,31 @@ +# Find the inih XML parsing library. +# +# Sets the usual variables expected for find_package scripts: +# +# INIH_INCLUDE_DIR - header location +# INIH_LIBRARIES - library to link against +# INIH_FOUND - true if inih was found. + +find_path (INIH_INCLUDE_DIR + NAMES INIReader.h + PATHS ${INIH_HOME}/include) +find_library (INIH_LIBRARY + NAMES inih + PATHS ${INIH_HOME}/lib) + +# Support the REQUIRED and QUIET arguments, and set INIH_FOUND if found. +include (FindPackageHandleStandardArgs) +FIND_PACKAGE_HANDLE_STANDARD_ARGS (inih DEFAULT_MSG INIH_LIBRARY + INIH_INCLUDE_DIR) + +if (INIH_FOUND) + set (INIH_LIBRARIES ${INIH_LIBRARY}) + if (NOT inih_FIND_QUIETLY) + message (STATUS "inih include = ${INIH_INCLUDE_DIR}") + message (STATUS "inih library = ${INIH_LIBRARY}") + endif () +else () + message (STATUS "No inih found") +endif() + +mark_as_advanced (INIH_LIBRARY INIH_INCLUDE_DIR) diff --git a/include/credentials.h b/include/credentials.h new file mode 100644 index 0000000..197d78c --- /dev/null +++ b/include/credentials.h @@ -0,0 +1,75 @@ +// MinIO C++ Library for Amazon S3 Compatible Cloud Storage +// Copyright 2022 MinIO, 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. + +#ifndef _MINIO_CREDS_CREDENTIALS_H +#define _MINIO_CREDS_CREDENTIALS_H + +#include + +#include "utils.h" + +namespace minio { +namespace creds { +static bool expired(utils::Time expiration) { + if (!expiration) return false; + utils::Time now = utils::Time::Now(); + now.Add(10); + return expiration < now; +} + +/** + * Credentials contains access key and secret key with optional session token + * and expiration. + */ +struct Credentials { + error::Error err; + std::string access_key; + std::string secret_key; + std::string session_token; + utils::Time expiration; + + bool IsExpired() { return expired(expiration); } + + operator bool() const { + return !err && !access_key.empty() && expired(expiration); + } + + static Credentials ParseXML(std::string_view data, std::string root) { + pugi::xml_document xdoc; + pugi::xml_parse_result result = xdoc.load_string(data.data()); + if (!result) return Credentials{error::Error("unable to parse XML")}; + + auto credentials = xdoc.select_node((root + "/Credentials").c_str()); + + auto text = credentials.node().select_node("AccessKeyId/text()"); + std::string access_key = text.node().value(); + + text = credentials.node().select_node("SecretAccessKey/text()"); + std::string secret_key = text.node().value(); + + text = credentials.node().select_node("SessionToken/text()"); + std::string session_token = text.node().value(); + + text = credentials.node().select_node("Expiration/text()"); + auto expiration = utils::Time::FromISO8601UTC(text.node().value()); + + return Credentials{error::SUCCESS, access_key, secret_key, session_token, + expiration}; + } +}; // class Credentials +} // namespace creds +} // namespace minio + +#endif // #ifndef _MINIO_CREDS_CREDENTIALS_H diff --git a/include/creds.h b/include/creds.h deleted file mode 100644 index db323cb..0000000 --- a/include/creds.h +++ /dev/null @@ -1,70 +0,0 @@ -// MinIO C++ Library for Amazon S3 Compatible Cloud Storage -// Copyright 2022 MinIO, 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. - -#ifndef _MINIO_CREDS_H -#define _MINIO_CREDS_H - -#include - -namespace minio { -namespace creds { -/** - * Credentials contains access key and secret key with optional session token - * and expiration. - */ -class Credentials { - private: - std::string_view access_key_; - std::string_view secret_key_; - std::string_view session_token_; - unsigned int expiration_; - - public: - Credentials(const Credentials& creds); - Credentials(std::string_view access_key, std::string_view secret_key, - std::string_view session_token = "", unsigned int expiration = 0); - std::string AccessKey(); - std::string SecretKey(); - std::string SessionToken(); - bool IsExpired(); -}; // class Credentials - -/** - * Credential provider interface. - */ -class Provider { - public: - Provider() {} - virtual ~Provider() {} - virtual Credentials Fetch() = 0; -}; // class Provider - -/** - * Static credential provider. - */ -class StaticProvider : public Provider { - private: - Credentials* creds_ = NULL; - - public: - StaticProvider(std::string_view access_key, std::string_view secret_key, - std::string_view session_token = ""); - ~StaticProvider(); - Credentials Fetch(); -}; // class StaticProvider -} // namespace creds -} // namespace minio - -#endif // #ifndef _MINIO_CREDS_H diff --git a/include/http.h b/include/http.h index d658174..d265fab 100644 --- a/include/http.h +++ b/include/http.h @@ -175,6 +175,8 @@ struct Request { bool debug = false; bool ignore_cert_check = false; std::string ssl_cert_file; + std::string key_file; + std::string cert_file; Request(Method method, Url url); Response Execute(); @@ -200,7 +202,7 @@ struct Response { operator bool() const { return error.empty() && status_code >= 200 && status_code <= 299; } - error::Error GetError() { + error::Error Error() { if (!error.empty()) return error::Error(error); if (status_code && (status_code < 200 || status_code > 299)) { return error::Error("failed with HTTP status code " + diff --git a/include/providers.h b/include/providers.h new file mode 100644 index 0000000..be67959 --- /dev/null +++ b/include/providers.h @@ -0,0 +1,616 @@ +// MinIO C++ Library for Amazon S3 Compatible Cloud Storage +// Copyright 2022 MinIO, 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. + +#ifndef _MINIO_CREDS_PROVIDERS_H +#define _MINIO_CREDS_PROVIDERS_H + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "credentials.h" +#include "signer.h" +#include "utils.h" + +#define DEFAULT_DURATION_SECONDS (60 * 60 * 24) // 1 day. +#define MIN_DURATION_SECONDS (60 * 15) // 15 minutes. +#define MAX_DURATION_SECONDS (60 * 60 * 24 * 7) // 7 days. + +namespace minio { +namespace creds { +struct Jwt { + std::string token; + unsigned int expiry = 0; + + operator bool() const { return !token.empty(); } +}; // struct Jwt + +using JwtFunction = std::function; + +static error::Error checkLoopbackHost(std::string host) { + struct addrinfo hints = {0}; + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + int status; + struct addrinfo* res = NULL; + if ((status = getaddrinfo(host.c_str(), NULL, &hints, &res)) != 0) { + return error::Error(std::string("getaddrinfo: ") + gai_strerror(status)); + } + + for (struct addrinfo* ai = res; ai != NULL; ai = ai->ai_next) { + std::string ip(inet_ntoa(((struct sockaddr_in*)ai->ai_addr)->sin_addr)); + if (!utils::StartsWith(ip, "127.")) { + return error::Error(host + " is not loopback only host"); + } + } + + freeaddrinfo(res); // free the linked list + + return error::SUCCESS; +} + +/** + * Credential provider interface. + */ +class Provider { + protected: + error::Error err_; + Credentials creds_; + + public: + Provider() {} + + virtual ~Provider() {} + + operator bool() const { return !err_; } + + virtual Credentials Fetch() = 0; +}; // class Provider + +class ChainedProvider : public Provider { + private: + std::list providers_; + Provider* provider_ = NULL; + + public: + ChainedProvider(std::list providers) { + this->providers_ = providers; + } + + Credentials Fetch() { + if (err_) return Credentials{err_}; + + if (creds_) return creds_; + + if (provider_ != NULL) { + creds_ = provider_->Fetch(); + if (creds_) return creds_; + } + + for (auto provider : providers_) { + provider_ = provider; + creds_ = provider_->Fetch(); + if (creds_) return creds_; + } + + return Credentials{error::Error("All providers fail to fetch credentials")}; + } +}; // class ChainedProvider + +/** + * Static credential provider. + */ +class StaticProvider : public Provider { + public: + StaticProvider(std::string access_key, std::string secret_key, + std::string session_token = "") { + this->creds_ = + Credentials{error::SUCCESS, access_key, secret_key, session_token}; + } + + Credentials Fetch() { return creds_; } +}; // class StaticProvider + +class EnvAwsProvider : public Provider { + public: + EnvAwsProvider() { + std::string access_key; + std::string secret_key; + std::string session_token; + + if (!utils::GetEnv(access_key, "AWS_ACCESS_KEY_ID")) { + utils::GetEnv(access_key, "AWS_ACCESS_KEY"); + } + if (!utils::GetEnv(secret_key, "AWS_SECRET_ACCESS_KEY")) { + utils::GetEnv(secret_key, "AWS_SECRET_KEY"); + } + utils::GetEnv(session_token, "AWS_SESSION_TOKEN"); + + this->creds_ = + Credentials{error::SUCCESS, access_key, secret_key, session_token}; + } + + Credentials Fetch() { return creds_; } +}; // class EnvAwsProvider + +class EnvMinioProvider : public Provider { + public: + EnvMinioProvider() { + std::string access_key; + std::string secret_key; + + utils::GetEnv(access_key, "MINIO_ACCESS_KEY"); + utils::GetEnv(secret_key, "MINIO_SECRET_KEY"); + this->creds_ = Credentials{error::SUCCESS, access_key, secret_key}; + } + + Credentials Fetch() { return creds_; } +}; // class EnvMinioProvider + +class AwsConfigProvider : public Provider { + public: + AwsConfigProvider(std::string filename = "", std::string profile = "") { + if (filename.empty()) { + if (!utils::GetEnv(filename, "AWS_SHARED_CREDENTIALS_FILE")) { + filename = utils::GetHomeDir() + "/aws/credentials"; + } + } + + if (profile.empty()) { + if (!utils::GetEnv(profile, "AWS_PROFILE")) profile = "default"; + } + + INIReader reader(filename); + if (reader.ParseError() < 0) { + this->creds_ = Credentials{error::Error("unable to read " + filename)}; + } else { + this->creds_ = Credentials{ + error::SUCCESS, reader.Get(profile, "aws_access_key_id", ""), + reader.Get(profile, "aws_secret_access_key", ""), + reader.Get(profile, "aws_session_token", "")}; + } + } + + Credentials Fetch() { return creds_; } +}; // class AwsConfigProvider + +class MinioClientConfigProvider : public Provider { + public: + MinioClientConfigProvider(std::string filename = "", std::string alias = "") { + if (filename.empty()) filename = utils::GetHomeDir() + "/.mc/config.json"; + + if (alias.empty()) { + if (!utils::GetEnv(alias, "MINIO_ALIAS")) alias = "s3"; + } + + std::ifstream ifs(filename); + nlohmann::json json = nlohmann::json::parse(ifs); + ifs.close(); + + nlohmann::json aliases; + if (json.contains("hosts")) { + aliases = json["hosts"]; + } else if (json.contains("aliases")) { + aliases = json["aliases"]; + } else { + this->creds_ = Credentials{ + error::Error("invalid configuration in file " + filename)}; + return; + } + + if (!aliases.contains(alias)) { + this->creds_ = Credentials{error::Error( + "alias " + alias + " not found in MinIO client configuration file " + + filename)}; + return; + } + + this->creds_ = Credentials{error::SUCCESS, aliases[alias]["accessKey"], + aliases[alias]["secretKey"]}; + } + + Credentials Fetch() { return creds_; } +}; // class MinioClientConfigProvider + +class AssumeRoleProvider : public Provider { + private: + http::Url sts_endpoint_; + std::string access_key_; + std::string secret_key_; + std::string region_; + std::string body_; + std::string content_sha256_; + + public: + AssumeRoleProvider(http::Url sts_endpoint, std::string access_key, + std::string secret_key, unsigned int duration_seconds = 0, + std::string policy = "", std::string region = "", + std::string role_arn = "", + std::string role_session_name = "", + std::string external_id = "") { + this->sts_endpoint_ = sts_endpoint; + this->access_key_ = access_key; + this->secret_key_ = secret_key; + this->region_ = region; + + if (duration_seconds < DEFAULT_DURATION_SECONDS) { + duration_seconds = DEFAULT_DURATION_SECONDS; + } + + utils::Multimap map; + map.Add("Action", "AssumeRole"); + map.Add("Version", "2011-06-15"); + map.Add("DurationSeconds", std::to_string(duration_seconds)); + if (!role_arn.empty()) map.Add("RoleArn", role_arn); + if (!role_session_name.empty()) { + map.Add("RoleSessionName", role_session_name); + } + if (!policy.empty()) map.Add("Policy", policy); + if (!external_id.empty()) map.Add("ExternalId", external_id); + + this->body_ = map.ToQueryString(); + this->content_sha256_ = utils::Sha256Hash(body_); + } + + Credentials Fetch() { + if (err_) return Credentials{err_}; + + if (creds_) return creds_; + + utils::Time date = utils::Time::Now(); + utils::Multimap headers; + headers.Add("Content-Type", "application/x-www-form-urlencoded"); + headers.Add("Host", sts_endpoint_.host); + headers.Add("X-Amz-Date", date.ToAmzDate()); + + http::Method method = http::Method::kPost; + signer::SignV4STS(method, sts_endpoint_.path, region_, headers, + utils::Multimap(), access_key_, secret_key_, + content_sha256_, date); + + http::Request req(method, sts_endpoint_); + req.headers = headers; + req.body = body_; + http::Response resp = req.Execute(); + if (!resp) { + creds_ = Credentials{resp.Error()}; + } else { + creds_ = Credentials::ParseXML(resp.body, "AssumeRoleResult"); + } + + return creds_; + } +}; // class AssumeRoleProvider + +class WebIdentityClientGrantsProvider : public Provider { + private: + JwtFunction jwtfunc_ = NULL; + http::Url sts_endpoint_; + unsigned int duration_seconds_ = 0; + std::string policy_; + std::string role_arn_; + std::string role_session_name_; + + public: + WebIdentityClientGrantsProvider(JwtFunction jwtfunc, http::Url sts_endpoint, + unsigned int duration_seconds = 0, + std::string policy = "", + std::string role_arn = "", + std::string role_session_name = "") { + this->jwtfunc_ = jwtfunc; + this->sts_endpoint_ = sts_endpoint; + this->duration_seconds_ = duration_seconds; + this->policy_ = policy; + this->role_arn_ = role_arn; + this->role_session_name_ = role_session_name; + } + + virtual bool IsWebIdentity() = 0; + + unsigned int getDurationSeconds(unsigned int expiry) { + if (duration_seconds_) expiry = duration_seconds_; + if (expiry > MAX_DURATION_SECONDS) return MAX_DURATION_SECONDS; + if (expiry == 0) return expiry; + if (expiry < MIN_DURATION_SECONDS) return MIN_DURATION_SECONDS; + return expiry; + } + + Credentials Fetch() { + if (creds_) return creds_; + + Jwt jwt = jwtfunc_(); + + utils::Multimap map; + map.Add("Version", "2011-06-15"); + unsigned int duration_seconds = getDurationSeconds(jwt.expiry); + if (duration_seconds) { + map.Add("DurationSeconds", std::to_string(duration_seconds)); + } + if (!policy_.empty()) map.Add("Policy", policy_); + + if (IsWebIdentity()) { + map.Add("Action", "AssumeRoleWithWebIdentity"); + map.Add("WebIdentityToken", jwt.token); + if (!role_arn_.empty()) { + map.Add("RoleArn", role_arn_); + if (!role_session_name_.empty()) { + map.Add("RoleSessionName", role_session_name_); + } else { + map.Add("RoleSessionName", utils::Time::Now().ToISO8601UTC()); + } + } + } else { + map.Add("Action", "AssumeRoleWithClientGrants"); + map.Add("Token", jwt.token); + } + + http::Url url = sts_endpoint_; + url.query_string = map.ToQueryString(); + http::Request req(http::Method::kPost, url); + http::Response resp = req.Execute(); + if (!resp) { + creds_ = Credentials{resp.Error()}; + } else { + creds_ = Credentials::ParseXML( + resp.body, IsWebIdentity() ? "AssumeRoleWithWebIdentityResult" + : "AssumeRoleWithClientGrantsResult"); + } + return creds_; + } +}; // class WebIdentityClientGrantsProvider + +class ClientGrantsProvider : public WebIdentityClientGrantsProvider { + public: + ClientGrantsProvider(JwtFunction jwtfunc, http::Url sts_endpoint, + unsigned int duration_seconds = 0, + std::string policy = "", std::string role_arn = "", + std::string role_session_name = "") + : WebIdentityClientGrantsProvider(jwtfunc, sts_endpoint, duration_seconds, + policy, role_arn, role_session_name) {} + bool IsWebIdentity() { return false; } +}; // class ClientGrantsProvider + +class WebIdentityProvider : public WebIdentityClientGrantsProvider { + public: + WebIdentityProvider(JwtFunction jwtfunc, http::Url sts_endpoint, + unsigned int duration_seconds = 0, + std::string policy = "", std::string role_arn = "", + std::string role_session_name = "") + : WebIdentityClientGrantsProvider(jwtfunc, sts_endpoint, duration_seconds, + policy, role_arn, role_session_name) {} + bool IsWebIdentity() { return true; } +}; // class WebIdentityProvider + +class IamAwsProvider : public Provider { + private: + http::Url custom_endpoint_; + std::string token_file_; + std::string aws_region_; + std::string role_arn_; + std::string role_session_name_; + std::string relative_uri_; + std::string full_uri_; + + Credentials fetch(http::Url url) { + http::Request req(http::Method::kGet, url); + http::Response resp = req.Execute(); + if (!resp) return Credentials{resp.Error()}; + + nlohmann::json json = nlohmann::json::parse(resp.body); + std::string code = json.value("Code", "Success"); + if (code != "Success") { + return Credentials{error::Error(url.String() + " failed with code " + + code + " and message " + + json.value("Message", ""))}; + } + + std::string expiration = json["Expiration"]; + return Credentials{error::SUCCESS, json["AccessKeyId"], + json["SecretAccessKey"], json["Token"], + utils::Time::FromISO8601UTC(expiration.c_str())}; + } + + error::Error getRoleName(std::string& role_name, http::Url url) { + http::Request req(http::Method::kGet, url); + http::Response resp = req.Execute(); + if (!resp) return resp.Error(); + + std::list role_names; + std::string lines = resp.body; + size_t pos; + while ((pos = lines.find("\n")) != std::string::npos) { + role_names.push_back(lines.substr(0, pos)); + lines.erase(0, pos + 1); + } + if (!lines.empty()) role_names.push_back(lines); + + if (role_names.empty()) { + return error::Error("no IAM roles attached to EC2 service " + url); + } + + role_name = utils::Trim(role_names.front(), '\r'); + return error::SUCCESS; + } + + public: + IamAwsProvider(http::Url custom_endpoint = http::Url()) { + this->custom_endpoint_ = custom_endpoint; + utils::GetEnv(this->token_file_, "AWS_WEB_IDENTITY_TOKEN_FILE"); + utils::GetEnv(this->aws_region_, "AWS_REGION"); + utils::GetEnv(this->role_arn_, "AWS_ROLE_ARN"); + utils::GetEnv(this->role_session_name_, "AWS_ROLE_SESSION_NAME"); + utils::GetEnv(this->relative_uri_, + "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); + if (!this->relative_uri_.empty() && this->relative_uri_.front() != '/') { + this->relative_uri_ = "/" + this->relative_uri_; + } + utils::GetEnv(this->full_uri_, "AWS_CONTAINER_CREDENTIALS_FULL_URI"); + } + + Credentials Fetch() { + if (creds_) return creds_; + + http::Url url = custom_endpoint_; + if (!token_file_.empty()) { + if (!url) { + url.https = true; + url.host = "sts.amazonaws.com"; + if (!aws_region_.empty()) { + url.host = "sts." + aws_region_ + ".amazonaws.com"; + } + } + + WebIdentityProvider provider = WebIdentityProvider( + [&token_file = token_file_]() -> Jwt { + std::ifstream ifs(token_file); + nlohmann::json json = nlohmann::json::parse(ifs); + ifs.close(); + return Jwt{json["access_token"], json["expires_in"]}; + }, + url, 0, "", role_arn_, role_session_name_); + creds_ = provider.Fetch(); + return creds_; + } + + if (!relative_uri_.empty()) { + if (!url) { + url.https = true; + url.host = "169.254.170.2"; + url.path = relative_uri_; + } + } else if (!full_uri_.empty()) { + if (!url) url = http::Url::Parse(full_uri_); + if (error::Error err = checkLoopbackHost(url.host)) { + creds_ = Credentials{err}; + return creds_; + } + } else { + if (!url) { + url.https = true; + url.host = "169.254.169.254"; + url.path = "/latest/meta-data/iam/security-credentials/"; + } + + std::string role_name; + if (error::Error err = getRoleName(role_name, url)) { + creds_ = Credentials{err}; + return creds_; + } + + url.path += "/" + role_name; + } + + creds_ = fetch(url); + return creds_; + } +}; // class IamAwsProvider + +class LdapIdentityProvider : public Provider { + private: + http::Url sts_endpoint_; + + public: + LdapIdentityProvider(http::Url sts_endpoint, std::string ldap_username, + std::string ldap_password) { + this->sts_endpoint_ = sts_endpoint; + utils::Multimap map; + map.Add("Action", "AssumeRoleWithLDAPIdentity"); + map.Add("Version", "2011-06-15"); + map.Add("LDAPUsername", ldap_username); + map.Add("LDAPPassword", ldap_password); + this->sts_endpoint_.query_string = map.ToQueryString(); + } + + Credentials Fetch() { + if (creds_) return creds_; + + http::Request req(http::Method::kPost, sts_endpoint_); + http::Response resp = req.Execute(); + if (!resp) return Credentials{resp.Error()}; + + creds_ = + Credentials::ParseXML(resp.body, "AssumeRoleWithLDAPIdentityResult"); + return creds_; + } +}; // class LdapIdentityProvider + +struct CertificateIdentityProvider : public Provider { + private: + http::Url sts_endpoint_; + std::string key_file_; + std::string cert_file_; + std::string ssl_cert_file_; + + public: + CertificateIdentityProvider(http::Url sts_endpoint, std::string key_file, + std::string cert_file, + std::string ssl_cert_file = "", + unsigned int duration_seconds = 0) { + if (!sts_endpoint.https) { + this->err_ = error::Error("sts endpoint scheme must be HTTPS"); + return; + } + + if (key_file.empty() || cert_file.empty()) { + this->err_ = error::Error("client key and certificate must be provided"); + return; + } + + unsigned int expiry = duration_seconds; + if (duration_seconds < DEFAULT_DURATION_SECONDS) { + expiry = DEFAULT_DURATION_SECONDS; + } + + utils::Multimap map; + map.Add("Action", "AssumeRoleWithCertificate"); + map.Add("Version", "2011-06-15"); + map.Add("DurationSeconds", std::to_string(expiry)); + + sts_endpoint_ = sts_endpoint; + sts_endpoint_.query_string = map.ToQueryString(); + key_file_ = key_file; + cert_file_ = cert_file; + ssl_cert_file_ = ssl_cert_file; + } + + Credentials Fetch() { + if (err_) return Credentials{err_}; + + if (creds_) return creds_; + + http::Request req(http::Method::kPost, sts_endpoint_); + req.ssl_cert_file = ssl_cert_file_; + req.key_file = key_file_; + req.cert_file = cert_file_; + + http::Response resp = req.Execute(); + if (!resp) return Credentials{resp.Error()}; + + creds_ = + Credentials::ParseXML(resp.body, "AssumeRoleWithCertificateResult"); + return creds_; + } +}; // struct CertificateIdentityProvider +} // namespace creds +} // namespace minio + +#endif // #ifndef _MINIO_CREDS_PROVIDERS_H diff --git a/include/request.h b/include/request.h index 917e010..ced5778 100644 --- a/include/request.h +++ b/include/request.h @@ -16,7 +16,8 @@ #ifndef _MINIO_REQUEST_H #define _MINIO_REQUEST_H -#include "creds.h" +#include "credentials.h" +#include "providers.h" #include "signer.h" namespace minio { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1915e72..b9c6cc8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -SET(SRCS args.cc baseclient.cc client.cc creds.cc http.cc request.cc response.cc select.cc signer.cc types.cc utils.cc) +SET(SRCS args.cc baseclient.cc client.cc http.cc request.cc response.cc select.cc signer.cc types.cc utils.cc) SET(S3CLIENT_INSTALL_LIST) add_library(miniocpp STATIC ${SRCS}) diff --git a/src/baseclient.cc b/src/baseclient.cc index 0544463..b16154c 100644 --- a/src/baseclient.cc +++ b/src/baseclient.cc @@ -927,17 +927,16 @@ minio::s3::BaseClient::GetPresignedObjectUrl(GetPresignedObjectUrlArgs args) { if (provider_ != NULL) { creds::Credentials creds = provider_->Fetch(); - if (!creds.SessionToken().empty()) { - query_params.Add("X-Amz-Security-Token", creds.SessionToken()); + if (!creds.session_token.empty()) { + query_params.Add("X-Amz-Security-Token", creds.session_token); } utils::Time date = utils::Time::Now(); if (args.request_time) date = args.request_time; - std::string access_key = creds.AccessKey(); - std::string secret_key = creds.SecretKey(); signer::PresignV4(args.method, url.host, url.path, region, query_params, - access_key, secret_key, date, args.expiry_seconds); + creds.access_key, creds.secret_key, date, + args.expiry_seconds); url.query_string = query_params.ToQueryString(); } @@ -963,12 +962,10 @@ minio::s3::BaseClient::GetPresignedPostFormData(PostPolicy policy) { } creds::Credentials creds = provider_->Fetch(); - std::string access_key = creds.AccessKey(); - std::string secret_key = creds.SecretKey(); - std::string session_token = creds.SessionToken(); std::map data; - if (error::Error err = policy.FormData(data, access_key, secret_key, - session_token, region)) { + if (error::Error err = + policy.FormData(data, creds.access_key, creds.secret_key, + creds.session_token, region)) { return err; } return data; diff --git a/src/creds.cc b/src/creds.cc deleted file mode 100644 index 8a2a42e..0000000 --- a/src/creds.cc +++ /dev/null @@ -1,59 +0,0 @@ -// MinIO C++ Library for Amazon S3 Compatible Cloud Storage -// Copyright 2022 MinIO, 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 "creds.h" - -minio::creds::Credentials::Credentials(const Credentials& creds) { - access_key_ = creds.access_key_; - secret_key_ = creds.secret_key_; - session_token_ = creds.session_token_; - expiration_ = creds.expiration_; -} - -minio::creds::Credentials::Credentials(std::string_view access_key, - std::string_view secret_key, - std::string_view session_token, - unsigned int expiration) { - access_key_ = access_key; - secret_key_ = secret_key; - session_token_ = session_token; - expiration_ = expiration; -} - -std::string minio::creds::Credentials::AccessKey() { - return std::string(access_key_); -} - -std::string minio::creds::Credentials::SecretKey() { - return std::string(secret_key_); -} - -std::string minio::creds::Credentials::SessionToken() { - return std::string(session_token_); -} - -bool minio::creds::Credentials::IsExpired() { return expiration_ != 0; } - -minio::creds::StaticProvider::StaticProvider(std::string_view access_key, - std::string_view secret_key, - std::string_view session_token) { - creds_ = new Credentials(access_key, secret_key, session_token); -} - -minio::creds::StaticProvider::~StaticProvider() { delete creds_; } - -minio::creds::Credentials minio::creds::StaticProvider::Fetch() { - return *creds_; -} diff --git a/src/http.cc b/src/http.cc index 62d703a..77982af 100644 --- a/src/http.cc +++ b/src/http.cc @@ -195,6 +195,12 @@ minio::http::Response minio::http::Request::execute() { request.setOpt(new curlpp::Options::SslVerifyPeer(true)); request.setOpt(new curlpp::Options::CaInfo(ssl_cert_file)); } + if (!key_file.empty()) { + request.setOpt(new curlpp::Options::SslKey(key_file)); + } + if (!cert_file.empty()) { + request.setOpt(new curlpp::Options::SslCert(cert_file)); + } } utils::CharBuffer charbuf((char *)body.data(), body.size()); diff --git a/src/request.cc b/src/request.cc index b836367..3e3de6d 100644 --- a/src/request.cc +++ b/src/request.cc @@ -170,14 +170,12 @@ void minio::s3::Request::BuildHeaders(http::Url& url, if (provider != NULL) { creds::Credentials creds = provider->Fetch(); - if (!creds.SessionToken().empty()) { - headers.Add("X-Amz-Security-Token", creds.SessionToken()); + if (!creds.session_token.empty()) { + headers.Add("X-Amz-Security-Token", creds.session_token); } - std::string access_key = creds.AccessKey(); - std::string secret_key = creds.SecretKey(); signer::SignV4S3(method, url.path, region, headers, query_params, - access_key, secret_key, sha256, date); + creds.access_key, creds.secret_key, sha256, date); } } diff --git a/vcpkg.json b/vcpkg.json index df910b9..8440ad9 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -6,6 +6,7 @@ "openssl", "curlpp", "nlohmann-json", + "inih", "pugixml" ] }