Merge 'Extend aws_sigv4 code to suite S3 client needs' from Pavel Emelyanov

The AWS signature-generating code was moved from alternator some time ago as is. Now it's clear that in which places it should be extended to work for S3 client as well. The enhancements are

- Support UNSIGNED-PAYLOAD to omit calculating checksums for request body
- Include full URL path into the signature, not just hard-coded "/" string
- Don't check datastamp expiration if not asked for

This is a part of #13493

Closes #13535

* github.com:scylladb/scylladb:
  utils/aws: Brush up the aws_sigv4.hh header
  utils/aws: Export timepoint formatter
  utils/aws: Omit datestamp expiration checks when not needed
  utils/aws: Add canonical-uri argument
  utils/aws: Support unsigned-payload signatures
This commit is contained in:
Nadav Har'El
2023-04-18 16:33:52 +03:00
3 changed files with 29 additions and 17 deletions

View File

@@ -322,8 +322,8 @@ future<std::string> server::verify_signature(const request& req, const chunked_c
user_signature = std::move(user_signature)] (key_cache::value_ptr key_ptr) {
std::string signature;
try {
signature = utils::aws::get_signature(user, *key_ptr, std::string_view(host), req._method,
datestamp, signed_headers_str, signed_headers_map, content, region, service, "");
signature = utils::aws::get_signature(user, *key_ptr, std::string_view(host), "/", req._method,
datestamp, signed_headers_str, signed_headers_map, &content, region, service, "");
} catch (const std::exception& e) {
throw api_error::invalid_signature(e.what());
}

View File

@@ -47,7 +47,7 @@ static std::string apply_sha256(const std::vector<temporary_buffer<char>>& msg)
return to_hex(hasher.finalize());
}
static std::string format_time_point(db_clock::time_point tp) {
std::string format_time_point(db_clock::time_point tp) {
time_t time_point_repr = db_clock::to_time_t(tp);
std::string time_point_str;
time_point_str.resize(17);
@@ -74,29 +74,31 @@ void check_expiry(std::string_view signature_date) {
}
}
std::string get_signature(std::string_view access_key_id, std::string_view secret_access_key, std::string_view host, std::string_view method,
std::string_view orig_datestamp, std::string_view signed_headers_str, const std::map<std::string_view, std::string_view>& signed_headers_map,
const std::vector<temporary_buffer<char>>& body_content, std::string_view region, std::string_view service, std::string_view query_string) {
std::string get_signature(std::string_view access_key_id, std::string_view secret_access_key,
std::string_view host, std::string_view canonical_uri, std::string_view method,
std::optional<std::string_view> orig_datestamp, std::string_view signed_headers_str, const std::map<std::string_view, std::string_view>& signed_headers_map,
const std::vector<temporary_buffer<char>>* body_content, std::string_view region, std::string_view service, std::string_view query_string) {
auto amz_date_it = signed_headers_map.find("x-amz-date");
if (amz_date_it == signed_headers_map.end()) {
throw std::runtime_error("X-Amz-Date header is mandatory for signature verification");
}
std::string_view amz_date = amz_date_it->second;
check_expiry(amz_date);
std::string_view datestamp = amz_date.substr(0, 8);
if (datestamp != orig_datestamp) {
throw std::runtime_error(
format("X-Amz-Date date does not match the provided datestamp. Expected {}, got {}",
orig_datestamp, datestamp));
if (orig_datestamp) {
check_expiry(amz_date);
if (datestamp != *orig_datestamp) {
throw std::runtime_error(
format("X-Amz-Date date does not match the provided datestamp. Expected {}, got {}",
*orig_datestamp, datestamp));
}
}
std::string_view canonical_uri = "/";
std::stringstream canonical_headers;
for (const auto& header : signed_headers_map) {
canonical_headers << fmt::format("{}:{}", header.first, header.second) << '\n';
}
std::string payload_hash = apply_sha256(body_content);
std::string payload_hash = body_content != nullptr ? apply_sha256(*body_content) : "UNSIGNED-PAYLOAD";
std::string canonical_request = fmt::format("{}\n{}\n{}\n{}\n{}\n{}", method, canonical_uri, query_string, canonical_headers.str(), signed_headers_str, payload_hash);
std::string_view algorithm = "AWS4-HMAC-SHA256";

View File

@@ -6,8 +6,10 @@
* SPDX-License-Identifier: AGPL-3.0-or-later
*/
#pragma once
#include <gnutls/crypto.h>
#include "utils/hashers.hh"
#include "db_clock.hh"
// The declared below get_signature() method makes the Signature string for AWS
// authenticated requests as described in [1]. It can be used in two ways.
@@ -29,9 +31,17 @@ using hmac_sha256_digest = std::array<char, 32>;
namespace aws {
std::string get_signature(std::string_view access_key_id, std::string_view secret_access_key, std::string_view host, std::string_view method,
std::string_view orig_datestamp, std::string_view signed_headers_str, const std::map<std::string_view, std::string_view>& signed_headers_map,
const std::vector<temporary_buffer<char>>& body_content, std::string_view region, std::string_view service, std::string_view query_string);
std::string get_signature(std::string_view access_key_id, std::string_view secret_access_key,
std::string_view host, std::string_view canonical_uri, std::string_view method,
std::optional<std::string_view> orig_datestamp, std::string_view signed_headers_str, const std::map<std::string_view, std::string_view>& signed_headers_map,
const std::vector<temporary_buffer<char>>* body_content, std::string_view region, std::string_view service, std::string_view query_string);
// Convenience alias not to pass obscure nullptr argument to get_signature()
static inline constexpr std::vector<temporary_buffer<char>>* unsigned_content = nullptr;
// Same for datestamp checking
static inline auto omit_datestamp_expiration_check = std::nullopt;
std::string format_time_point(db_clock::time_point tp);
} // aws namespace
} // utils namespace