providers.h 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. // MinIO C++ Library for Amazon S3 Compatible Cloud Storage
  2. // Copyright 2022 MinIO, Inc.
  3. //
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. //
  8. // http://www.apache.org/licenses/LICENSE-2.0
  9. //
  10. // Unless required by applicable law or agreed to in writing, software
  11. // distributed under the License is distributed on an "AS IS" BASIS,
  12. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. // See the License for the specific language governing permissions and
  14. // limitations under the License.
  15. #ifndef _MINIO_CREDS_PROVIDERS_H
  16. #define _MINIO_CREDS_PROVIDERS_H
  17. #ifdef _MSC_VER
  18. # include <winsock2.h>
  19. # include <ws2tcpip.h>
  20. # pragma comment(lib, "ws2_32.lib")
  21. #else
  22. # include <arpa/inet.h>
  23. # include <netdb.h>
  24. # include <sys/socket.h>
  25. #endif
  26. #include <INIReader.h>
  27. #include <sys/types.h>
  28. #include "credentials.h"
  29. #include "signer.h"
  30. #include "utils.h"
  31. #include <fstream>
  32. #include <nlohmann/json.hpp>
  33. #include <string>
  34. #define DEFAULT_DURATION_SECONDS (60 * 60 * 24) // 1 day.
  35. #define MIN_DURATION_SECONDS (60 * 15) // 15 minutes.
  36. #define MAX_DURATION_SECONDS (60 * 60 * 24 * 7) // 7 days.
  37. #undef close
  38. namespace minio {
  39. namespace creds {
  40. struct Jwt {
  41. std::string token;
  42. unsigned int expiry = 0;
  43. operator bool() const { return !token.empty(); }
  44. }; // struct Jwt
  45. using JwtFunction = std::function<Jwt()>;
  46. static error::Error checkLoopbackHost(std::string host) {
  47. struct addrinfo hints = {0};
  48. hints.ai_family = AF_INET;
  49. hints.ai_socktype = SOCK_STREAM;
  50. int status;
  51. struct addrinfo* res = NULL;
  52. if ((status = getaddrinfo(host.c_str(), NULL, &hints, &res)) != 0) {
  53. return error::Error(std::string("getaddrinfo: ") + gai_strerror(status));
  54. }
  55. for (struct addrinfo* ai = res; ai != NULL; ai = ai->ai_next) {
  56. std::string ip(inet_ntoa(((struct sockaddr_in*)ai->ai_addr)->sin_addr));
  57. if (!utils::StartsWith(ip, "127.")) {
  58. return error::Error(host + " is not loopback only host");
  59. }
  60. }
  61. freeaddrinfo(res); // free the linked list
  62. return error::SUCCESS;
  63. }
  64. /**
  65. * Credential provider interface.
  66. */
  67. class Provider {
  68. protected:
  69. error::Error err_;
  70. Credentials creds_;
  71. public:
  72. Provider() {}
  73. virtual ~Provider() {}
  74. operator bool() const { return !err_; }
  75. virtual Credentials Fetch() = 0;
  76. }; // class Provider
  77. class ChainedProvider : public Provider {
  78. private:
  79. std::list<Provider*> providers_;
  80. Provider* provider_ = NULL;
  81. public:
  82. ChainedProvider(std::list<Provider*> providers) {
  83. this->providers_ = providers;
  84. }
  85. Credentials Fetch() {
  86. if (err_) return Credentials{err_};
  87. if (creds_) return creds_;
  88. if (provider_ != NULL) {
  89. creds_ = provider_->Fetch();
  90. if (creds_) return creds_;
  91. }
  92. for (auto provider : providers_) {
  93. provider_ = provider;
  94. creds_ = provider_->Fetch();
  95. if (creds_) return creds_;
  96. }
  97. return Credentials{error::Error("All providers fail to fetch credentials")};
  98. }
  99. }; // class ChainedProvider
  100. /**
  101. * Static credential provider.
  102. */
  103. class StaticProvider : public Provider {
  104. public:
  105. StaticProvider(std::string access_key, std::string secret_key,
  106. std::string session_token = "") {
  107. this->creds_ =
  108. Credentials{error::SUCCESS, access_key, secret_key, session_token};
  109. }
  110. Credentials Fetch() { return creds_; }
  111. }; // class StaticProvider
  112. class EnvAwsProvider : public Provider {
  113. public:
  114. EnvAwsProvider() {
  115. std::string access_key;
  116. std::string secret_key;
  117. std::string session_token;
  118. if (!utils::GetEnv(access_key, "AWS_ACCESS_KEY_ID")) {
  119. utils::GetEnv(access_key, "AWS_ACCESS_KEY");
  120. }
  121. if (!utils::GetEnv(secret_key, "AWS_SECRET_ACCESS_KEY")) {
  122. utils::GetEnv(secret_key, "AWS_SECRET_KEY");
  123. }
  124. utils::GetEnv(session_token, "AWS_SESSION_TOKEN");
  125. this->creds_ =
  126. Credentials{error::SUCCESS, access_key, secret_key, session_token};
  127. }
  128. Credentials Fetch() { return creds_; }
  129. }; // class EnvAwsProvider
  130. class EnvMinioProvider : public Provider {
  131. public:
  132. EnvMinioProvider() {
  133. std::string access_key;
  134. std::string secret_key;
  135. utils::GetEnv(access_key, "MINIO_ACCESS_KEY");
  136. utils::GetEnv(secret_key, "MINIO_SECRET_KEY");
  137. this->creds_ = Credentials{error::SUCCESS, access_key, secret_key};
  138. }
  139. Credentials Fetch() { return creds_; }
  140. }; // class EnvMinioProvider
  141. class AwsConfigProvider : public Provider {
  142. public:
  143. AwsConfigProvider(std::string filename = "", std::string profile = "") {
  144. if (filename.empty()) {
  145. if (!utils::GetEnv(filename, "AWS_SHARED_CREDENTIALS_FILE")) {
  146. filename = utils::GetHomeDir() + "/aws/credentials";
  147. }
  148. }
  149. if (profile.empty()) {
  150. if (!utils::GetEnv(profile, "AWS_PROFILE")) profile = "default";
  151. }
  152. INIReader reader(filename);
  153. if (reader.ParseError() < 0) {
  154. this->creds_ = Credentials{error::Error("unable to read " + filename)};
  155. } else {
  156. this->creds_ = Credentials{
  157. error::SUCCESS, reader.Get(profile, "aws_access_key_id", ""),
  158. reader.Get(profile, "aws_secret_access_key", ""),
  159. reader.Get(profile, "aws_session_token", "")};
  160. }
  161. }
  162. Credentials Fetch() { return creds_; }
  163. }; // class AwsConfigProvider
  164. class MinioClientConfigProvider : public Provider {
  165. public:
  166. MinioClientConfigProvider(std::string filename = "", std::string alias = "") {
  167. if (filename.empty()) filename = utils::GetHomeDir() + "/.mc/config.json";
  168. if (alias.empty()) {
  169. if (!utils::GetEnv(alias, "MINIO_ALIAS")) alias = "s3";
  170. }
  171. std::ifstream ifs(filename);
  172. nlohmann::json json = nlohmann::json::parse(ifs);
  173. ifs.close();
  174. nlohmann::json aliases;
  175. if (json.contains("hosts")) {
  176. aliases = json["hosts"];
  177. } else if (json.contains("aliases")) {
  178. aliases = json["aliases"];
  179. } else {
  180. this->creds_ = Credentials{
  181. error::Error("invalid configuration in file " + filename)};
  182. return;
  183. }
  184. if (!aliases.contains(alias)) {
  185. this->creds_ = Credentials{error::Error(
  186. "alias " + alias + " not found in MinIO client configuration file " +
  187. filename)};
  188. return;
  189. }
  190. this->creds_ = Credentials{error::SUCCESS, aliases[alias]["accessKey"],
  191. aliases[alias]["secretKey"]};
  192. }
  193. Credentials Fetch() { return creds_; }
  194. }; // class MinioClientConfigProvider
  195. class AssumeRoleProvider : public Provider {
  196. private:
  197. http::Url sts_endpoint_;
  198. std::string access_key_;
  199. std::string secret_key_;
  200. std::string region_;
  201. std::string body_;
  202. std::string content_sha256_;
  203. public:
  204. AssumeRoleProvider(http::Url sts_endpoint, std::string access_key,
  205. std::string secret_key, unsigned int duration_seconds = 0,
  206. std::string policy = "", std::string region = "",
  207. std::string role_arn = "",
  208. std::string role_session_name = "",
  209. std::string external_id = "") {
  210. this->sts_endpoint_ = sts_endpoint;
  211. this->access_key_ = access_key;
  212. this->secret_key_ = secret_key;
  213. this->region_ = region;
  214. if (duration_seconds < DEFAULT_DURATION_SECONDS) {
  215. duration_seconds = DEFAULT_DURATION_SECONDS;
  216. }
  217. utils::Multimap map;
  218. map.Add("Action", "AssumeRole");
  219. map.Add("Version", "2011-06-15");
  220. map.Add("DurationSeconds", std::to_string(duration_seconds));
  221. if (!role_arn.empty()) map.Add("RoleArn", role_arn);
  222. if (!role_session_name.empty()) {
  223. map.Add("RoleSessionName", role_session_name);
  224. }
  225. if (!policy.empty()) map.Add("Policy", policy);
  226. if (!external_id.empty()) map.Add("ExternalId", external_id);
  227. this->body_ = map.ToQueryString();
  228. this->content_sha256_ = utils::Sha256Hash(body_);
  229. }
  230. Credentials Fetch() {
  231. if (err_) return Credentials{err_};
  232. if (creds_) return creds_;
  233. utils::Time date = utils::Time::Now();
  234. utils::Multimap headers;
  235. headers.Add("Content-Type", "application/x-www-form-urlencoded");
  236. headers.Add("Host", sts_endpoint_.host);
  237. headers.Add("X-Amz-Date", date.ToAmzDate());
  238. http::Method method = http::Method::kPost;
  239. signer::SignV4STS(method, sts_endpoint_.path, region_, headers,
  240. utils::Multimap(), access_key_, secret_key_,
  241. content_sha256_, date);
  242. http::Request req(method, sts_endpoint_);
  243. req.headers = headers;
  244. req.body = body_;
  245. http::Response resp = req.Execute();
  246. if (!resp) {
  247. creds_ = Credentials{resp.Error()};
  248. } else {
  249. creds_ = Credentials::ParseXML(resp.body, "AssumeRoleResult");
  250. }
  251. return creds_;
  252. }
  253. }; // class AssumeRoleProvider
  254. class WebIdentityClientGrantsProvider : public Provider {
  255. private:
  256. JwtFunction jwtfunc_ = NULL;
  257. http::Url sts_endpoint_;
  258. unsigned int duration_seconds_ = 0;
  259. std::string policy_;
  260. std::string role_arn_;
  261. std::string role_session_name_;
  262. public:
  263. WebIdentityClientGrantsProvider(JwtFunction jwtfunc, http::Url sts_endpoint,
  264. unsigned int duration_seconds = 0,
  265. std::string policy = "",
  266. std::string role_arn = "",
  267. std::string role_session_name = "") {
  268. this->jwtfunc_ = jwtfunc;
  269. this->sts_endpoint_ = sts_endpoint;
  270. this->duration_seconds_ = duration_seconds;
  271. this->policy_ = policy;
  272. this->role_arn_ = role_arn;
  273. this->role_session_name_ = role_session_name;
  274. }
  275. virtual bool IsWebIdentity() = 0;
  276. unsigned int getDurationSeconds(unsigned int expiry) {
  277. if (duration_seconds_) expiry = duration_seconds_;
  278. if (expiry > MAX_DURATION_SECONDS) return MAX_DURATION_SECONDS;
  279. if (expiry == 0) return expiry;
  280. if (expiry < MIN_DURATION_SECONDS) return MIN_DURATION_SECONDS;
  281. return expiry;
  282. }
  283. Credentials Fetch() {
  284. if (creds_) return creds_;
  285. Jwt jwt = jwtfunc_();
  286. utils::Multimap map;
  287. map.Add("Version", "2011-06-15");
  288. unsigned int duration_seconds = getDurationSeconds(jwt.expiry);
  289. if (duration_seconds) {
  290. map.Add("DurationSeconds", std::to_string(duration_seconds));
  291. }
  292. if (!policy_.empty()) map.Add("Policy", policy_);
  293. if (IsWebIdentity()) {
  294. map.Add("Action", "AssumeRoleWithWebIdentity");
  295. map.Add("WebIdentityToken", jwt.token);
  296. if (!role_arn_.empty()) {
  297. map.Add("RoleArn", role_arn_);
  298. if (!role_session_name_.empty()) {
  299. map.Add("RoleSessionName", role_session_name_);
  300. } else {
  301. map.Add("RoleSessionName", utils::Time::Now().ToISO8601UTC());
  302. }
  303. }
  304. } else {
  305. map.Add("Action", "AssumeRoleWithClientGrants");
  306. map.Add("Token", jwt.token);
  307. }
  308. http::Url url = sts_endpoint_;
  309. url.query_string = map.ToQueryString();
  310. http::Request req(http::Method::kPost, url);
  311. http::Response resp = req.Execute();
  312. if (!resp) {
  313. creds_ = Credentials{resp.Error()};
  314. } else {
  315. creds_ = Credentials::ParseXML(
  316. resp.body, IsWebIdentity() ? "AssumeRoleWithWebIdentityResult"
  317. : "AssumeRoleWithClientGrantsResult");
  318. }
  319. return creds_;
  320. }
  321. }; // class WebIdentityClientGrantsProvider
  322. class ClientGrantsProvider : public WebIdentityClientGrantsProvider {
  323. public:
  324. ClientGrantsProvider(JwtFunction jwtfunc, http::Url sts_endpoint,
  325. unsigned int duration_seconds = 0,
  326. std::string policy = "", std::string role_arn = "",
  327. std::string role_session_name = "")
  328. : WebIdentityClientGrantsProvider(jwtfunc, sts_endpoint, duration_seconds,
  329. policy, role_arn, role_session_name) {}
  330. bool IsWebIdentity() { return false; }
  331. }; // class ClientGrantsProvider
  332. class WebIdentityProvider : public WebIdentityClientGrantsProvider {
  333. public:
  334. WebIdentityProvider(JwtFunction jwtfunc, http::Url sts_endpoint,
  335. unsigned int duration_seconds = 0,
  336. std::string policy = "", std::string role_arn = "",
  337. std::string role_session_name = "")
  338. : WebIdentityClientGrantsProvider(jwtfunc, sts_endpoint, duration_seconds,
  339. policy, role_arn, role_session_name) {}
  340. bool IsWebIdentity() { return true; }
  341. }; // class WebIdentityProvider
  342. class IamAwsProvider : public Provider {
  343. private:
  344. http::Url custom_endpoint_;
  345. std::string token_file_;
  346. std::string aws_region_;
  347. std::string role_arn_;
  348. std::string role_session_name_;
  349. std::string relative_uri_;
  350. std::string full_uri_;
  351. Credentials fetch(http::Url url) {
  352. http::Request req(http::Method::kGet, url);
  353. http::Response resp = req.Execute();
  354. if (!resp) return Credentials{resp.Error()};
  355. nlohmann::json json = nlohmann::json::parse(resp.body);
  356. std::string code = json.value("Code", "Success");
  357. if (code != "Success") {
  358. return Credentials{error::Error(url.String() + " failed with code " +
  359. code + " and message " +
  360. json.value("Message", ""))};
  361. }
  362. std::string expiration = json["Expiration"];
  363. return Credentials{error::SUCCESS, json["AccessKeyId"],
  364. json["SecretAccessKey"], json["Token"],
  365. utils::Time::FromISO8601UTC(expiration.c_str())};
  366. }
  367. error::Error getRoleName(std::string& role_name, http::Url url) {
  368. http::Request req(http::Method::kGet, url);
  369. http::Response resp = req.Execute();
  370. if (!resp) return resp.Error();
  371. std::list<std::string> role_names;
  372. std::string lines = resp.body;
  373. size_t pos;
  374. while ((pos = lines.find("\n")) != std::string::npos) {
  375. role_names.push_back(lines.substr(0, pos));
  376. lines.erase(0, pos + 1);
  377. }
  378. if (!lines.empty()) role_names.push_back(lines);
  379. if (role_names.empty()) {
  380. const char* err;
  381. err = "no IAM roles attached to EC2 service ";
  382. err += url;
  383. return error::Error(err);
  384. }
  385. role_name = utils::Trim(role_names.front(), '\r');
  386. return error::SUCCESS;
  387. }
  388. public:
  389. IamAwsProvider(http::Url custom_endpoint = http::Url()) {
  390. this->custom_endpoint_ = custom_endpoint;
  391. utils::GetEnv(this->token_file_, "AWS_WEB_IDENTITY_TOKEN_FILE");
  392. utils::GetEnv(this->aws_region_, "AWS_REGION");
  393. utils::GetEnv(this->role_arn_, "AWS_ROLE_ARN");
  394. utils::GetEnv(this->role_session_name_, "AWS_ROLE_SESSION_NAME");
  395. utils::GetEnv(this->relative_uri_,
  396. "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI");
  397. if (!this->relative_uri_.empty() && this->relative_uri_.front() != '/') {
  398. this->relative_uri_ = "/" + this->relative_uri_;
  399. }
  400. utils::GetEnv(this->full_uri_, "AWS_CONTAINER_CREDENTIALS_FULL_URI");
  401. }
  402. Credentials Fetch() {
  403. if (creds_) return creds_;
  404. http::Url url = custom_endpoint_;
  405. if (!token_file_.empty()) {
  406. if (!url) {
  407. url.https = true;
  408. url.host = "sts.amazonaws.com";
  409. if (!aws_region_.empty()) {
  410. url.host = "sts." + aws_region_ + ".amazonaws.com";
  411. }
  412. }
  413. WebIdentityProvider provider = WebIdentityProvider(
  414. [&token_file = token_file_]() -> Jwt {
  415. std::ifstream ifs(token_file);
  416. nlohmann::json json = nlohmann::json::parse(ifs);
  417. ifs.close();
  418. return Jwt{json["access_token"], json["expires_in"]};
  419. },
  420. url, 0, "", role_arn_, role_session_name_);
  421. creds_ = provider.Fetch();
  422. return creds_;
  423. }
  424. if (!relative_uri_.empty()) {
  425. if (!url) {
  426. url.https = true;
  427. url.host = "169.254.170.2";
  428. url.path = relative_uri_;
  429. }
  430. } else if (!full_uri_.empty()) {
  431. if (!url) url = http::Url::Parse(full_uri_);
  432. if (error::Error err = checkLoopbackHost(url.host)) {
  433. creds_ = Credentials{err};
  434. return creds_;
  435. }
  436. } else {
  437. if (!url) {
  438. url.https = true;
  439. url.host = "169.254.169.254";
  440. url.path = "/latest/meta-data/iam/security-credentials/";
  441. }
  442. std::string role_name;
  443. if (error::Error err = getRoleName(role_name, url)) {
  444. creds_ = Credentials{err};
  445. return creds_;
  446. }
  447. url.path += "/" + role_name;
  448. }
  449. creds_ = fetch(url);
  450. return creds_;
  451. }
  452. }; // class IamAwsProvider
  453. class LdapIdentityProvider : public Provider {
  454. private:
  455. http::Url sts_endpoint_;
  456. public:
  457. LdapIdentityProvider(http::Url sts_endpoint, std::string ldap_username,
  458. std::string ldap_password) {
  459. this->sts_endpoint_ = sts_endpoint;
  460. utils::Multimap map;
  461. map.Add("Action", "AssumeRoleWithLDAPIdentity");
  462. map.Add("Version", "2011-06-15");
  463. map.Add("LDAPUsername", ldap_username);
  464. map.Add("LDAPPassword", ldap_password);
  465. this->sts_endpoint_.query_string = map.ToQueryString();
  466. }
  467. Credentials Fetch() {
  468. if (creds_) return creds_;
  469. http::Request req(http::Method::kPost, sts_endpoint_);
  470. http::Response resp = req.Execute();
  471. if (!resp) return Credentials{resp.Error()};
  472. creds_ =
  473. Credentials::ParseXML(resp.body, "AssumeRoleWithLDAPIdentityResult");
  474. return creds_;
  475. }
  476. }; // class LdapIdentityProvider
  477. struct CertificateIdentityProvider : public Provider {
  478. private:
  479. http::Url sts_endpoint_;
  480. std::string key_file_;
  481. std::string cert_file_;
  482. std::string ssl_cert_file_;
  483. public:
  484. CertificateIdentityProvider(http::Url sts_endpoint, std::string key_file,
  485. std::string cert_file,
  486. std::string ssl_cert_file = "",
  487. unsigned int duration_seconds = 0) {
  488. if (!sts_endpoint.https) {
  489. this->err_ = error::Error("sts endpoint scheme must be HTTPS");
  490. return;
  491. }
  492. if (key_file.empty() || cert_file.empty()) {
  493. this->err_ = error::Error("client key and certificate must be provided");
  494. return;
  495. }
  496. unsigned int expiry = duration_seconds;
  497. if (duration_seconds < DEFAULT_DURATION_SECONDS) {
  498. expiry = DEFAULT_DURATION_SECONDS;
  499. }
  500. utils::Multimap map;
  501. map.Add("Action", "AssumeRoleWithCertificate");
  502. map.Add("Version", "2011-06-15");
  503. map.Add("DurationSeconds", std::to_string(expiry));
  504. sts_endpoint_ = sts_endpoint;
  505. sts_endpoint_.query_string = map.ToQueryString();
  506. key_file_ = key_file;
  507. cert_file_ = cert_file;
  508. ssl_cert_file_ = ssl_cert_file;
  509. }
  510. Credentials Fetch() {
  511. if (err_) return Credentials{err_};
  512. if (creds_) return creds_;
  513. http::Request req(http::Method::kPost, sts_endpoint_);
  514. req.ssl_cert_file = ssl_cert_file_;
  515. req.key_file = key_file_;
  516. req.cert_file = cert_file_;
  517. http::Response resp = req.Execute();
  518. if (!resp) return Credentials{resp.Error()};
  519. creds_ =
  520. Credentials::ParseXML(resp.body, "AssumeRoleWithCertificateResult");
  521. return creds_;
  522. }
  523. }; // struct CertificateIdentityProvider
  524. } // namespace creds
  525. } // namespace minio
  526. #endif // #ifndef _MINIO_CREDS_PROVIDERS_H