blob: ac5957080e7e4190b45e8aad5ef40c48f74fd130 [file] [log] [blame]
/********************************************************************
* 2014 -
* open source under Apache License Version 2.0
********************************************************************/
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "KmsClientProvider.h"
#include "Logger.h"
#include <gsasl.h>
#include <map>
#include <boost/property_tree/json_parser.hpp>
using namespace Hdfs::Internal;
using boost::property_tree::read_json;
using boost::property_tree::write_json;
namespace Hdfs {
/**
* Convert ptree format to json format
*/
std::string KmsClientProvider::toJson(const ptree &data) {
std::ostringstream buf;
try {
write_json(buf, data, false);
std::string json = buf.str();
return json;
} catch (...) {
THROW(HdfsIOException, "KmsClientProvider : Write json failed.");
}
}
/**
* Convert json format to ptree format
*/
ptree KmsClientProvider::fromJson(const std::string &data) {
ptree pt2;
try {
std::istringstream is(data);
read_json(is, pt2);
return pt2;
} catch (...) {
THROW(HdfsIOException, "KmsClientProvider : Read json failed.");
}
}
/**
* Encode string to base64.
*/
std::string KmsClientProvider::base64Encode(const std::string &data) {
char * buffer = NULL;
size_t len = 0;
int rc = 0;
std::string result;
LOG(DEBUG3, "KmsClientProvider : Encode data is %s", data.c_str());
if (GSASL_OK != (rc = gsasl_base64_to(data.data(), data.size(), &buffer, &len))) {
assert(GSASL_MALLOC_ERROR == rc);
throw std::bad_alloc();
}
if (buffer) {
result.assign(buffer, len);
free(buffer);
}
if (!buffer || result.length() != len) {
THROW(HdfsIOException,
"KmsClientProvider: Failed to encode string to base64");
}
return result;
}
/**
* Decode base64 to string.
*/
std::string KmsClientProvider::base64Decode(const std::string &data) {
char * buffer = NULL;
size_t len = 0;
int rc = 0;
std::string result;
if (GSASL_OK != (rc = gsasl_base64_from(data.data(), data.size(), &buffer, &len))) {
assert(GSASL_MALLOC_ERROR == rc);
throw std::bad_alloc();
}
if (buffer) {
result.assign(buffer, len);
free(buffer);
}
if (!buffer || result.length() != len) {
THROW(HdfsIOException,
"KmsClientProvider: Failed to decode base64 to string");
}
return result;
}
/**
* Construct a KmsClientProvider instance.
* @param auth RpcAuth to get the auth method and user info.
* @param conf a SessionConfig to get the configuration.
*/
KmsClientProvider::KmsClientProvider(shared_ptr<RpcAuth> rpcAuth, shared_ptr<SessionConfig> config) : auth(rpcAuth), conf(config)
{
hc.reset(new HttpClient());
method = RpcAuth::ParseMethod(conf->getKmsMethod());
}
/**
* Set HttpClient object.
*/
void KmsClientProvider::setHttpClient(shared_ptr<HttpClient> hc)
{
this->hc = hc;
}
/**
* Parse kms url from configure file.
*/
std::string KmsClientProvider::parseKmsUrl()
{
std::string start = "kms://";
std::string http = "http@";
std::string https = "https@";
std::string urlParse = conf->getKmsUrl();
LOG(DEBUG3, "KmsClientProvider : Get kms url from conf : %s.",
urlParse.c_str());
if (urlParse.compare(0, start.length(), start) == 0) {
start = urlParse.substr(start.length());
if (start.compare(0, http.length(), http) == 0) {
return "http://" + start.substr(http.length());
} else if (start.compare(0, https.length(), https) == 0) {
return "https://" + start.substr(https.length());
} else
THROW(HdfsIOException, "Bad KMS provider URL: %s", urlParse.c_str());
} else
THROW(HdfsIOException, "Bad KMS provider URL: %s", urlParse.c_str());
}
/**
* Build kms url based on urlSuffix and different auth method.
*/
std::string KmsClientProvider::buildKmsUrl(const std::string &url, const std::string &urlSuffix)
{
std::string baseUrl = url;
baseUrl = url + "/v1/" + urlSuffix;
std::size_t found = urlSuffix.find('?');
if (method == AuthMethod::KERBEROS) {
// todo
THROW(InvalidParameter, "KmsClientProvider : Not support kerberos yet.");
} else if (method == AuthMethod::SIMPLE) {
std::string user = auth->getUser().getRealUser();
LOG(DEBUG3,
"KmsClientProvider : Kms urlSuffix is : %s. Auth real user is : %s.",
urlSuffix.c_str(), user.c_str());
if (user.length() == 0)
user = auth->getUser().getKrbName();
if (found != std::string::npos)
return baseUrl + "&user.name=" + user;
else
return baseUrl + "?user.name=" + user;
} else {
return baseUrl;
}
}
/**
* Set common headers for kms API.
*/
void KmsClientProvider::setCommonHeaders(std::vector<std::string>& headers)
{
headers.push_back("Content-Type: application/json");
headers.push_back("Accept: *");
}
/**
* Create an encryption key from kms.
* @param keyName the name of this key.
* @param cipher the ciphertext of this key. e.g. "AES/CTR/NoPadding" .
* @param length the length of this key.
* @param material will be encode to base64.
* @param description key's info.
*/
void KmsClientProvider::createKey(const std::string &keyName, const std::string &cipher, const int length, const std::string &material, const std::string &description)
{
hc->init();
/* Prepare url for HttpClient.*/
url = parseKmsUrl();
std::string urlSuffix = "keys";
url = buildKmsUrl(url, urlSuffix);
/* Prepare headers for HttpClient.*/
std::vector<std::string> headers;
setCommonHeaders(headers);
/* Prepare body for HttpClient. */
ptree map;
map.put("name", keyName);
map.put("cipher", cipher);
map.put("description", description);
std::string body = toJson(map);
/* Set options for HttpClient to get response. */
hc->setURL(url);
hc->setHeaders(headers);
hc->setBody(body);
hc->setRequestRetryTimes(conf->getHttpRequestRetryTimes());
hc->setRequestTimeout(conf->getCurlTimeOut());
hc->setExpectedResponseCode(201);
std::string response = hc->post();
LOG(DEBUG3,
"KmsClientProvider::createKey : The key name, key cipher, key length, key material, description are : %s, %s, %d, %s, %s. The kms url is : %s . The kms body is : %s. The response of kms server is : %s .",
keyName.c_str(), cipher.c_str(), length, material.c_str(),
description.c_str(), url.c_str(), body.c_str(), response.c_str());
}
/**
* Get key metadata based on encrypted file's key name.
* @param encryptionInfo the encryption info of file.
* @return return response info about key metadata from kms server.
*/
ptree KmsClientProvider::getKeyMetadata(const FileEncryptionInfo &encryptionInfo)
{
hc->init();
url = parseKmsUrl();
std::string urlSuffix = "key/" + hc->escape(encryptionInfo.getKeyName()) + "/_metadata";
url = buildKmsUrl(url, urlSuffix);
hc->setURL(url);
hc->setExpectedResponseCode(200);
hc->setRequestRetryTimes(conf->getHttpRequestRetryTimes());
hc->setRequestTimeout(conf->getCurlTimeOut());
std::string response = hc->get();
LOG(DEBUG3,
"KmsClientProvider::getKeyMetadata : The kms url is : %s. The response of kms server is : %s .",
url.c_str(), response.c_str());
return fromJson(response);
}
/**
* Delete an encryption key from kms.
* @param encryptionInfo the encryption info of file.
*/
void KmsClientProvider::deleteKey(const FileEncryptionInfo &encryptionInfo)
{
hc->init();
url = parseKmsUrl();
std::string urlSuffix = "key/" + hc->escape(encryptionInfo.getKeyName());
url = buildKmsUrl(url, urlSuffix);
hc->setURL(url);
hc->setExpectedResponseCode(200);
hc->setRequestRetryTimes(conf->getHttpRequestRetryTimes());
hc->setRequestTimeout(conf->getCurlTimeOut());
std::string response = hc->del();
LOG(DEBUG3,
"KmsClientProvider::deleteKey : The kms url is : %s. The response of kms server is : %s .",
url.c_str(), response.c_str());
}
/**
* Decrypt an encrypted key from kms.
* @param encryptionInfo the encryption info of file.
* @return return decrypted key.
*/
ptree KmsClientProvider::decryptEncryptedKey(const FileEncryptionInfo &encryptionInfo)
{
hc->init();
/* Prepare HttpClient url. */
url = parseKmsUrl();
std::string urlSuffix = "keyversion/" + hc->escape(encryptionInfo.getEzKeyVersionName()) + "/_eek?eek_op=decrypt";
url = buildKmsUrl(url, urlSuffix);
/* Prepare HttpClient headers. */
std::vector<std::string> headers;
setCommonHeaders(headers);
/* Prepare HttpClient body with json format. */
ptree map;
map.put("name", encryptionInfo.getKeyName());
map.put("iv", base64Encode(encryptionInfo.getIv()));
map.put("material", base64Encode(encryptionInfo.getKey()));
std::string body = toJson(map);
/* Set options for HttpClient. */
hc->setURL(url);
hc->setHeaders(headers);
hc->setBody(body);
hc->setExpectedResponseCode(200);
hc->setRequestRetryTimes(conf->getHttpRequestRetryTimes());
hc->setRequestTimeout(conf->getCurlTimeOut());
std::string response = hc->post();
LOG(DEBUG3,
"KmsClientProvider::decryptEncryptedKey : The kms url is : %s . The kms body is : %s. The response of kms server is : %s .",
url.c_str(), body.c_str(), response.c_str());
return fromJson(response);
}
}