mirror of
https://github.com/valitydev/osquery-1.git
synced 2024-11-07 18:08:53 +00:00
262833c86a
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.
241 lines
8.0 KiB
C++
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);
|
|
}
|
|
}
|