]> git.apps.os.sepia.ceph.com Git - ceph-ci.git/commitdiff
rgw: add SSE-KMS with Vault using token auth
authorSergio de Carvalho <scarvalhojr@gmail.com>
Thu, 15 Aug 2019 14:09:08 +0000 (15:09 +0100)
committerSergio de Carvalho <scarvalhojr@gmail.com>
Tue, 1 Oct 2019 18:55:23 +0000 (19:55 +0100)
Extend server-side encryption functionality in Rados Gateway to support
HashiCorp Vault as a Key Management System in addition to existing
support for OpenStack Barbican.

This is the first part of this change, supporting Vault's token-based
authentication only. Agent-based authentication as well as other
features such as Vault namespaces will be added in subsequent commits.

Note that Barbican remains the default backend for SSE-KMS
(rgw crypt s3 kms backend) to avoid breaking existing deployments.

Feature: https://tracker.ceph.com/issues/41062
Notes: https://pad.ceph.com/p/rgw_sse-kms

Implemented so far:
* Move existing SSE-KMS functions from rgw_crypt.cc to rgw_kms.cc
* Vault authentication with a token read from file
* Add new ceph.conf settings for Vault
* Document new ceph.conf settings
* Update main encryption documentation page
* Add documentation page for SSE-KMS using Vault

Signed-off-by: Andrea Baglioni <andrea.baglioni@workday.com>
Signed-off-by: Sergio de Carvalho <sergio.carvalho@workday.com>
14 files changed:
doc/radosgw/barbican.rst
doc/radosgw/config-ref.rst
doc/radosgw/encryption.rst
doc/radosgw/index.rst
doc/radosgw/vault.rst [new file with mode: 0644]
src/common/legacy_config_opts.h
src/common/options.cc
src/rgw/CMakeLists.txt
src/rgw/rgw_crypt.cc
src/rgw/rgw_kms.cc [new file with mode: 0644]
src/rgw/rgw_kms.h [new file with mode: 0644]
src/test/rgw/CMakeLists.txt
src/test/rgw/test_rgw_kms.cc [new file with mode: 0644]
src/vstart.sh

index 3a7fe6e5c184a059cb25ddadece817f17404132c..6205567076311c7e5fff1d93e3555d6cfec701a9 100644 (file)
@@ -94,9 +94,10 @@ Response::
 Configure the Ceph Object Gateway
 =================================
 
-Edit the Ceph configuration file to add information about the Barbican server
-and Keystone user::
+Edit the Ceph configuration file to enable Barbican as a KMS and add information
+about the Barbican server and Keystone user::
 
+   rgw crypt s3 kms backend = barbican
    rgw barbican url = http://barbican.example.com:9311
    rgw keystone barbican user = rgwcrypt-user
    rgw keystone barbican password = rgwcrypt-password
index 3ed09def9f357b31458f114a4cbd496efa20a8af..c21ddba4d3109d259b8c3140e5046a3bb336ac34 100644 (file)
@@ -893,6 +893,19 @@ Keystone Settings
 :Type: Boolean
 :Default: ``true``
 
+
+Server-side encryption Settings
+===============================
+
+``rgw crypt s3 kms backend``
+
+:Description: Where the SSE-KMS encryption keys are stored. Supported KMS
+              systems are OpenStack Barbican (``barbican``, the default) and
+              HashiCorp Vault (``vault``).
+:Type: String
+:Default: None
+
+
 Barbican Settings
 =================
 
@@ -937,6 +950,29 @@ Barbican Settings
 :Default: None
 
 
+HashiCorp Vault Settings
+========================
+
+``rgw crypt vault auth```
+
+:Description: Type of authentication method to be used. The only method
+              currently supported is ``token``.
+:Type: String
+:Default: ``token``
+
+``rgw crypt vault token file``
+
+:Description: If authentication method is ``token``, provide a path to the token
+              file, which should be readable only by Rados Gateway.
+:Type: String
+:Default: None
+
+``rgw crypt vault addr``
+
+:Description: Provide a URL to the Vault server secret path.
+:Type: String
+:Default: None
+
 QoS settings
 ------------
 
index ea89e502ab0b8e828d34fdcfc32437d3acc56d65..151f3b5efe5dfaabe6aa6e6f4935842b872c94f5 100644 (file)
@@ -36,9 +36,9 @@ or decrypt data.
 This is implemented in S3 according to the `Amazon SSE-KMS`_ specification.
 
 In principle, any key management service could be used here, but currently
-only integration with `Barbican`_ is implemented.
+only integration with `Barbican`_ and `Vault`_ are implemented.
 
-See `OpenStack Barbican Integration`_.
+See `OpenStack Barbican Integration`_ and `HashiCorp Vault Integration`_.
 
 Automatic Encryption (for testing only)
 =======================================
@@ -58,4 +58,6 @@ The configuration expects a base64-encoded 256 bit key. For example::
 .. _Amazon SSE-C: https://docs.aws.amazon.com/AmazonS3/latest/dev/ServerSideEncryptionCustomerKeys.html
 .. _Amazon SSE-KMS: http://docs.aws.amazon.com/AmazonS3/latest/dev/UsingKMSEncryption.html
 .. _Barbican: https://wiki.openstack.org/wiki/Barbican
+.. _Vault: https://www.vaultproject.io/docs/
 .. _OpenStack Barbican Integration: ../barbican
+.. _HashiCorp Vault Integration: ../vault
index 898421a1ebc13d5636d444334b4de2fe0e0668bd..22b882f3ed06fc22f4602e994c9b7ae45601093d 100644 (file)
@@ -52,6 +52,7 @@ you may write data with one API and retrieve it with the other.
    Export over NFS <nfs>
    OpenStack Keystone Integration <keystone>
    OpenStack Barbican Integration <barbican>
+   HashiCorp Vault Integration <vault>
    Open Policy Agent Integration <opa>
    Multi-tenancy <multitenancy>
    Compression <compression>
diff --git a/doc/radosgw/vault.rst b/doc/radosgw/vault.rst
new file mode 100644 (file)
index 0000000..0cbae34
--- /dev/null
@@ -0,0 +1,79 @@
+===========================
+HashiCorp Vault Integration
+===========================
+
+HashiCorp `Vault`_ can be used as a secure key management service for
+`Server-Side Encryption`_ (SSE-KMS).
+
+#. `Vault authentication`_
+#. `Create a key in Vault`_
+#. `Configure the Ceph Object Gateway`_
+#. `Upload object`_
+
+Vault authentication
+====================
+
+Vault provides several authentication mechanisms. Currently, the Object Gateway
+supports the `token authentication method`_ only.
+
+When authenticating with Vault using the token method, save the token in a
+plain-text file. The path to this file must be provided in the Gateway
+configuration file (see below). For security reasons, ensure the file is
+readable by the Object Gateway only.
+
+Create a key in Vault
+=====================
+
+Generate and save a 256-bit key in Vault. Vault provides several Secret
+Engines, which store, generate, and encrypt data. For instance, create a key
+in the `KV Secrets engine`_ using Vault's command line client::
+
+  export VAULT_ADDR='http://vaultserver:8200'
+  vault kv put secret/myproject/mybucketkey key=$(dd bs=32 count=1 if=/dev/urandom of=/dev/stdout 2>/dev/null | base64)
+
+Output::
+
+  ====== Metadata ======
+  Key              Value
+  ---              -----
+  created_time     2019-08-29T17:01:09.095824999Z
+  deletion_time    n/a
+  destroyed        false
+  version          1
+
+  === Data ===
+  Key    Value
+  ---    -----
+  key    Ak5dRyLQjwX/wb7vo6Fq1qjsfk1dh2CiSicX+gLAhwk=
+
+The URL to the secret in Vault must be provided in the Gateway configuration
+file (see below).
+
+Configure the Ceph Object Gateway
+=================================
+
+Edit the Ceph configuration file to enable Vault as a KMS for server-side
+encryption::
+
+   rgw crypt s3 kms backend = vault
+   rgw crypt vault auth = token
+   rgw crypt vault addr = http://vaultserver:8200
+   rgw crypt vault token file = /path/to/token.file
+
+Upload object
+=============
+
+When uploading an object, provide the SSE key ID in the request. As an example,
+using the AWS command-line client::
+
+  aws --endpoint=http://radosgw:8000 s3 cp plaintext.txt s3://mybucket/encrypted.txt --sse=aws:kms --sse-kms-key-id /v1/secret/data/myproject/mybucketkey
+
+The object gateway will fetch the key from Vault (using the token for
+authentication), encrypt the object and store it in the bucket. Any request to
+downlod the object will require the correct key ID for the Gateway to
+successfully the decrypt it.
+
+.. _Server-Side Encryption: ../encryption
+.. _Vault: https://www.vaultproject.io/docs/
+.. _token authentication method: https://www.vaultproject.io/docs/auth/token.html
+.. _KV Secrets engine: https://www.vaultproject.io/docs/secrets/kv/
index b095624e410052ed5fe6aa1163f080c66ef64497..f385e28eba23d94249e0de1678047ca13bd54abd 100644 (file)
@@ -1525,6 +1525,12 @@ OPTION(rgw_swift_versioning_enabled, OPT_BOOL) // whether swift object versionin
 OPTION(rgw_trust_forwarded_https, OPT_BOOL) // trust Forwarded and X-Forwarded-Proto headers for ssl termination
 OPTION(rgw_crypt_require_ssl, OPT_BOOL) // requests including encryption key headers must be sent over ssl
 OPTION(rgw_crypt_default_encryption_key, OPT_STR) // base64 encoded key for encryption of rgw objects
+
+OPTION(rgw_crypt_s3_kms_backend, OPT_STR) // Where SSE-KMS encryption keys are stored
+OPTION(rgw_crypt_vault_auth, OPT_STR) // Type of authentication method to be used with Vault
+OPTION(rgw_crypt_vault_token_file, OPT_STR) // Path to the token file for Vault authentication
+OPTION(rgw_crypt_vault_addr, OPT_STR) // URL to Vault server endpoint
+
 OPTION(rgw_crypt_s3_kms_encryption_keys, OPT_STR) // extra keys that may be used for aws:kms
                                                       // defined as map "key1=YmluCmJvb3N0CmJvb3N0LQ== key2=b3V0CnNyYwpUZXN0aW5nCg=="
 OPTION(rgw_crypt_suppress_logs, OPT_BOOL)   // suppress logs that might print customer key
index 0b329761177656b58f553ff77c73399f20202538..55b6f5a4371d5fe1e5f5102dbb4e81dde71eff8c 100644 (file)
@@ -6938,10 +6938,48 @@ std::vector<Option> get_rgw_options() {
     .set_default("")
     .set_description(""),
 
+    Option("rgw_crypt_s3_kms_backend", Option::TYPE_STR, Option::LEVEL_ADVANCED)
+    .set_default("barbican")
+    .set_enum_allowed({"barbican", "vault", "testing"})
+    .set_description(
+        "Where the SSE-KMS encryption keys are stored. Supported KMS "
+        "systems are OpenStack Barbican ('barbican', the default) and HashiCorp "
+        "Vault ('vault')."),
+
     Option("rgw_crypt_s3_kms_encryption_keys", Option::TYPE_STR, Option::LEVEL_DEV)
     .set_default("")
     .set_description(""),
 
+    Option("rgw_crypt_vault_auth", Option::TYPE_STR, Option::LEVEL_ADVANCED)
+    .set_default("token")
+    .set_enum_allowed({"token"})
+    .set_description(
+        "Type of authentication method to be used with Vault. "
+        "The only currently supported method is 'token'.")
+    .add_see_also({
+        "rgw_crypt_s3_kms_backend",
+        "rgw_crypt_vault_addr",
+        "rgw_crypt_vault_token_file"}),
+
+    Option("rgw_crypt_vault_token_file", Option::TYPE_STR, Option::LEVEL_ADVANCED)
+    .set_default("")
+    .set_description(
+        "If authentication method is 'token', provide a path to the token file, "
+        "which for security reasons should readable only by Rados Gateway.")
+    .add_see_also({
+      "rgw_crypt_s3_kms_backend",
+      "rgw_crypt_vault_auth",
+      "rgw_crypt_vault_addr"}),
+
+    Option("rgw_crypt_vault_addr", Option::TYPE_STR, Option::LEVEL_ADVANCED)
+    .set_default("")
+    .set_description(
+        "Provide a URL to the Vault server secret path.")
+    .add_see_also({
+      "rgw_crypt_s3_kms_backend",
+      "rgw_crypt_vault_auth",
+      "rgw_crypt_vault_token_file"}),
+
     Option("rgw_crypt_suppress_logs", Option::TYPE_BOOL, Option::LEVEL_ADVANCED)
     .set_default(true)
     .set_description("Suppress logs that might print client key"),
index fbcf151f635703aca6bcde50fc499e3d1b4a1452..8e08d2fbf7bb9e920d02c090032ff00a1d0bcb4c 100644 (file)
@@ -140,7 +140,8 @@ set(librgw_common_srcs
   rgw_rest_sts.cc
   rgw_perf_counters.cc
   rgw_rest_iam.cc
-  rgw_object_lock.cc)
+  rgw_object_lock.cc
+  rgw_kms.cc)
 
 if(WITH_RADOSGW_AMQP_ENDPOINT)
   list(APPEND librgw_common_srcs rgw_amqp.cc)
index f04d87dd2beb91997e5909acee88c6b815b367c4..787c1f0ffba6ec897099d142f7c7862c8ec4756c 100644 (file)
 #include <rgw/rgw_rest_s3.h>
 #include "include/ceph_assert.h"
 #include <boost/utility/string_view.hpp>
-#include <rgw/rgw_keystone.h>
-#include "include/str_map.h"
 #include "crypto/crypto_accel.h"
 #include "crypto/crypto_plugin.h"
+#include "rgw/rgw_kms.h"
 
 #include <openssl/evp.h>
 
@@ -578,119 +577,6 @@ std::string create_random_key_selector(CephContext * const cct) {
   return std::string(random, sizeof(random));
 }
 
-static int get_barbican_url(CephContext * const cct,
-                     std::string& url)
-{
-  url = cct->_conf->rgw_barbican_url;
-  if (url.empty()) {
-    ldout(cct, 0) << "ERROR: conf rgw_barbican_url is not set" << dendl;
-    return -EINVAL;
-  }
-
-  if (url.back() != '/') {
-    url.append("/");
-  }
-
-  return 0;
-}
-
-static int request_key_from_barbican(CephContext *cct,
-                              boost::string_view key_id,
-                              boost::string_view key_selector,
-                              const std::string& barbican_token,
-                              std::string& actual_key) {
-  std::string secret_url;
-  int res;
-  res = get_barbican_url(cct, secret_url);
-  if (res < 0) {
-     return res;
-  }
-  secret_url += "v1/secrets/" + std::string(key_id);
-
-  bufferlist secret_bl;
-  RGWHTTPTransceiver secret_req(cct, "GET", secret_url, &secret_bl);
-  secret_req.append_header("Accept", "application/octet-stream");
-  secret_req.append_header("X-Auth-Token", barbican_token);
-
-  res = secret_req.process(null_yield);
-  if (res < 0) {
-    return res;
-  }
-  if (secret_req.get_http_status() ==
-      RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) {
-    return -EACCES;
-  }
-
-  if (secret_req.get_http_status() >=200 &&
-      secret_req.get_http_status() < 300 &&
-      secret_bl.length() == AES_256_KEYSIZE) {
-    actual_key.assign(secret_bl.c_str(), secret_bl.length());
-    memset(secret_bl.c_str(), 0, secret_bl.length());
-    } else {
-      res = -EACCES;
-    }
-  return res;
-}
-
-static map<string,string> get_str_map(const string &str) {
-  map<string,string> m;
-  get_str_map(str, &m, ";, \t");
-  return m;
-}
-
-static int get_actual_key_from_kms(CephContext *cct,
-                            boost::string_view key_id,
-                            boost::string_view key_selector,
-                            std::string& actual_key)
-{
-  int res = 0;
-  ldout(cct, 20) << "Getting KMS encryption key for key=" << key_id << dendl;
-  static map<string,string> str_map = get_str_map(
-      cct->_conf->rgw_crypt_s3_kms_encryption_keys);
-
-  map<string, string>::iterator it = str_map.find(std::string(key_id));
-  if (it != str_map.end() ) {
-    std::string master_key;
-    try {
-      master_key = from_base64((*it).second);
-    } catch (...) {
-      ldout(cct, 5) << "ERROR: get_actual_key_from_kms invalid encryption key id "
-                    << "which contains character that is not base64 encoded."
-                    << dendl;
-      return -EINVAL;
-    }
-
-    if (master_key.length() == AES_256_KEYSIZE) {
-      uint8_t _actual_key[AES_256_KEYSIZE];
-      if (AES_256_ECB_encrypt(cct,
-          reinterpret_cast<const uint8_t*>(master_key.c_str()), AES_256_KEYSIZE,
-          reinterpret_cast<const uint8_t*>(key_selector.data()),
-          _actual_key, AES_256_KEYSIZE)) {
-        actual_key = std::string((char*)&_actual_key[0], AES_256_KEYSIZE);
-      } else {
-        res = -EIO;
-      }
-      memset(_actual_key, 0, sizeof(_actual_key));
-    } else {
-      ldout(cct, 20) << "Wrong size for key=" << key_id << dendl;
-      res = -EIO;
-    }
-  } else {
-    std::string token;
-    if (rgw::keystone::Service::get_keystone_barbican_token(cct, token) < 0) {
-      ldout(cct, 5) << "Failed to retrieve token for barbican" << dendl;
-      res = -EINVAL;
-      return res;
-    }
-
-    res = request_key_from_barbican(cct, key_id, key_selector, token, actual_key);
-    if (res != 0) {
-      ldout(cct, 5) << "Failed to retrieve secret from barbican:" << key_id << dendl;
-    }
-  }
-  return res;
-}
-
 static inline void set_attr(map<string, bufferlist>& attrs,
                             const char* key,
                             boost::string_view value)
diff --git a/src/rgw/rgw_kms.cc b/src/rgw/rgw_kms.cc
new file mode 100644 (file)
index 0000000..acd3fce
--- /dev/null
@@ -0,0 +1,277 @@
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+/**
+ * Server-side encryption integrations with Key Management Systems (SSE-KMS)
+ */
+
+#include "include/str_map.h"
+#include "common/safe_io.h"
+#include "rgw/rgw_crypt.h"
+#include "rgw/rgw_keystone.h"
+#include "rgw/rgw_b64.h"
+#include "rgw/rgw_kms.h"
+
+#define dout_context g_ceph_context
+#define dout_subsys ceph_subsys_rgw
+
+using namespace rgw;
+
+static map<string,string> get_str_map(const string &str) {
+  map<string,string> m;
+  get_str_map(str, &m, ";, \t");
+  return m;
+}
+
+static int get_actual_key_from_conf(CephContext *cct,
+                                    boost::string_view key_id,
+                                    boost::string_view key_selector,
+                                    std::string& actual_key)
+{
+  int res = 0;
+
+  static map<string,string> str_map = get_str_map(
+      cct->_conf->rgw_crypt_s3_kms_encryption_keys);
+
+  map<string, string>::iterator it = str_map.find(std::string(key_id));
+  if (it == str_map.end())
+    return -ERR_INVALID_ACCESS_KEY;
+
+  std::string master_key;
+  try {
+    master_key = from_base64((*it).second);
+  } catch (std::exception&) {
+    ldout(cct, 5) << "ERROR: get_actual_key_from_conf invalid encryption key id "
+                  << "which contains character that is not base64 encoded."
+                  << dendl;
+    return -EINVAL;
+  }
+
+  if (master_key.length() == AES_256_KEYSIZE) {
+    uint8_t _actual_key[AES_256_KEYSIZE];
+    if (AES_256_ECB_encrypt(cct,
+        reinterpret_cast<const uint8_t*>(master_key.c_str()), AES_256_KEYSIZE,
+        reinterpret_cast<const uint8_t*>(key_selector.data()),
+        _actual_key, AES_256_KEYSIZE)) {
+      actual_key = std::string((char*)&_actual_key[0], AES_256_KEYSIZE);
+    } else {
+      res = -EIO;
+    }
+    memset(_actual_key, 0, sizeof(_actual_key));
+  } else {
+    ldout(cct, 20) << "Wrong size for key=" << key_id << dendl;
+    res = -EIO;
+  }
+
+  return res;
+}
+
+static int get_barbican_url(CephContext * const cct,
+                            std::string& url)
+{
+  url = cct->_conf->rgw_barbican_url;
+  if (url.empty()) {
+    ldout(cct, 0) << "ERROR: conf rgw_barbican_url is not set" << dendl;
+    return -EINVAL;
+  }
+
+  if (url.back() != '/') {
+    url.append("/");
+  }
+
+  return 0;
+}
+
+static int request_key_from_barbican(CephContext *cct,
+                                     boost::string_view key_id,
+                                     const std::string& barbican_token,
+                                     std::string& actual_key) {
+  std::string secret_url;
+  int res;
+  res = get_barbican_url(cct, secret_url);
+  if (res < 0) {
+     return res;
+  }
+  secret_url += "v1/secrets/" + std::string(key_id);
+
+  bufferlist secret_bl;
+  RGWHTTPTransceiver secret_req(cct, "GET", secret_url, &secret_bl);
+  secret_req.append_header("Accept", "application/octet-stream");
+  secret_req.append_header("X-Auth-Token", barbican_token);
+
+  res = secret_req.process(null_yield);
+  if (res < 0) {
+    return res;
+  }
+  if (secret_req.get_http_status() ==
+      RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) {
+    return -EACCES;
+  }
+
+  if (secret_req.get_http_status() >=200 &&
+      secret_req.get_http_status() < 300 &&
+      secret_bl.length() == AES_256_KEYSIZE) {
+    actual_key.assign(secret_bl.c_str(), secret_bl.length());
+    secret_bl.zero();
+    } else {
+      res = -EACCES;
+    }
+  return res;
+}
+
+static int get_actual_key_from_barbican(CephContext *cct,
+                                        boost::string_view key_id,
+                                        std::string& actual_key)
+{
+  int res = 0;
+  std::string token;
+
+  if (rgw::keystone::Service::get_keystone_barbican_token(cct, token) < 0) {
+    ldout(cct, 5) << "Failed to retrieve token for Barbican" << dendl;
+    return -EINVAL;
+  }
+
+  res = request_key_from_barbican(cct, key_id, token, actual_key);
+  if (res != 0) {
+    ldout(cct, 5) << "Failed to retrieve secret from Barbican:" << key_id << dendl;
+  }
+  return res;
+}
+
+static int request_key_from_vault_with_token(CephContext *cct,
+                                             boost::string_view key_id,
+                                             bufferlist *secret_bl)
+{
+  std::string token_file, vault_addr, vault_token;
+  int res = 0;
+
+  token_file = cct->_conf->rgw_crypt_vault_token_file;
+  if (token_file.empty()) {
+    ldout(cct, 0) << "ERROR: Vault token file not set in rgw_crypt_vault_token_file" << dendl;
+    return -EINVAL;
+  }
+  ldout(cct, 20) << "Vault token file: " << token_file << dendl;
+
+  char buf[2048];
+  res = safe_read_file("", token_file.c_str(), buf, sizeof(buf));
+  if (res < 0) {
+    if (-ENOENT == res) {
+      ldout(cct, 0) << "ERROR: Token file '" << token_file << "' not found  " << dendl;
+    } else if (-EACCES == res) {
+      ldout(cct, 0) << "ERROR: Permission denied reading token file" << dendl;
+    } else {
+      ldout(cct, 0) << "ERROR: Failed to read token file with error " << res << dendl;
+    }
+    return res;
+  }
+  // drop trailing newlines
+  while (res && isspace(buf[res-1])) {
+    --res;
+  }
+  vault_token = std::string{buf, static_cast<size_t>(res)};
+  memset(buf, 0, sizeof(buf));
+
+  vault_addr = cct->_conf->rgw_crypt_vault_addr;
+  if (vault_addr.empty()) {
+    ldout(cct, 0) << "ERROR: Vault address not set in rgw_crypt_vault_addr" << dendl;
+    return -EINVAL;
+  }
+
+  std::string secret_url = vault_addr + std::string(key_id);
+  RGWHTTPTransceiver secret_req(cct, "GET", secret_url, secret_bl);
+  secret_req.append_header("X-Vault-Token", vault_token);
+  vault_token.replace(0, vault_token.length(), vault_token.length(), '\000');
+  res = secret_req.process(null_yield);
+  if (res < 0) {
+    ldout(cct, 0) << "ERROR: Request to Vault failed with error " << res << dendl;
+    return res;
+  }
+
+  if (secret_req.get_http_status() ==
+      RGWHTTPTransceiver::HTTP_STATUS_UNAUTHORIZED) {
+    ldout(cct, 0) << "ERROR: Vault request failed authorization" << dendl;
+    return -EACCES;
+  }
+
+  ldout(cct, 20) << "Request to Vault returned " << res << " and HTTP status "
+    << secret_req.get_http_status() << dendl;
+  return res;
+}
+
+static int get_actual_key_from_vault(CephContext *cct,
+                                     boost::string_view key_id,
+                                     std::string& actual_key)
+{
+  int res = 0;
+  std::string auth;
+  bufferlist secret_bl;
+
+  auth = cct->_conf->rgw_crypt_vault_auth;
+  ldout(cct, 20) << "Vault authentication method: " << auth << dendl;
+
+  // Currently only token-based authentication is supported
+  if (RGW_SSE_KMS_VAULT_AUTH_TOKEN == auth) {
+    res = request_key_from_vault_with_token(cct, key_id, &secret_bl);
+  } else {
+    ldout(cct, 0) << "ERROR: Invalid rgw_crypt_vault_auth: " << auth << dendl;
+    return -EINVAL;
+  }
+
+  if (res < 0) {
+    return res;
+  }
+
+  JSONParser parser;
+  if (!parser.parse(secret_bl.c_str(), secret_bl.length())) {
+    ldout(cct, 0) << "ERROR: Failed to parse JSON response from Vault" << dendl;
+    return -EINVAL;
+  }
+  secret_bl.zero();
+
+  JSONObj *json_obj = &parser;
+  std::array<std::string, 3> elements = {"data", "data", "key"};
+  for(const auto& elem : elements) {
+    json_obj = json_obj->find_obj(elem);
+    if (!json_obj) {
+      ldout(cct, 0) << "ERROR: Key not found in JSON response from Vault" << dendl;
+      return -EINVAL;
+    }
+  }
+
+  std::string secret;
+  try {
+    secret = from_base64(json_obj->get_data());
+  } catch (std::exception&) {
+    ldout(cct, 0) << "ERROR: Failed to base64 decode key retrieved from Vault" << dendl;
+    return -EINVAL;
+  }
+
+  actual_key.assign(secret.c_str(), secret.length());
+  secret.replace(0, secret.length(), secret.length(), '\000');
+
+  return res;
+}
+
+int get_actual_key_from_kms(CephContext *cct,
+                            boost::string_view key_id,
+                            boost::string_view key_selector,
+                            std::string& actual_key)
+{
+  std::string kms_backend;
+
+  kms_backend = cct->_conf->rgw_crypt_s3_kms_backend;
+  ldout(cct, 20) << "Getting KMS encryption key for key " << key_id << dendl;
+  ldout(cct, 20) << "SSE-KMS backend is " << kms_backend << dendl;
+
+  if (RGW_SSE_KMS_BACKEND_BARBICAN == kms_backend)
+    return get_actual_key_from_barbican(cct, key_id, actual_key);
+
+  if (RGW_SSE_KMS_BACKEND_VAULT == kms_backend)
+    return get_actual_key_from_vault(cct, key_id, actual_key);
+
+  if (RGW_SSE_KMS_BACKEND_TESTING == kms_backend)
+    return get_actual_key_from_conf(cct, key_id, key_selector, actual_key);
+
+  ldout(cct, 0) << "ERROR: Invalid rgw_crypt_s3_kms_backend: " << kms_backend << dendl;
+  return -EINVAL;
+}
diff --git a/src/rgw/rgw_kms.h b/src/rgw/rgw_kms.h
new file mode 100644 (file)
index 0000000..3e8410e
--- /dev/null
@@ -0,0 +1,33 @@
+// -*- mode:C; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+/**
+ * Server-side encryption integrations with Key Management Systems (SSE-KMS)
+ */
+
+#ifndef CEPH_RGW_KMS_H
+#define CEPH_RGW_KMS_H
+
+static const std::string RGW_SSE_KMS_BACKEND_TESTING = "testing";
+static const std::string RGW_SSE_KMS_BACKEND_BARBICAN = "barbican";
+static const std::string RGW_SSE_KMS_BACKEND_VAULT = "vault";
+
+static const std::string RGW_SSE_KMS_VAULT_AUTH_TOKEN = "token";
+static const std::string RGW_SSE_KMS_VAULT_AUTH_AGENT = "agent";
+
+/**
+ * Retrieves the actual server-side encryption key from a KMS system given a
+ * key ID. Currently supported KMS systems are OpenStack Barbican and HashiCorp
+ * Vault, but keys can also be retrieved from Ceph configuration file (if
+ * kms is set to 'local').
+ *
+ * \params
+ * TODO
+ * \return
+ */
+int get_actual_key_from_kms(CephContext *cct,
+                            boost::string_view key_id,
+                            boost::string_view key_selector,
+                            std::string& actual_key);
+
+#endif
index 65cb731f8a4e33f8bd0c7145c2661435c86cf26e..3089faf0b7c835c892421d0474706e5eb5ec6a96 100644 (file)
@@ -165,3 +165,8 @@ add_ceph_unittest(unittest_rgw_arn)
 
 target_link_libraries(unittest_rgw_arn ${rgw_libs})
 
+# unittest_rgw_kms
+add_executable(unittest_rgw_kms test_rgw_kms.cc)
+add_ceph_unittest(unittest_rgw_kms)
+
+target_link_libraries(unittest_rgw_kms ${rgw_libs})
diff --git a/src/test/rgw/test_rgw_kms.cc b/src/test/rgw/test_rgw_kms.cc
new file mode 100644 (file)
index 0000000..197aba6
--- /dev/null
@@ -0,0 +1,32 @@
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+
+#include <gtest/gtest.h>
+#include "common/ceph_context.h"
+#include "rgw/rgw_common.h"
+#include "rgw/rgw_kms.cc"
+
+TEST(TestSSEKMS, vault_token_file_unset)
+{
+  CephContext *cct = (new CephContext(CEPH_ENTITY_TYPE_ANY))->get();
+
+  std::string key_id, actual_key;
+  bufferlist secret_bl;
+  ASSERT_EQ(
+      request_key_from_vault_with_token(cct, key_id, &secret_bl),
+      -EINVAL
+  );
+}
+
+TEST(TestSSEKMS, non_existent_vault_token_file)
+{
+  CephContext *cct = (new CephContext(CEPH_ENTITY_TYPE_ANY))->get();
+  cct->_conf.set_val("rgw_crypt_vault_token_file", "/nonexistent/file");
+
+  std::string key_id, key_selector, actual_key;
+  bufferlist secret_bl;
+  ASSERT_EQ(
+      request_key_from_vault_with_token(cct, key_id, &secret_bl),
+      -ENOENT
+  );
+}
index 5eb4e723feb15f35ee11dcdb0c52393c7efcf15a..c73f673f387267d7279481103f475f55bbe72756 100755 (executable)
@@ -668,11 +668,18 @@ EOF
         admin socket = $CEPH_ASOK_DIR/\$name.\$pid.asok
 
         ; needed for s3tests
+        rgw crypt s3 kms backend = testing
         rgw crypt s3 kms encryption keys = testkey-1=YmluCmJvb3N0CmJvb3N0LWJ1aWxkCmNlcGguY29uZgo= testkey-2=aWIKTWFrZWZpbGUKbWFuCm91dApzcmMKVGVzdGluZwo=
         rgw crypt require ssl = false
         ; uncomment the following to set LC days as the value in seconds;
         ; needed for passing lc time based s3-tests (can be verbose)
         ; rgw lc debug interval = 10
+        ; The following settings are for SSE-KMS with Vault
+        ; rgw crypt s3 kms backend = vault
+        ; rgw crypt vault auth = token
+        ; rgw crypt vault addr = http://127.0.0.1:8200
+        ; rgw crypt vault token file = /path/to/token.file
+
 $extra_conf
 EOF