osquery-1/osquery/logger/plugins/aws_util.cpp
Zachary Wasserman 262833c86a Add AWS Kinesis and Firehose logger plugins (#2045)
This commit adds logger plugin implementations for the Amazon
Kinesis (https://aws.amazon.com/kinesis/) and Kinesis
Firehose (https://aws.amazon.com/kinesis/firehose/) services. To support
these plugins there are a number of utility classes and functions for
AWS authentication, configuration and API integration. The logger plugin
implementations take advantage of the BufferedLogForwarder base class
for reliable buffering and batch sending of logs. In their current
implementations, the logger plugins only support sending of result logs
to these AWS services.
2016-04-25 16:19:51 -07:00

241 lines
8.0 KiB
C++

/*
* Copyright (c) 2014-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
*/
#include <sstream>
#include <string>
#include <boost/property_tree/ini_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <aws/core/Region.h>
#include <aws/core/client/AWSClient.h>
#include <aws/core/client/ClientConfiguration.h>
#include <osquery/flags.h>
#include <osquery/logger.h>
#include "osquery/remote/transports/tls.h"
#include "osquery/logger/plugins/aws_util.h"
namespace pt = boost::property_tree;
namespace bn = boost::network;
namespace http = boost::network::http;
namespace uri = boost::network::uri;
namespace osquery {
FLAG(string, aws_access_key_id, "", "AWS access key ID override");
FLAG(string, aws_secret_access_key, "", "AWS secret access key override");
FLAG(string,
aws_profile_name,
"",
"AWS config profile to use for auth and region config");
FLAG(string, aws_region, "", "AWS region override");
// Map of AWS region name string -> AWS::Region enum
static const std::map<std::string, Aws::Region> kAwsRegions = {
{"us-east-1", Aws::Region::US_EAST_1},
{"us-west-1", Aws::Region::US_WEST_1},
{"us-west-2", Aws::Region::US_WEST_2},
{"eu-west-1", Aws::Region::EU_WEST_1},
{"eu-central-1", Aws::Region::EU_CENTRAL_1},
{"ap-southeast-1", Aws::Region::AP_SOUTHEAST_1},
{"ap-southeast-2", Aws::Region::AP_SOUTHEAST_2},
{"ap-northeast-1", Aws::Region::AP_NORTHEAST_1},
{"ap-northeast-2", Aws::Region::AP_NORTHEAST_2},
{"sa-east-1", Aws::Region::SA_EAST_1}};
// Default AWS region to use when no region set in flags or profile
static const Aws::Region kDefaultAWSRegion = Aws::Region::US_EAST_1;
std::shared_ptr<Aws::Http::HttpClient>
NetlibHttpClientFactory::CreateHttpClient(
const Aws::Client::ClientConfiguration& clientConfiguration) const {
return std::make_shared<NetlibHttpClient>();
}
std::shared_ptr<Aws::Http::HttpResponse> NetlibHttpClient::MakeRequest(
Aws::Http::HttpRequest& request,
Aws::Utils::RateLimits::RateLimiterInterface* readLimiter,
Aws::Utils::RateLimits::RateLimiterInterface* writeLimiter) const {
// AWS allows rate limiters to be passed around, but we are doing rate
// limiting on the logger plugin side and so don't implement this.
if (readLimiter != nullptr || writeLimiter != nullptr) {
LOG(WARNING) << "Read/write limiters currently unsupported.";
}
Aws::Http::URI uri = request.GetUri();
uri.SetPath(Aws::Http::URI::URLEncodePath(uri.GetPath()));
Aws::String url = uri.GetURIString();
http::client client;
http::client::request req(url);
for (const auto& requestHeader : request.GetHeaders()) {
req << bn::header(requestHeader.first, requestHeader.second);
}
std::string body;
if (request.GetContentBody()) {
std::stringstream ss;
ss << request.GetContentBody()->rdbuf();
body = ss.str();
}
auto response =
std::make_shared<Aws::Http::Standard::StandardHttpResponse>(request);
try {
http::client::response resp;
switch (request.GetMethod()) {
case Aws::Http::HttpMethod::HTTP_GET:
resp = client.get(req);
break;
case Aws::Http::HttpMethod::HTTP_POST:
resp = client.post(req, body, request.GetContentType());
break;
case Aws::Http::HttpMethod::HTTP_PUT:
resp = client.put(req, body, request.GetContentType());
break;
case Aws::Http::HttpMethod::HTTP_HEAD:
resp = client.head(req);
break;
case Aws::Http::HttpMethod::HTTP_PATCH:
LOG(ERROR) << "cpp-netlib does not support HTTP PATCH";
return nullptr;
break;
case Aws::Http::HttpMethod::HTTP_DELETE:
resp = client.delete_(req);
break;
default:
LOG(ERROR) << "Unrecognized HTTP Method used: "
<< static_cast<int>(request.GetMethod());
return nullptr;
break;
}
response->SetResponseCode(
static_cast<Aws::Http::HttpResponseCode>(resp.status()));
for (const auto& header : resp.headers()) {
if (header.first == "content-type") {
response->SetContentType(header.second);
}
response->AddHeader(header.first, header.second);
}
response->GetResponseBody() << resp.body();
} catch (const std::exception& e) {
LOG(ERROR) << "Exception making HTTP request to url (" << url
<< "): " << e.what();
return nullptr;
}
return response;
}
Aws::Auth::AWSCredentials
OsqueryFlagsAWSCredentialsProvider::GetAWSCredentials() {
// Note that returning empty credentials means the provider chain will just
// try the next provider.
if (FLAGS_aws_access_key_id.empty() ^ FLAGS_aws_secret_access_key.empty()) {
LOG(WARNING) << "Only one of aws_access_key_id and aws_secret_access_key "
"were specified. Ignoring.";
return Aws::Auth::AWSCredentials("", "");
}
return Aws::Auth::AWSCredentials(FLAGS_aws_access_key_id,
FLAGS_aws_secret_access_key);
}
OsqueryAWSCredentialsProviderChain::OsqueryAWSCredentialsProviderChain()
: AWSCredentialsProviderChain() {
// The order of the AddProvider calls determines the order in which the
// provider chain attempts to retrieve credentials.
AddProvider(std::make_shared<OsqueryFlagsAWSCredentialsProvider>());
if (!FLAGS_aws_profile_name.empty()) {
AddProvider(
std::make_shared<Aws::Auth::ProfileConfigFileAWSCredentialsProvider>(
FLAGS_aws_profile_name.c_str()));
}
AddProvider(std::make_shared<Aws::Auth::EnvironmentAWSCredentialsProvider>());
AddProvider(
std::make_shared<Aws::Auth::ProfileConfigFileAWSCredentialsProvider>());
AddProvider(
std::make_shared<Aws::Auth::InstanceProfileCredentialsProvider>());
}
Status getAWSRegionFromProfile(Aws::Region& region) {
std::string profile_dir =
Aws::Auth::ProfileConfigFileAWSCredentialsProvider::GetProfileDirectory();
pt::ptree tree;
try {
pt::ini_parser::read_ini(profile_dir + "/config", tree);
} catch (const pt::ini_parser::ini_parser_error& e) {
return Status(1, std::string("Error reading profile file: ") + e.what());
}
// For some reason, profile names are prefixed with "profile ", except for
// "default" which is not.
std::string profile_key = FLAGS_aws_profile_name;
if (!profile_key.empty() && profile_key != "default") {
profile_key = "profile " + profile_key;
} else {
profile_key = "default";
}
auto section_it = tree.find(profile_key);
if (section_it == tree.not_found()) {
return Status(1, "AWS profile not found: " + FLAGS_aws_profile_name);
}
auto key_it = section_it->second.find("region");
if (key_it == section_it->second.not_found()) {
return Status(1, "AWS region not found for profile: " +
FLAGS_aws_profile_name);
}
std::string region_string = key_it->second.data();
if (kAwsRegions.count(region_string) > 0) {
region = kAwsRegions.at(region_string);
} else {
return Status(1, "Invalid aws_region in profile: " + region_string);
}
return Status(0);
}
Status getAWSRegion(Aws::Region& region) {
// First try using the flag aws_region
if (!FLAGS_aws_region.empty()) {
if (kAwsRegions.count(FLAGS_aws_region) > 0) {
VLOG(1) << "Using AWS region from flag: " << FLAGS_aws_region;
region = kAwsRegions.at(FLAGS_aws_region);
return Status(0);
} else {
return Status(1, "Invalid aws_region specified: " + FLAGS_aws_region);
}
}
// Try finding in profile, but use default if that fails and no profile name
// was specified
Status s = getAWSRegionFromProfile(region);
if (s.ok() || !FLAGS_aws_profile_name.empty()) {
VLOG(1) << "Using AWS region from profile: "
<< Aws::RegionMapper::GetRegionName(region);
return s;
}
region = kDefaultAWSRegion;
VLOG(1) << "Using default AWS region: "
<< Aws::RegionMapper::GetRegionName(region);
return Status(0);
}
}