summaryrefslogtreecommitdiffstats
path: root/storage/maria/libmarias3/src/assume_role.c
diff options
context:
space:
mode:
Diffstat (limited to 'storage/maria/libmarias3/src/assume_role.c')
-rw-r--r--storage/maria/libmarias3/src/assume_role.c703
1 files changed, 703 insertions, 0 deletions
diff --git a/storage/maria/libmarias3/src/assume_role.c b/storage/maria/libmarias3/src/assume_role.c
new file mode 100644
index 00000000..255b1eca
--- /dev/null
+++ b/storage/maria/libmarias3/src/assume_role.c
@@ -0,0 +1,703 @@
+/* vim:expandtab:shiftwidth=2:tabstop=2:smarttab:
+ * Copyright 2020 MariaDB Corporation Ab. All rights reserved.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+ * MA 02110-1301 USA
+ */
+
+#include "config.h"
+#include "common.h"
+#include "sha256.h"
+
+#include <math.h>
+
+const char *default_iam_domain = "iam.amazonaws.com";
+const char *default_sts_domain = "sts.amazonaws.com";
+const char *iam_request_region = "us-east-1";
+
+static void set_error(ms3_st *ms3, const char *error)
+{
+ ms3_cfree(ms3->last_error);
+
+ if (!error)
+ {
+ ms3->last_error = NULL;
+ return;
+ }
+
+ ms3->last_error = ms3_cstrdup(error);
+}
+
+static void set_error_nocopy(ms3_st *ms3, char *error)
+{
+ ms3_cfree(ms3->last_error);
+
+ if (!error)
+ {
+ ms3->last_error = NULL;
+ return;
+ }
+
+ ms3->last_error = error;
+}
+
+static size_t header_callback(char *buffer, size_t size,
+ size_t nitems, void *userdata)
+{
+ ms3debug("%.*s\n", (int)(nitems * size), buffer);
+
+ if (userdata)
+ {
+ // HEAD request
+ if (!strncasecmp(buffer, "Last-Modified", 13))
+ {
+ ms3_status_st *status = (ms3_status_st *) userdata;
+ // Date/time, format: Fri, 15 Mar 2019 16:58:54 GMT
+ struct tm ttmp = {0};
+ strptime(buffer + 15, "%a, %d %b %Y %H:%M:%S %Z", &ttmp);
+ status->created = mktime(&ttmp);
+ }
+ else if (!strncasecmp(buffer, "Content-Length", 14))
+ {
+ ms3_status_st *status = (ms3_status_st *) userdata;
+ // Length
+ status->length = strtoull(buffer + 16, NULL, 10);
+ }
+ }
+
+ return nitems * size;
+}
+
+static size_t body_callback(void *buffer, size_t size,
+ size_t nitems, void *userdata)
+{
+ uint8_t *ptr;
+ size_t realsize = nitems * size;
+
+ struct memory_buffer_st *mem = (struct memory_buffer_st *)userdata;
+
+ if (realsize + mem->length >= mem->alloced)
+ {
+ size_t additional_size = mem->buffer_chunk_size;
+
+ if (realsize >= mem->buffer_chunk_size)
+ {
+ additional_size = (ceil((double)realsize / (double)mem->buffer_chunk_size) + 1)
+ * mem->buffer_chunk_size;
+ }
+
+ ptr = (uint8_t *)ms3_crealloc(mem->data, mem->alloced + additional_size);
+
+ if (!ptr)
+ {
+ ms3debug("Curl response OOM");
+ return 0;
+ }
+
+ mem->alloced += additional_size;
+ mem->data = ptr;
+ }
+
+ memcpy(&(mem->data[mem->length]), buffer, realsize);
+ mem->length += realsize;
+ mem->data[mem->length] = '\0';
+
+ ms3debug("Read %zu bytes, buffer %zu bytes", realsize, mem->length);
+// ms3debug("Data: %s", (char*)buffer);
+ return nitems * size;
+}
+
+static uint8_t build_assume_role_request_uri(CURL *curl, const char *base_domain, const char *query, bool use_http)
+{
+ char uri_buffer[MAX_URI_LENGTH];
+ const char *domain;
+ const uint8_t path_parts = 10; // "https://" + "." + "/"
+ const char *http_protocol = "http";
+ const char *https_protocol = "https";
+ const char *protocol;
+
+ if (base_domain)
+ {
+ domain = base_domain;
+ }
+ else
+ {
+ domain = default_sts_domain;
+ }
+
+ if (use_http)
+ {
+ protocol = http_protocol;
+ }
+ else
+ {
+ protocol = https_protocol;
+ }
+
+ if (query)
+ {
+ if (path_parts + strlen(domain) + strlen(query) >= MAX_URI_LENGTH - 1)
+ {
+ return MS3_ERR_URI_TOO_LONG;
+ }
+
+ snprintf(uri_buffer, MAX_URI_LENGTH - 1, "%s://%s/?%s", protocol,
+ domain, query);
+ }
+ else
+ {
+ return MS3_ERR_PARAMETER;
+ }
+
+ ms3debug("URI: %s", uri_buffer);
+ curl_easy_setopt(curl, CURLOPT_URL, uri_buffer);
+ return 0;
+}
+
+static char *generate_assume_role_query(CURL *curl, const char *action, size_t role_duration,
+ const char *version, const char *role_session_name, const char *role_arn,
+ const char *continuation, char *query_buffer)
+{
+ size_t query_buffer_length = 0;
+ char *encoded;
+ query_buffer[0] = '\0';
+
+ if (action)
+ {
+ encoded = curl_easy_escape(curl, action, (int)strlen(action));
+ query_buffer_length = strlen(query_buffer);
+ if (query_buffer_length)
+ {
+ snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
+ "&Action=%s", encoded);
+ }
+ else
+ {
+ snprintf(query_buffer, 3072, "Action=%s", encoded);
+ }
+ curl_free(encoded);
+ }
+ if (role_duration >= 900 && role_duration <= 43200)
+ {
+ query_buffer_length = strlen(query_buffer);
+ if (query_buffer_length)
+ {
+ snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
+ "&DurationSeconds=%zu", role_duration);
+ }
+ else
+ {
+ snprintf(query_buffer, 3072, "DurationSeconds=%zu", role_duration);
+ }
+ }
+ if (continuation)
+ {
+ encoded = curl_easy_escape(curl, continuation, (int)strlen(continuation));
+ query_buffer_length = strlen(query_buffer);
+ if (query_buffer_length)
+ {
+ snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
+ "&Marker=%s", encoded);
+ }
+ else
+ {
+ snprintf(query_buffer, 3072, "Marker=%s", encoded);
+ }
+ curl_free(encoded);
+ }
+ if (role_arn)
+ {
+ encoded = curl_easy_escape(curl, role_arn, (int)strlen(role_arn));
+ query_buffer_length = strlen(query_buffer);
+ if (query_buffer_length)
+ {
+ snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
+ "&RoleArn=%s", encoded);
+ }
+ else
+ {
+ snprintf(query_buffer, 3072, "RoleArn=%s", encoded);
+ }
+ curl_free(encoded);
+ }
+ if (role_session_name)
+ {
+ encoded = curl_easy_escape(curl, role_session_name, (int)strlen(role_session_name));
+ query_buffer_length = strlen(query_buffer);
+ if (query_buffer_length)
+ {
+ snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
+ "&RoleSessionName=%s", encoded);
+ }
+ else
+ {
+ snprintf(query_buffer, 3072, "RoleSessionName=%s", encoded);
+ }
+ curl_free(encoded);
+ }
+ if (version)
+ {
+ encoded = curl_easy_escape(curl, version, (int)strlen(version));
+ query_buffer_length = strlen(query_buffer);
+ if (query_buffer_length)
+ {
+ snprintf(query_buffer + query_buffer_length, 3072 - query_buffer_length,
+ "&Version=%s", encoded);
+ }
+ else
+ {
+ snprintf(query_buffer, 3072, "Version=%s", encoded);
+ }
+ curl_free(encoded);
+ }
+
+ return query_buffer;
+}
+
+
+static uint8_t generate_assume_role_request_hash(uri_method_t method, const char *query, char *post_hash,
+ struct curl_slist *headers, char *return_hash)
+{
+ char signing_data[3072];
+ size_t pos = 0;
+ uint8_t sha256hash[32]; // SHA_256 binary length
+ uint8_t hash_pos = 0;
+ uint8_t i;
+ struct curl_slist *current_header = headers;
+
+ // Method first
+ switch (method)
+ {
+ case MS3_GET:
+ {
+ sprintf(signing_data, "GET\n");
+ pos += 4;
+ break;
+ }
+
+ case MS3_HEAD:
+ {
+ sprintf(signing_data, "HEAD\n");
+ pos += 5;
+ break;
+ }
+
+ case MS3_PUT:
+ {
+ sprintf(signing_data, "PUT\n");
+ pos += 4;
+ break;
+ }
+
+ case MS3_DELETE:
+ {
+ sprintf(signing_data, "DELETE\n");
+ pos += 7;
+ break;
+ }
+
+ default:
+ {
+ ms3debug("Bad method detected");
+ return MS3_ERR_IMPOSSIBLE;
+ }
+ }
+
+ // URL query (if exists)
+ if (query)
+ {
+ snprintf(signing_data + pos, sizeof(signing_data) - pos, "/\n%s\n", query);
+ pos += strlen(query) + 3;
+ }
+ else
+ {
+ sprintf(signing_data + pos, "\n");
+ pos++;
+ }
+
+ do
+ {
+ snprintf(signing_data + pos, sizeof(signing_data) - pos, "%s\n",
+ current_header->data);
+ pos += strlen(current_header->data) + 1;
+ }
+ while ((current_header = current_header->next));
+
+ // List if header names
+ // The newline between headers and this is important
+ snprintf(signing_data + pos, sizeof(signing_data) - pos,
+ "\nhost;x-amz-content-sha256;x-amz-date\n");
+ pos += 38;
+
+ // Hash of post data (can be hash of empty)
+ snprintf(signing_data + pos, sizeof(signing_data) - pos, "%.*s", 64, post_hash);
+ //pos+= 64;
+
+ // Hash all of the above
+ sha256((uint8_t *)signing_data, strlen(signing_data), (uint8_t *)sha256hash);
+
+ for (i = 0; i < 32; i++)
+ {
+ sprintf(return_hash + hash_pos, "%.2x", sha256hash[i]);
+ hash_pos += 2;
+ }
+
+ ms3debug("Signature data: %s", signing_data);
+ ms3debug("Signature: %.*s", 64, return_hash);
+
+ return 0;
+}
+
+static uint8_t
+build_assume_role_request_headers(CURL *curl, struct curl_slist **head,
+ const char *base_domain,
+ const char* endpoint_type,
+ const char *region, const char *key,
+ const char *secret, const char *query,
+ uri_method_t method,
+ struct put_buffer_st *post_data)
+{
+ uint8_t ret = 0;
+ time_t now;
+ struct tm tmp_tm;
+ char headerbuf[3072];
+ char secrethead[45];
+ char date[9];
+ char sha256hash[65];
+ char post_hash[65];
+ uint8_t tmp_hash[32];
+ // Alternate between these two so hmac doesn't overwrite itself
+ uint8_t hmac_hash[32];
+ uint8_t hmac_hash2[32];
+ uint8_t hash_pos = 0;
+ const char *domain;
+ const char *type;
+ struct curl_slist *headers = NULL;
+ uint8_t offset;
+ uint8_t i;
+ struct curl_slist *current_header;
+
+ // Host header
+ if (base_domain)
+ {
+ domain = base_domain;
+ }
+ else
+ {
+ domain = default_sts_domain;
+ }
+
+ if (endpoint_type)
+ {
+ type = endpoint_type;
+ }
+ else
+ {
+ type = "sts";
+ }
+
+ snprintf(headerbuf, sizeof(headerbuf), "host:%s", domain);
+
+ headers = curl_slist_append(headers, headerbuf);
+ *head = headers;
+
+ // Hash post data
+ sha256(post_data->data, post_data->length, tmp_hash);
+
+ for (i = 0; i < 32; i++)
+ {
+ sprintf(post_hash + hash_pos, "%.2x", tmp_hash[i]);
+ hash_pos += 2;
+ }
+
+ snprintf(headerbuf, sizeof(headerbuf), "x-amz-content-sha256:%.*s", 64,
+ post_hash);
+ headers = curl_slist_append(headers, headerbuf);
+
+ // Date/time header
+ time(&now);
+ snprintf(headerbuf, sizeof(headerbuf), "x-amz-date:");
+ offset = strlen(headerbuf);
+ gmtime_r(&now, &tmp_tm);
+ strftime(headerbuf + offset, sizeof(headerbuf) - offset, "%Y%m%dT%H%M%SZ",
+ &tmp_tm);
+ headers = curl_slist_append(headers, headerbuf);
+
+ // Builds the request hash
+ ret = generate_assume_role_request_hash(method, query, post_hash, headers, sha256hash);
+
+ if (ret)
+ {
+ return ret;
+ }
+
+ // User signing key hash
+ // Date hashed using AWS4:secret_key
+ snprintf(secrethead, sizeof(secrethead), "AWS4%.*s", 40, secret);
+ strftime(headerbuf, sizeof(headerbuf), "%Y%m%d", &tmp_tm);
+ hmac_sha256((uint8_t *)secrethead, strlen(secrethead), (uint8_t *)headerbuf,
+ strlen(headerbuf), hmac_hash);
+
+ // Region signed by above key
+ hmac_sha256(hmac_hash, 32, (uint8_t *)region, strlen(region),
+ hmac_hash2);
+
+ // Service signed by above key
+ hmac_sha256(hmac_hash2, 32, (uint8_t *)type, strlen(type),
+ hmac_hash);
+
+ // Request version signed by above key (always "aws4_request")
+ sprintf(headerbuf, "aws4_request");
+ hmac_sha256(hmac_hash, 32, (uint8_t *)headerbuf, strlen(headerbuf),
+ hmac_hash2);
+
+ // Sign everything with the key
+ snprintf(headerbuf, sizeof(headerbuf), "AWS4-HMAC-SHA256\n");
+ offset = strlen(headerbuf);
+ strftime(headerbuf + offset, sizeof(headerbuf) - offset, "%Y%m%dT%H%M%SZ\n",
+ &tmp_tm);
+ offset = strlen(headerbuf);
+ strftime(date, 9, "%Y%m%d", &tmp_tm);
+ snprintf(headerbuf + offset, sizeof(headerbuf) - offset,
+ "%.*s/%s/%s/aws4_request\n%.*s", 8, date, region, type, 64, sha256hash);
+ ms3debug("Data to sign: %s", headerbuf);
+ hmac_sha256(hmac_hash2, 32, (uint8_t *)headerbuf, strlen(headerbuf),
+ hmac_hash);
+
+ hash_pos = 0;
+
+ for (i = 0; i < 32; i++)
+ {
+ sprintf(sha256hash + hash_pos, "%.2x", hmac_hash[i]);
+ hash_pos += 2;
+ }
+
+ // Make auth header
+ snprintf(headerbuf, sizeof(headerbuf),
+ "Authorization: AWS4-HMAC-SHA256 Credential=%s/%s/%s/%s/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date, Signature=%s",
+ key, date, region, type, sha256hash);
+
+ headers = curl_slist_append(headers, headerbuf);
+
+ // Disable this header or PUT will barf with a 501
+ sprintf(headerbuf, "Transfer-Encoding:");
+ headers = curl_slist_append(headers, headerbuf);
+
+ current_header = headers;
+
+ do
+ {
+ ms3debug("Header: %s", current_header->data);
+ }
+ while ((current_header = current_header->next));
+
+ curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
+
+ return 0;
+}
+
+uint8_t execute_assume_role_request(ms3_st *ms3, command_t cmd,
+ const uint8_t *data, size_t data_size,
+ char *continuation)
+{
+ CURL *curl = NULL;
+ struct curl_slist *headers = NULL;
+ uint8_t res = 0;
+ struct memory_buffer_st mem;
+ uri_method_t method;
+ char *query = NULL;
+ struct put_buffer_st post_data;
+ CURLcode curl_res;
+ long response_code = 0;
+ char* endpoint = NULL;
+ const char* region = iam_request_region;
+ char endpoint_type[8];
+
+ mem.data = NULL;
+ mem.length = 0;
+ mem.alloced = 1;
+ mem.buffer_chunk_size = ms3->buffer_chunk_size;
+
+ post_data.data = (uint8_t *) data;
+ post_data.length = data_size;
+ post_data.offset = 0;
+
+ curl = ms3->curl;
+
+ if (!ms3->first_run)
+ {
+ curl_easy_reset(curl);
+ }
+ else
+ {
+ ms3->first_run = false;
+ }
+
+ if (cmd == MS3_CMD_ASSUME_ROLE)
+ {
+ query = generate_assume_role_query(curl, "AssumeRole", ms3->role_session_duration, "2011-06-15", "libmariaS3",
+ ms3->iam_role_arn, continuation, ms3->query_buffer);
+ endpoint = ms3->sts_endpoint;
+ region = ms3->sts_region;
+ sprintf(endpoint_type, "sts");
+ method = MS3_GET;
+ }
+ else if (cmd == MS3_CMD_LIST_ROLE)
+ {
+ query = generate_assume_role_query(curl, "ListRoles", 0, "2010-05-08", NULL, NULL, continuation, ms3->query_buffer);
+ endpoint = ms3->iam_endpoint;
+ sprintf(endpoint_type, "iam");
+ method = MS3_GET;
+ }
+
+ res = build_assume_role_request_uri(curl, endpoint, query, ms3->use_http);
+
+ if (res)
+ {
+ return res;
+ }
+
+ res = build_assume_role_request_headers(curl, &headers, endpoint,
+ endpoint_type, region,
+ ms3->s3key, ms3->s3secret, query,
+ method, &post_data);
+
+ if (res)
+ {
+ ms3_cfree(mem.data);
+ curl_slist_free_all(headers);
+
+ return res;
+ }
+
+ if (ms3->disable_verification)
+ {
+ ms3debug("Disabling SSL verification");
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0);
+ curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0);
+ }
+
+ curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, header_callback);
+ curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, body_callback);
+ curl_easy_setopt(curl, CURLOPT_WRITEDATA, (void *)&mem);
+ curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);
+ curl_res = curl_easy_perform(curl);
+
+ if (curl_res != CURLE_OK)
+ {
+ ms3debug("Curl error: %s", curl_easy_strerror(curl_res));
+ set_error(ms3, curl_easy_strerror(curl_res));
+ ms3_cfree(mem.data);
+ curl_slist_free_all(headers);
+
+ return MS3_ERR_REQUEST_ERROR;
+ }
+
+ curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &response_code);
+ ms3debug("Response code: %ld", response_code);
+
+ if (response_code == 404)
+ {
+ char *message = parse_error_message((char *)mem.data, mem.length);
+
+ if (message)
+ {
+ ms3debug("Response message: %s", message);
+ }
+
+ set_error_nocopy(ms3, message);
+ res = MS3_ERR_NOT_FOUND;
+ }
+ else if (response_code == 403)
+ {
+ char *message = parse_error_message((char *)mem.data, mem.length);
+
+ if (message)
+ {
+ ms3debug("Response message: %s", message);
+ }
+
+ set_error_nocopy(ms3, message);
+ res = MS3_ERR_AUTH;
+ }
+ else if (response_code >= 400)
+ {
+ char *message = parse_error_message((char *)mem.data, mem.length);
+
+ if (message)
+ {
+ ms3debug("Response message: %s", message);
+ }
+
+ set_error_nocopy(ms3, message);
+ res = MS3_ERR_SERVER;
+ }
+
+ switch (cmd)
+ {
+ case MS3_CMD_LIST_ROLE:
+ {
+ char *cont = NULL;
+ res = parse_role_list_response((const char *)mem.data, mem.length, ms3->iam_role ,ms3->iam_role_arn, &cont);
+
+ if (cont && res)
+ {
+ res = execute_assume_role_request(ms3, cmd, data, data_size, cont);
+ if (res)
+ {
+ ms3_cfree(cont);
+ ms3_cfree(mem.data);
+ curl_slist_free_all(headers);
+ return res;
+ }
+ ms3_cfree(cont);
+ }
+
+ ms3_cfree(mem.data);
+ break;
+ }
+
+ case MS3_CMD_ASSUME_ROLE:
+ {
+ if (res)
+ {
+ ms3_cfree(mem.data);
+ curl_slist_free_all(headers);
+ return res;
+ }
+ res = parse_assume_role_response((const char *)mem.data, mem.length, ms3->role_key, ms3->role_secret, ms3->role_session_token);
+ ms3_cfree(mem.data);
+ break;
+ }
+
+ case MS3_CMD_LIST:
+ case MS3_CMD_LIST_RECURSIVE:
+ case MS3_CMD_PUT:
+ case MS3_CMD_GET:
+ case MS3_CMD_DELETE:
+ case MS3_CMD_HEAD:
+ case MS3_CMD_COPY:
+ default:
+ {
+ ms3_cfree(mem.data);
+ ms3debug("Bad cmd detected");
+ res = MS3_ERR_IMPOSSIBLE;
+ }
+ }
+
+ curl_slist_free_all(headers);
+
+ return res;
+}