Skip to content

Commit ca3662f

Browse files
committed
Add support for SSE KMS on S3 #14
1 parent 5546f3a commit ca3662f

File tree

4 files changed

+26
-0
lines changed

4 files changed

+26
-0
lines changed

extension/httpfs/create_secret_functions.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ unique_ptr<BaseSecret> CreateS3SecretFunctions::CreateSecretFunctionInternal(Cli
6161
lower_name, named_param.second.type().ToString());
6262
}
6363
secret->secret_map["use_ssl"] = Value::BOOLEAN(named_param.second.GetValue<bool>());
64+
} else if (lower_name == "kms_key_id") {
65+
secret->secret_map["kms_key_id"] = named_param.second.ToString();
6466
} else if (lower_name == "url_compatibility_mode") {
6567
if (named_param.second.type() != LogicalType::BOOLEAN) {
6668
throw InvalidInputException("Invalid type past to secret option: '%s', found '%s', expected: 'BOOLEAN'",
@@ -90,6 +92,7 @@ void CreateS3SecretFunctions::SetBaseNamedParams(CreateSecretFunction &function,
9092
function.named_parameters["endpoint"] = LogicalType::VARCHAR;
9193
function.named_parameters["url_style"] = LogicalType::VARCHAR;
9294
function.named_parameters["use_ssl"] = LogicalType::BOOLEAN;
95+
function.named_parameters["kms_key_id"] = LogicalType::VARCHAR;
9396
function.named_parameters["url_compatibility_mode"] = LogicalType::BOOLEAN;
9497

9598
if (type == "r2") {

extension/httpfs/httpfs_extension.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ static void LoadInternal(DatabaseInstance &instance) {
4747
config.AddExtensionOption("s3_endpoint", "S3 Endpoint", LogicalType::VARCHAR);
4848
config.AddExtensionOption("s3_url_style", "S3 URL style", LogicalType::VARCHAR, Value("vhost"));
4949
config.AddExtensionOption("s3_use_ssl", "S3 use SSL", LogicalType::BOOLEAN, Value(true));
50+
config.AddExtensionOption("s3_kms_key_id", "S3 KMS Key ID", LogicalType::VARCHAR);
5051
config.AddExtensionOption("s3_url_compatibility_mode", "Disable Globs and Query Parameters on S3 URLs",
5152
LogicalType::BOOLEAN, Value(false));
5253

extension/httpfs/include/s3fs.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ struct S3AuthParams {
2727
string secret_access_key;
2828
string session_token;
2929
string endpoint;
30+
string kms_key_id;
3031
string url_style;
3132
bool use_ssl = true;
3233
bool s3_url_compatibility_mode = false;
@@ -42,6 +43,7 @@ struct AWSEnvironmentCredentialsProvider {
4243
static constexpr const char *SESSION_TOKEN_ENV_VAR = "AWS_SESSION_TOKEN";
4344
static constexpr const char *DUCKDB_ENDPOINT_ENV_VAR = "DUCKDB_S3_ENDPOINT";
4445
static constexpr const char *DUCKDB_USE_SSL_ENV_VAR = "DUCKDB_S3_USE_SSL";
46+
static constexpr const char *DUCKDB_KMS_KEY_ID_ENV_VAR = "DUCKDB_S3_KMS_KEY_ID";
4547

4648
explicit AWSEnvironmentCredentialsProvider(DBConfig &config) : config(config) {};
4749

extension/httpfs/s3fs.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,11 +43,20 @@ static HeaderMap create_s3_header(string url, string query, string host, string
4343
datetime_now = StrfTimeFormat::Format(timestamp, "%Y%m%dT%H%M%SZ");
4444
}
4545

46+
// Only some S3 operations supports SSE-KMS, which this "heuristic" attempts to detect.
47+
// https://docs.aws.amazon.com/AmazonS3/latest/userguide/specifying-kms-encryption.html#sse-request-headers-kms
48+
bool use_sse_kms = auth_params.kms_key_id.length() > 0 && (method == "POST" || method == "PUT") &&
49+
query.find("uploadId") == std::string::npos;
50+
4651
res["x-amz-date"] = datetime_now;
4752
res["x-amz-content-sha256"] = payload_hash;
4853
if (auth_params.session_token.length() > 0) {
4954
res["x-amz-security-token"] = auth_params.session_token;
5055
}
56+
if (use_sse_kms) {
57+
res["x-amz-server-side-encryption"] = "aws:kms";
58+
res["x-amz-server-side-encryption-aws-kms-key-id"] = auth_params.kms_key_id;
59+
}
5160

5261
string signed_headers = "";
5362
hash_bytes canonical_request_hash;
@@ -59,6 +68,9 @@ static HeaderMap create_s3_header(string url, string query, string host, string
5968
if (auth_params.session_token.length() > 0) {
6069
signed_headers += ";x-amz-security-token";
6170
}
71+
if (use_sse_kms) {
72+
signed_headers += ";x-amz-server-side-encryption;x-amz-server-side-encryption-aws-kms-key-id";
73+
}
6274
auto canonical_request = method + "\n" + S3FileSystem::UrlEncode(url) + "\n" + query;
6375
if (content_type.length() > 0) {
6476
canonical_request += "\ncontent-type:" + content_type;
@@ -67,6 +79,10 @@ static HeaderMap create_s3_header(string url, string query, string host, string
6779
if (auth_params.session_token.length() > 0) {
6880
canonical_request += "\nx-amz-security-token:" + auth_params.session_token;
6981
}
82+
if (use_sse_kms) {
83+
canonical_request += "\nx-amz-server-side-encryption:aws:kms";
84+
canonical_request += "\nx-amz-server-side-encryption-aws-kms-key-id:" + auth_params.kms_key_id;
85+
}
7086

7187
canonical_request += "\n\n" + signed_headers + "\n" + payload_hash;
7288
sha256(canonical_request.c_str(), canonical_request.length(), canonical_request_hash);
@@ -130,6 +146,7 @@ void AWSEnvironmentCredentialsProvider::SetAll() {
130146
this->SetExtensionOptionValue("s3_session_token", SESSION_TOKEN_ENV_VAR);
131147
this->SetExtensionOptionValue("s3_endpoint", DUCKDB_ENDPOINT_ENV_VAR);
132148
this->SetExtensionOptionValue("s3_use_ssl", DUCKDB_USE_SSL_ENV_VAR);
149+
this->SetExtensionOptionValue("s3_kms_key_id", DUCKDB_KMS_KEY_ID_ENV_VAR);
133150
}
134151

135152
S3AuthParams AWSEnvironmentCredentialsProvider::CreateParams() {
@@ -141,6 +158,7 @@ S3AuthParams AWSEnvironmentCredentialsProvider::CreateParams() {
141158
params.secret_access_key = SECRET_KEY_ENV_VAR;
142159
params.session_token = SESSION_TOKEN_ENV_VAR;
143160
params.endpoint = DUCKDB_ENDPOINT_ENV_VAR;
161+
params.kms_key_id = DUCKDB_KMS_KEY_ID_ENV_VAR;
144162
params.use_ssl = DUCKDB_USE_SSL_ENV_VAR;
145163

146164
return params;
@@ -166,6 +184,7 @@ S3AuthParams S3AuthParams::ReadFrom(optional_ptr<FileOpener> opener, FileOpenerI
166184
secret_reader.TryGetSecretKeyOrSetting("session_token", "s3_session_token", result.session_token);
167185
secret_reader.TryGetSecretKeyOrSetting("region", "s3_region", result.region);
168186
secret_reader.TryGetSecretKeyOrSetting("use_ssl", "s3_use_ssl", result.use_ssl);
187+
secret_reader.TryGetSecretKeyOrSetting("kms_key_id", "s3_kms_key_id", result.kms_key_id);
169188
secret_reader.TryGetSecretKeyOrSetting("s3_url_compatibility_mode", "s3_url_compatibility_mode",
170189
result.s3_url_compatibility_mode);
171190

@@ -202,6 +221,7 @@ unique_ptr<KeyValueSecret> CreateSecret(vector<string> &prefix_paths_p, string &
202221
return_value->secret_map["endpoint"] = params.endpoint;
203222
return_value->secret_map["url_style"] = params.url_style;
204223
return_value->secret_map["use_ssl"] = params.use_ssl;
224+
return_value->secret_map["kms_key_id"] = params.kms_key_id;
205225
return_value->secret_map["s3_url_compatibility_mode"] = params.s3_url_compatibility_mode;
206226

207227
//! Set redact keys

0 commit comments

Comments
 (0)