mirror of
https://github.com/valitydev/osquery-1.git
synced 2024-11-07 01:55:20 +00:00
Merge pull request #1407 from theopolis/tls_customization
Add 'hidden' flags to customize TLS plugins
This commit is contained in:
commit
cd1d39b323
@ -83,6 +83,14 @@ The posted logger data is exactly the same as logged to disk by the **filesystem
|
||||
{}
|
||||
```
|
||||
|
||||
**Customizations**
|
||||
|
||||
There are several unlisted flags to further control the remote settings. These controls are helpful if using a somewhat opaque API.
|
||||
|
||||
`--tls_secret_always=True` will always send the enrollment secret. This will not perform an enrollment request with every config/logger attempt but rather "also" include the secret. If this is enabled, the secret is appended as a URI variable.
|
||||
|
||||
`--tls_enroll_override=enroll_secret` this allows one to rename the enrollment key request body or URI variable.
|
||||
|
||||
## Remote logging buffering
|
||||
|
||||
In most cases the client plugins default to 3-strikes-you're-out when attempting to POST to the configured endpoints. If a configuration cannot be retrieved the client will exit non-0 but a non-responsive logger endpoint will cause logs to buffer in RocksDB. The logging buffer size can be controlled by a [CLI flag](../installation/cli-flags.md), and if the size overflows logs will drop.
|
||||
|
@ -210,6 +210,13 @@ std::string getHostname();
|
||||
*/
|
||||
std::string generateHostUuid();
|
||||
|
||||
/**
|
||||
* @brief Get a configured UUID/name that uniquely identify this machine
|
||||
*
|
||||
* @return string to identify this machine
|
||||
*/
|
||||
std::string getHostIdentifier();
|
||||
|
||||
/**
|
||||
* @brief Getter for the current time, in a human-readable format.
|
||||
*
|
||||
|
@ -83,7 +83,7 @@ std::string getNodeKey(const std::string& enroll_plugin, bool force = false);
|
||||
*
|
||||
* @return enroll_secret The trimmed content read from FLAGS_enroll_secret_path.
|
||||
*/
|
||||
std::string getEnrollSecret();
|
||||
const std::string& getEnrollSecret();
|
||||
|
||||
/**
|
||||
* @brief Enroll plugin registry.
|
||||
|
@ -41,6 +41,10 @@ FLAG(uint64,
|
||||
0,
|
||||
"Optional interval in seconds to re-read configuration (min=10)");
|
||||
|
||||
DECLARE_bool(tls_secret_always);
|
||||
DECLARE_string(tls_enroll_override);
|
||||
DECLARE_bool(tls_node_api);
|
||||
|
||||
class TLSConfigPlugin : public ConfigPlugin {
|
||||
public:
|
||||
Status setUp();
|
||||
@ -66,13 +70,24 @@ Status TLSConfigPlugin::setUp() {
|
||||
return Status(0, "OK");
|
||||
}
|
||||
|
||||
Status makeTLSConfigRequest(const std::string& uri, pt::ptree& output) {
|
||||
Status makeTLSConfigRequest(const std::string& uri,
|
||||
const std::string& node_key,
|
||||
pt::ptree& output) {
|
||||
// Make a request to the config endpoint, providing the node secret.
|
||||
pt::ptree params;
|
||||
params.put<std::string>("node_key", getNodeKey("tls"));
|
||||
|
||||
auto request = Request<TLSTransport, JSONSerializer>(uri);
|
||||
auto status = request.call(params);
|
||||
// If using a GET request, append the node_key to the URI variables.
|
||||
std::string uri_suffix;
|
||||
if (FLAGS_tls_node_api) {
|
||||
uri_suffix = "&node_key=" + node_key;
|
||||
} else {
|
||||
params.put<std::string>("node_key", node_key);
|
||||
}
|
||||
|
||||
// Again check for GET to call with/without parameters.
|
||||
auto request = Request<TLSTransport, JSONSerializer>(uri + uri_suffix);
|
||||
auto status = (FLAGS_tls_node_api) ? request.call() : request.call(params);
|
||||
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
@ -84,19 +99,32 @@ Status makeTLSConfigRequest(const std::string& uri, pt::ptree& output) {
|
||||
}
|
||||
|
||||
// Receive config or key rejection
|
||||
if (output.count("node_invalid") > 0) {
|
||||
if (output.count("node_invalid") > 0 || output.count("error") > 0) {
|
||||
return Status(1, "Config retrieval failed: Invalid node key");
|
||||
}
|
||||
return Status(0, "OK");
|
||||
}
|
||||
|
||||
Status TLSConfigPlugin::genConfig(std::map<std::string, std::string>& config) {
|
||||
auto uri = "https://" + FLAGS_tls_hostname + FLAGS_config_tls_endpoint;
|
||||
VLOG(1) << "TLSConfigPlugin requesting a config from: " << uri;
|
||||
auto node_key = getNodeKey("tls");
|
||||
auto uri = "https://" + FLAGS_tls_hostname;
|
||||
if (FLAGS_tls_node_api) {
|
||||
// The TLS API should treat clients as nodes.
|
||||
// In this case the node_key acts as an identifier (node) and the endpoints
|
||||
// (if provided) are treated as edges from the nodes.
|
||||
uri += "/" + node_key;
|
||||
}
|
||||
uri += FLAGS_config_tls_endpoint;
|
||||
|
||||
// Some APIs may require persistent identification.
|
||||
if (FLAGS_tls_secret_always) {
|
||||
uri += ((uri.find("?") != std::string::npos) ? "&" : "?") +
|
||||
FLAGS_tls_enroll_override + "=" + getEnrollSecret();
|
||||
}
|
||||
|
||||
pt::ptree recv;
|
||||
for (size_t i = 1; i <= CONFIG_TLS_MAX_ATTEMPTS; i++) {
|
||||
auto status = makeTLSConfigRequest(uri, recv);
|
||||
auto status = makeTLSConfigRequest(uri, node_key, recv);
|
||||
if (status.ok()) {
|
||||
std::stringstream ss;
|
||||
try {
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include <boost/uuid/uuid_io.hpp>
|
||||
|
||||
#include <osquery/core.h>
|
||||
#include <osquery/database.h>
|
||||
#include <osquery/filesystem.h>
|
||||
#include <osquery/logger.h>
|
||||
#include <osquery/sql.h>
|
||||
@ -46,6 +47,11 @@ CLI_FLAG(bool,
|
||||
false,
|
||||
"Force osqueryd to kill previously-running daemons");
|
||||
|
||||
FLAG(string,
|
||||
host_identifier,
|
||||
"hostname",
|
||||
"Field used to identify the host running osquery (hostname, uuid)");
|
||||
|
||||
std::string getHostname() {
|
||||
char hostname[256] = {0}; // Linux max should be 64.
|
||||
gethostname(hostname, sizeof(hostname) - 1);
|
||||
@ -81,6 +87,27 @@ std::string generateHostUuid() {
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string getHostIdentifier() {
|
||||
if (FLAGS_host_identifier != "uuid") {
|
||||
// use the hostname as the default machine identifier
|
||||
return osquery::getHostname();
|
||||
}
|
||||
|
||||
// Generate a identifier/UUID for this application launch, and persist.
|
||||
static std::string ident;
|
||||
if (ident.size() == 0) {
|
||||
// Lookup the host identifier (UUID) previously generated and stored.
|
||||
getDatabaseValue(kPersistentSettings, "hostIdentifier", ident);
|
||||
if (ident.size() == 0) {
|
||||
ident = osquery::generateHostUuid();
|
||||
VLOG(1) << "Using uuid " << ident << " as host identifier";
|
||||
setDatabaseValue(kPersistentSettings, "hostIdentifier", ident);
|
||||
}
|
||||
}
|
||||
|
||||
return ident;
|
||||
}
|
||||
|
||||
std::string getAsciiTime() {
|
||||
auto result = std::time(nullptr);
|
||||
auto time_str = std::string(std::asctime(std::gmtime(&result)));
|
||||
|
@ -28,6 +28,19 @@ namespace fs = boost::filesystem;
|
||||
namespace osquery {
|
||||
std::string kFakeDirectory = "";
|
||||
|
||||
#ifdef DARWIN
|
||||
std::string kTestWorkingDirectory = "/private/tmp/osquery-tests";
|
||||
#else
|
||||
std::string kTestWorkingDirectory = "/tmp/osquery-tests";
|
||||
#endif
|
||||
|
||||
/// Most tests will use binary or disk-backed content for parsing tests.
|
||||
#ifndef OSQUERY_BUILD_SDK
|
||||
std::string kTestDataPath = "../../../tools/tests/";
|
||||
#else
|
||||
std::string kTestDataPath = "../../../../tools/tests/";
|
||||
#endif
|
||||
|
||||
DECLARE_string(database_path);
|
||||
DECLARE_string(extensions_socket);
|
||||
DECLARE_string(modules_autoload);
|
||||
@ -53,29 +66,21 @@ void initTesting() {
|
||||
|
||||
// Set safe default values for path-based flags.
|
||||
// Specific unittests may edit flags temporarily.
|
||||
std::string testWorkingDirectory =
|
||||
kTestWorkingDirectory + std::to_string(getuid()) + "/";
|
||||
kFakeDirectory = testWorkingDirectory + kFakeDirectoryName;
|
||||
kTestWorkingDirectory += std::to_string(getuid()) + "/";
|
||||
kFakeDirectory = kTestWorkingDirectory + kFakeDirectoryName;
|
||||
|
||||
fs::remove_all(testWorkingDirectory);
|
||||
fs::create_directories(testWorkingDirectory);
|
||||
FLAGS_database_path = testWorkingDirectory + "unittests.db";
|
||||
FLAGS_extensions_socket = testWorkingDirectory + "unittests.em";
|
||||
FLAGS_extensions_autoload = testWorkingDirectory + "unittests-ext.load";
|
||||
FLAGS_modules_autoload = testWorkingDirectory + "unittests-mod.load";
|
||||
fs::remove_all(kTestWorkingDirectory);
|
||||
fs::create_directories(kTestWorkingDirectory);
|
||||
FLAGS_database_path = kTestWorkingDirectory + "unittests.db";
|
||||
FLAGS_extensions_socket = kTestWorkingDirectory + "unittests.em";
|
||||
FLAGS_extensions_autoload = kTestWorkingDirectory + "unittests-ext.load";
|
||||
FLAGS_modules_autoload = kTestWorkingDirectory + "unittests-mod.load";
|
||||
FLAGS_disable_logging = true;
|
||||
|
||||
// Create a default DBHandle instance before unittests.
|
||||
(void)DBHandle::getInstance();
|
||||
}
|
||||
|
||||
/// Most tests will use binary or disk-backed content for parsing tests.
|
||||
#ifndef OSQUERY_BUILD_SDK
|
||||
std::string kTestDataPath = "../../../tools/tests/";
|
||||
#else
|
||||
std::string kTestDataPath = "../../../../tools/tests/";
|
||||
#endif
|
||||
|
||||
QueryData getTestDBExpectedResults() {
|
||||
QueryData d;
|
||||
Row row1;
|
||||
|
@ -27,19 +27,19 @@ namespace osquery {
|
||||
/// Init function for tests and benchmarks.
|
||||
void initTesting();
|
||||
|
||||
/// Cleanup/stop function for tests and benchmarks.
|
||||
void cleanupTesting();
|
||||
|
||||
/// Any SQL-dependent tests should use kTestQuery for a pre-populated example.
|
||||
const std::string kTestQuery = "SELECT * FROM test_table";
|
||||
|
||||
/// Tests can be run from within the source or build directory.
|
||||
/// The test initializer will attempt to discovery the current working path.
|
||||
extern std::string kTestDataPath;
|
||||
|
||||
/// Tests should limit intermediate input/output to a working directory.
|
||||
/// Config data, logging results, and intermediate database/caching usage.
|
||||
|
||||
#ifdef DARWIN
|
||||
const std::string kTestWorkingDirectory = "/private/tmp/osquery-tests";
|
||||
#else
|
||||
const std::string kTestWorkingDirectory = "/tmp/osquery-tests";
|
||||
#endif
|
||||
extern std::string kTestWorkingDirectory;
|
||||
|
||||
/// A fake directory tree should be used for filesystem iterator testing.
|
||||
const std::string kFakeDirectoryName = "fstree";
|
||||
|
@ -22,40 +22,10 @@
|
||||
|
||||
namespace osquery {
|
||||
|
||||
FLAG(string,
|
||||
host_identifier,
|
||||
"hostname",
|
||||
"Field used to identify the host running osquery (hostname, uuid)");
|
||||
|
||||
FLAG(bool, enable_monitor, false, "Enable the schedule monitor");
|
||||
|
||||
FLAG(uint64, schedule_timeout, 0, "Limit the schedule, 0 for no limit")
|
||||
|
||||
Status getHostIdentifier(std::string& ident) {
|
||||
if (FLAGS_host_identifier != "uuid") {
|
||||
// use the hostname as the default machine identifier
|
||||
ident = osquery::getHostname();
|
||||
return Status(0, "OK");
|
||||
}
|
||||
|
||||
// Lookup the host identifier (UUID) previously generated and stored.
|
||||
auto status = getDatabaseValue(kPersistentSettings, "hostIdentifier", ident);
|
||||
if (!status.ok()) {
|
||||
// The lookup failed, there is a problem accessing the database.
|
||||
VLOG(1) << "Could not access database; using hostname as host identifier";
|
||||
ident = osquery::getHostname();
|
||||
return Status(0, "OK");
|
||||
}
|
||||
|
||||
if (ident.size() == 0) {
|
||||
// There was no uuid stored in the database, generate one and store it.
|
||||
ident = osquery::generateHostUuid();
|
||||
VLOG(1) << "Using uuid " << ident << " as host identifier";
|
||||
return setDatabaseValue(kPersistentSettings, "hostIdentifier", ident);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
inline SQL monitor(const std::string& name, const ScheduledQuery& query) {
|
||||
// Snapshot the performance and times for the worker before running.
|
||||
auto pid = std::to_string(getpid());
|
||||
@ -91,11 +61,7 @@ void launchQuery(const std::string& name, const ScheduledQuery& query) {
|
||||
}
|
||||
|
||||
// Fill in a host identifier fields based on configuration or availability.
|
||||
std::string ident;
|
||||
auto status = getHostIdentifier(ident);
|
||||
if (!status.ok() || ident.empty()) {
|
||||
ident = "<unknown>";
|
||||
}
|
||||
std::string ident = getHostIdentifier();
|
||||
|
||||
// A query log item contains an optional set of differential results or
|
||||
// a copy of the most-recent execution alongside some query metadata.
|
||||
@ -118,7 +84,7 @@ void launchQuery(const std::string& name, const ScheduledQuery& query) {
|
||||
// Add this execution's set of results to the database-tracked named query.
|
||||
// We can then ask for a differential from the last time this named query
|
||||
// was executed by exact matching each row.
|
||||
status = dbQuery.addNewResults(sql.rows(), diff_results);
|
||||
auto status = dbQuery.addNewResults(sql.rows(), diff_results);
|
||||
if (!status.ok()) {
|
||||
LOG(ERROR) << "Error adding new results to database: " << status.what();
|
||||
return;
|
||||
|
@ -25,26 +25,28 @@
|
||||
|
||||
namespace osquery {
|
||||
|
||||
const std::string kRealTestPath = kTestWorkingDirectory + "inotify-trigger";
|
||||
const std::string kRealTestDir = kTestWorkingDirectory + "inotify-triggers";
|
||||
const std::string kRealTestDirPath = kRealTestDir + "/1";
|
||||
const std::string kRealTestSubDir = kRealTestDir + "/2";
|
||||
const std::string kRealTestSubDirPath = kRealTestSubDir + "/1";
|
||||
|
||||
int kMaxEventLatency = 3000;
|
||||
const int kMaxEventLatency = 3000;
|
||||
|
||||
class INotifyTests : public testing::Test {
|
||||
protected:
|
||||
void SetUp() {
|
||||
real_test_path = kTestWorkingDirectory + "inotify-trigger";
|
||||
real_test_dir = kTestWorkingDirectory + "inotify-triggers";
|
||||
real_test_dir_path = real_test_dir + "/1";
|
||||
real_test_sub_dir = real_test_dir + "/2";
|
||||
real_test_sub_dir_path = real_test_sub_dir + "/1";
|
||||
}
|
||||
|
||||
void TearDown() {
|
||||
// End the event loops, and join on the threads.
|
||||
boost::filesystem::remove_all(kRealTestPath);
|
||||
boost::filesystem::remove_all(kRealTestDir);
|
||||
boost::filesystem::remove_all(real_test_path);
|
||||
boost::filesystem::remove_all(real_test_dir);
|
||||
}
|
||||
|
||||
void StartEventLoop() {
|
||||
event_pub_ = std::make_shared<INotifyEventPublisher>();
|
||||
auto status = EventFactory::registerEventPublisher(event_pub_);
|
||||
FILE* fd = fopen(kRealTestPath.c_str(), "w");
|
||||
FILE* fd = fopen(real_test_path.c_str(), "w");
|
||||
fclose(fd);
|
||||
temp_thread_ = boost::thread(EventFactory::run, "inotify");
|
||||
}
|
||||
@ -88,8 +90,17 @@ class INotifyTests : public testing::Test {
|
||||
fclose(fd);
|
||||
}
|
||||
|
||||
protected:
|
||||
// Internal state managers.
|
||||
std::shared_ptr<INotifyEventPublisher> event_pub_;
|
||||
boost::thread temp_thread_;
|
||||
|
||||
// Transient paths.
|
||||
std::string real_test_path;
|
||||
std::string real_test_dir;
|
||||
std::string real_test_dir_path;
|
||||
std::string real_test_sub_dir;
|
||||
std::string real_test_sub_dir_path;
|
||||
};
|
||||
|
||||
TEST_F(INotifyTests, test_register_event_pub) {
|
||||
@ -217,7 +228,7 @@ TEST_F(INotifyTests, test_inotify_run) {
|
||||
EXPECT_TRUE(status.ok());
|
||||
|
||||
// Create a temporary file to watch, open writeable
|
||||
FILE* fd = fopen(kRealTestPath.c_str(), "w");
|
||||
FILE* fd = fopen(real_test_path.c_str(), "w");
|
||||
|
||||
// Create a subscriber.
|
||||
auto sub = std::make_shared<TestINotifyEventSubscriber>();
|
||||
@ -225,7 +236,7 @@ TEST_F(INotifyTests, test_inotify_run) {
|
||||
|
||||
// Create a subscriptioning context
|
||||
auto mc = std::make_shared<INotifySubscriptionContext>();
|
||||
mc->path = kRealTestPath;
|
||||
mc->path = real_test_path;
|
||||
status = EventFactory::addSubscription(
|
||||
"inotify", Subscription::create("TestINotifyEventSubscriber", mc));
|
||||
EXPECT_TRUE(status.ok());
|
||||
@ -252,10 +263,10 @@ TEST_F(INotifyTests, test_inotify_fire_event) {
|
||||
sub->init();
|
||||
|
||||
// Create a subscriptioning context, note the added Event to the symbol
|
||||
auto sc = sub->GetSubscription(kRealTestPath, 0);
|
||||
auto sc = sub->GetSubscription(real_test_path, 0);
|
||||
sub->subscribe(&TestINotifyEventSubscriber::SimpleCallback, sc, nullptr);
|
||||
|
||||
TriggerEvent(kRealTestPath);
|
||||
TriggerEvent(real_test_path);
|
||||
sub->WaitForEvents(kMaxEventLatency);
|
||||
|
||||
// Make sure our expected event fired (aka subscription callback was called).
|
||||
@ -269,10 +280,10 @@ TEST_F(INotifyTests, test_inotify_event_action) {
|
||||
auto sub = std::make_shared<TestINotifyEventSubscriber>();
|
||||
sub->init();
|
||||
|
||||
auto sc = sub->GetSubscription(kRealTestPath, 0);
|
||||
auto sc = sub->GetSubscription(real_test_path, 0);
|
||||
sub->subscribe(&TestINotifyEventSubscriber::Callback, sc, nullptr);
|
||||
|
||||
TriggerEvent(kRealTestPath);
|
||||
TriggerEvent(real_test_path);
|
||||
sub->WaitForEvents(kMaxEventLatency, 4);
|
||||
|
||||
// Make sure the inotify action was expected.
|
||||
@ -287,15 +298,15 @@ TEST_F(INotifyTests, test_inotify_event_action) {
|
||||
TEST_F(INotifyTests, test_inotify_optimization) {
|
||||
// Assume event type is registered.
|
||||
StartEventLoop();
|
||||
boost::filesystem::create_directory(kRealTestDir);
|
||||
boost::filesystem::create_directory(real_test_dir);
|
||||
|
||||
// Adding a descriptor to a directory will monitor files within.
|
||||
SubscriptionAction(kRealTestDir);
|
||||
EXPECT_TRUE(event_pub_->isPathMonitored(kRealTestDirPath));
|
||||
SubscriptionAction(real_test_dir);
|
||||
EXPECT_TRUE(event_pub_->isPathMonitored(real_test_dir_path));
|
||||
|
||||
// Adding a subscription to a file within a monitored directory is fine
|
||||
// but this will NOT cause an additional INotify watch.
|
||||
SubscriptionAction(kRealTestDirPath);
|
||||
SubscriptionAction(real_test_dir_path);
|
||||
EXPECT_EQ(event_pub_->numDescriptors(), 1);
|
||||
StopEventLoop();
|
||||
}
|
||||
@ -306,17 +317,17 @@ TEST_F(INotifyTests, test_inotify_recursion) {
|
||||
auto sub = std::make_shared<TestINotifyEventSubscriber>();
|
||||
sub->init();
|
||||
|
||||
boost::filesystem::create_directory(kRealTestDir);
|
||||
boost::filesystem::create_directory(kRealTestSubDir);
|
||||
boost::filesystem::create_directory(real_test_dir);
|
||||
boost::filesystem::create_directory(real_test_sub_dir);
|
||||
|
||||
// Subscribe to the directory inode
|
||||
auto mc = sub->createSubscriptionContext();
|
||||
mc->path = kRealTestDir;
|
||||
mc->path = real_test_dir;
|
||||
mc->recursive = true;
|
||||
sub->subscribe(&TestINotifyEventSubscriber::Callback, mc, nullptr);
|
||||
|
||||
// Trigger on a subdirectory's file.
|
||||
TriggerEvent(kRealTestSubDirPath);
|
||||
TriggerEvent(real_test_sub_dir_path);
|
||||
|
||||
sub->WaitForEvents(kMaxEventLatency, 1);
|
||||
EXPECT_TRUE(sub->count() > 0);
|
||||
|
@ -24,12 +24,11 @@ namespace osquery {
|
||||
|
||||
const int kDelayUS = 2000;
|
||||
const int kTimeoutUS = 1000000;
|
||||
const std::string kTestManagerSocket = kTestWorkingDirectory + "test.em";
|
||||
|
||||
class ExtensionsTest : public testing::Test {
|
||||
protected:
|
||||
void SetUp() {
|
||||
socket_path = kTestManagerSocket + std::to_string(rand());
|
||||
socket_path = kTestWorkingDirectory + "test.em" + std::to_string(rand());
|
||||
remove(socket_path);
|
||||
if (pathExists(socket_path).ok()) {
|
||||
throw std::domain_error("Cannot test sockets: " + socket_path);
|
||||
|
@ -30,11 +30,16 @@ namespace pt = boost::property_tree;
|
||||
namespace osquery {
|
||||
|
||||
FLAG(string, logger_tls_endpoint, "", "TLS/HTTPS endpoint for results logging");
|
||||
|
||||
FLAG(int32,
|
||||
logger_tls_period,
|
||||
4,
|
||||
"Seconds between flushing logs over TLS/HTTPS");
|
||||
|
||||
DECLARE_bool(tls_secret_always);
|
||||
DECLARE_string(tls_enroll_override);
|
||||
DECLARE_bool(tls_node_api);
|
||||
|
||||
/**
|
||||
* @brief Control the number of backing-store buffered logs.
|
||||
*
|
||||
@ -227,7 +232,20 @@ inline void clearLogs(bool results, const std::vector<std::string>& indexes) {
|
||||
}
|
||||
|
||||
void TLSLogForwarderRunner::start() {
|
||||
auto uri = "https://" + FLAGS_tls_hostname + FLAGS_logger_tls_endpoint;
|
||||
auto uri = "https://" + FLAGS_tls_hostname;
|
||||
if (FLAGS_tls_node_api) {
|
||||
// The TLS API should treat clients as nodes.
|
||||
// In this case the node_key acts as an identifier (node) and the endpoints
|
||||
// (if provided) are treated as edges from the nodes.
|
||||
uri += "/" + node_key_;
|
||||
}
|
||||
uri += FLAGS_logger_tls_endpoint;
|
||||
|
||||
// Some APIs may require persistent identification.
|
||||
if (FLAGS_tls_secret_always) {
|
||||
uri += ((uri.find("?") != std::string::npos) ? "&" : "?") +
|
||||
FLAGS_tls_enroll_override + "=" + getEnrollSecret();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
// Get a list of all the buffered log items.
|
||||
|
@ -61,11 +61,11 @@ std::string getNodeKey(const std::string& enroll_plugin, bool force) {
|
||||
return node_key;
|
||||
}
|
||||
|
||||
std::string getEnrollSecret() {
|
||||
const std::string& getEnrollSecret() {
|
||||
static std::string enrollment_secret;
|
||||
|
||||
if (enrollment_secret.size() == 0) {
|
||||
// Secret has not been read
|
||||
// Secret has not been read yet.
|
||||
if (FLAGS_enroll_secret_path != "") {
|
||||
osquery::readFile(FLAGS_enroll_secret_path, enrollment_secret);
|
||||
boost::trim(enrollment_secret);
|
||||
|
@ -28,6 +28,18 @@ CLI_FLAG(string,
|
||||
"",
|
||||
"TLS/HTTPS endpoint for client enrollment");
|
||||
|
||||
/// Undocumented feature for TLS access token passing.
|
||||
HIDDEN_FLAG(bool,
|
||||
tls_secret_always,
|
||||
false,
|
||||
"Include TLS enroll secret in every request");
|
||||
|
||||
/// Undocumented feature to override TLS enrollment key name.
|
||||
HIDDEN_FLAG(string,
|
||||
tls_enroll_override,
|
||||
"enroll_secret",
|
||||
"Override the TLS enroll secret key name");
|
||||
|
||||
class TLSEnrollPlugin : public EnrollPlugin {
|
||||
private:
|
||||
/// Enroll called, return cached key or if no key cached, call requestKey.
|
||||
@ -47,6 +59,11 @@ REGISTER(TLSEnrollPlugin, "enroll", "tls");
|
||||
std::string TLSEnrollPlugin::enroll(bool force) {
|
||||
// If no node secret has been negotiated, try a TLS request.
|
||||
auto uri = "https://" + FLAGS_tls_hostname + FLAGS_enroll_tls_endpoint;
|
||||
if (FLAGS_tls_secret_always) {
|
||||
uri += ((uri.find("?") != std::string::npos) ? "&" : "?") +
|
||||
FLAGS_tls_enroll_override + "=" + getEnrollSecret();
|
||||
}
|
||||
|
||||
if (node_secret_key_.size() == 0 || force) {
|
||||
VLOG(1) << "TLSEnrollPlugin requesting a node enroll key from: " << uri;
|
||||
for (size_t i = 1; i <= ENROLL_TLS_MAX_ATTEMPTS; i++) {
|
||||
@ -67,7 +84,8 @@ std::string TLSEnrollPlugin::enroll(bool force) {
|
||||
Status TLSEnrollPlugin::requestKey(const std::string& uri) {
|
||||
// Read the optional enrollment secret data (sent with an enrollment request).
|
||||
boost::property_tree::ptree params;
|
||||
params.put<std::string>("enroll_secret", getEnrollSecret());
|
||||
params.put<std::string>(FLAGS_tls_enroll_override, getEnrollSecret());
|
||||
params.put<std::string>("host_identifier", getHostIdentifier());
|
||||
|
||||
auto request = Request<TLSTransport, JSONSerializer>(uri);
|
||||
auto status = request.call(params);
|
||||
@ -82,13 +100,16 @@ Status TLSEnrollPlugin::requestKey(const std::string& uri) {
|
||||
return status;
|
||||
}
|
||||
|
||||
// Support multiple response keys as a node key (identifier).
|
||||
if (recv.count("node_key") > 0) {
|
||||
// Set the enroll key, should be stored in the RocksDB cache.
|
||||
// TODO: Store this response key in RocksDB.
|
||||
node_secret_key_ = recv.get<std::string>("node_key", "");
|
||||
return Status(0, "OK");
|
||||
} else {
|
||||
node_secret_key_ = recv.get("node_key", "");
|
||||
} else if (recv.count("id") > 0) {
|
||||
node_secret_key_ = recv.get("id", "");
|
||||
}
|
||||
|
||||
if (node_secret_key_.size() == 0) {
|
||||
return Status(1, "No enrollment key returned from TLS enroll plugin");
|
||||
}
|
||||
return Status(0, "OK");
|
||||
}
|
||||
}
|
||||
|
@ -13,11 +13,15 @@
|
||||
#include <osquery/core.h>
|
||||
#include <osquery/database.h>
|
||||
#include <osquery/enroll.h>
|
||||
#include <osquery/filesystem.h>
|
||||
#include <osquery/flags.h>
|
||||
|
||||
#include "osquery/core/test_util.h"
|
||||
|
||||
namespace osquery {
|
||||
|
||||
DECLARE_string(enroll_secret_path);
|
||||
|
||||
class EnrollTests : public testing::Test {
|
||||
public:
|
||||
void SetUp() {
|
||||
@ -48,6 +52,23 @@ class SimpleEnrollPlugin : public EnrollPlugin {
|
||||
// Register our simple enroll plugin.
|
||||
REGISTER(SimpleEnrollPlugin, "enroll", "test_simple");
|
||||
|
||||
TEST_F(EnrollTests, test_enroll_secret_retrieval) {
|
||||
// Write an example secret (deploy key).
|
||||
FLAGS_enroll_secret_path = kTestWorkingDirectory + "secret.txt";
|
||||
writeTextFile(FLAGS_enroll_secret_path, "test_secret\n", 0600, false);
|
||||
// Make sure the file content was read and trimmed.
|
||||
auto secret = getEnrollSecret();
|
||||
EXPECT_EQ(secret, "test_secret");
|
||||
|
||||
// Now change the file path.
|
||||
FLAGS_enroll_secret_path = kTestWorkingDirectory + "not_a_secret.txt";
|
||||
// And for good measure, write some content.
|
||||
writeTextFile(FLAGS_enroll_secret_path, "test_not_a_secret", 0600, false);
|
||||
// The enrollment key should not update.
|
||||
secret = getEnrollSecret();
|
||||
EXPECT_EQ(secret, "test_secret");
|
||||
}
|
||||
|
||||
TEST_F(EnrollTests, test_enroll_key_retrieval) {
|
||||
FLAGS_disable_enrollment = true;
|
||||
// Without enrollment, and with an empty nodeKey storage value, no node key
|
||||
|
@ -89,6 +89,11 @@ class Transport {
|
||||
return response_params_;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void setOption(const std::string& name, const T& value) {
|
||||
options_.put(name, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Virtual destructor
|
||||
*/
|
||||
@ -106,6 +111,9 @@ class Transport {
|
||||
|
||||
/// storage for response parameters
|
||||
boost::property_tree::ptree response_params_;
|
||||
|
||||
/// options from request call (use defined by specific transport)
|
||||
boost::property_tree::ptree options_;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -252,6 +260,11 @@ class Request {
|
||||
return transport_->getResponseStatus();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void setOption(const std::string& name, const T& value) {
|
||||
transport_->setOption(name, value);
|
||||
}
|
||||
|
||||
private:
|
||||
/// storage for the resource destination
|
||||
std::string destination_;
|
||||
|
@ -55,6 +55,16 @@ CLI_FLAG(string,
|
||||
"",
|
||||
"Optional path to a TLS client-auth PEM private key");
|
||||
|
||||
#if defined(DEBUG)
|
||||
HIDDEN_FLAG(bool,
|
||||
tls_allow_unsafe,
|
||||
false,
|
||||
"Allow TLS server certificate trust failures");
|
||||
#endif
|
||||
|
||||
/// Undocumented feature to override TLS endpoints.
|
||||
HIDDEN_FLAG(bool, tls_node_api, false, "Use node key as TLS endpoints");
|
||||
|
||||
TLSTransport::TLSTransport() : verify_peer_(true) {
|
||||
if (FLAGS_tls_server_certs.size() > 0) {
|
||||
server_certificate_file_ = FLAGS_tls_server_certs;
|
||||
@ -85,6 +95,13 @@ http::client TLSTransport::getClient() {
|
||||
ciphers += ":!CBC:!SHA";
|
||||
#endif
|
||||
|
||||
#if defined(DEBUG)
|
||||
// Configuration may allow unsafe TLS testing if compiled as a debug target.
|
||||
if (FLAGS_tls_allow_unsafe) {
|
||||
options.always_verify_peer(false);
|
||||
}
|
||||
#endif
|
||||
|
||||
options.openssl_ciphers(ciphers);
|
||||
options.openssl_options(SSL_OP_NO_SSLv3 | SSL_OP_NO_SSLv2 | SSL_OP_ALL);
|
||||
|
||||
@ -153,9 +170,20 @@ Status TLSTransport::sendRequest(const std::string& params) {
|
||||
http::client::request r(destination_);
|
||||
decorateRequest(r);
|
||||
|
||||
// Allow request calls to override the default HTTP POST verb.
|
||||
HTTPVerb verb = HTTP_POST;
|
||||
if (options_.count("verb") > 0) {
|
||||
verb = (HTTPVerb)options_.get<int>("verb", HTTP_POST);
|
||||
}
|
||||
|
||||
try {
|
||||
VLOG(1) << "TLS/HTTPS POST request to URI: " << destination_;
|
||||
response_ = client.post(r, params);
|
||||
VLOG(1) << "TLS/HTTPS " << ((verb == HTTP_POST) ? "POST" : "PUT")
|
||||
<< " request to URI: " << destination_;
|
||||
if (verb == HTTP_POST) {
|
||||
response_ = client.post(r, params);
|
||||
} else {
|
||||
response_ = client.put(r, params);
|
||||
}
|
||||
response_status_ =
|
||||
serializer_->deserialize(body(response_), response_params_);
|
||||
} catch (const std::exception& e) {
|
||||
|
@ -37,17 +37,25 @@ DECLARE_string(tls_client_cert);
|
||||
/// TLS server hostname.
|
||||
DECLARE_string(tls_hostname);
|
||||
|
||||
/**
|
||||
* @brief HTTP verb selections.
|
||||
*/
|
||||
enum HTTPVerb {
|
||||
HTTP_POST = 0,
|
||||
HTTP_PUT,
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief HTTPS (TLS) transport.
|
||||
*/
|
||||
class TLSTransport : public Transport {
|
||||
public:
|
||||
|
||||
/**
|
||||
* @brief Send a simple request to the destination with no parameters
|
||||
*
|
||||
* @return An instance of osquery::Status indicating the success or failure
|
||||
* of the operation
|
||||
* @return A status indicating socket, network, or transport success/error.
|
||||
* Return code (1) for general connectivity problems, return code (2) for TLS
|
||||
* specific errors.
|
||||
*/
|
||||
Status sendRequest();
|
||||
|
||||
@ -56,8 +64,9 @@ class TLSTransport : public Transport {
|
||||
*
|
||||
* @param params A string representing the serialized parameters
|
||||
*
|
||||
* @return An instance of osquery::Status indicating the success or failure
|
||||
* of the operation
|
||||
* @return A status indicating socket, network, or transport success/error.
|
||||
* Return code (1) for general connectivity problems, return code (2) for TLS
|
||||
* specific errors.
|
||||
*/
|
||||
Status sendRequest(const std::string& params);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user