diff --git a/docs/wiki/deployment/remote.md b/docs/wiki/deployment/remote.md index b95763d9..56de0bab 100644 --- a/docs/wiki/deployment/remote.md +++ b/docs/wiki/deployment/remote.md @@ -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. diff --git a/include/osquery/core.h b/include/osquery/core.h index e0c68690..eae927b5 100644 --- a/include/osquery/core.h +++ b/include/osquery/core.h @@ -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. * diff --git a/include/osquery/enroll.h b/include/osquery/enroll.h index 31012e11..363cf4df 100644 --- a/include/osquery/enroll.h +++ b/include/osquery/enroll.h @@ -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. diff --git a/osquery/config/plugins/tls.cpp b/osquery/config/plugins/tls.cpp index 43d1cade..780a8260 100644 --- a/osquery/config/plugins/tls.cpp +++ b/osquery/config/plugins/tls.cpp @@ -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("node_key", getNodeKey("tls")); - auto request = Request(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("node_key", node_key); + } + + // Again check for GET to call with/without parameters. + auto request = Request(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& 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 { diff --git a/osquery/core/system.cpp b/osquery/core/system.cpp index 59791415..9113aa6e 100644 --- a/osquery/core/system.cpp +++ b/osquery/core/system.cpp @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -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))); diff --git a/osquery/core/test_util.cpp b/osquery/core/test_util.cpp index f44e419c..a33ca378 100644 --- a/osquery/core/test_util.cpp +++ b/osquery/core/test_util.cpp @@ -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; diff --git a/osquery/core/test_util.h b/osquery/core/test_util.h index b7f3f620..6f55eace 100644 --- a/osquery/core/test_util.h +++ b/osquery/core/test_util.h @@ -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"; diff --git a/osquery/dispatcher/scheduler.cpp b/osquery/dispatcher/scheduler.cpp index 58e56f24..90a11d55 100644 --- a/osquery/dispatcher/scheduler.cpp +++ b/osquery/dispatcher/scheduler.cpp @@ -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 = ""; - } + 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; diff --git a/osquery/events/linux/tests/inotify_tests.cpp b/osquery/events/linux/tests/inotify_tests.cpp index a76989ca..fe5e54a5 100644 --- a/osquery/events/linux/tests/inotify_tests.cpp +++ b/osquery/events/linux/tests/inotify_tests.cpp @@ -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(); 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 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(); @@ -225,7 +236,7 @@ TEST_F(INotifyTests, test_inotify_run) { // Create a subscriptioning context auto mc = std::make_shared(); - 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(); 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(); 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); diff --git a/osquery/extensions/tests/extensions_tests.cpp b/osquery/extensions/tests/extensions_tests.cpp index a76022c5..30456978 100644 --- a/osquery/extensions/tests/extensions_tests.cpp +++ b/osquery/extensions/tests/extensions_tests.cpp @@ -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); diff --git a/osquery/logger/plugins/tls.cpp b/osquery/logger/plugins/tls.cpp index 742f8611..cea2715d 100644 --- a/osquery/logger/plugins/tls.cpp +++ b/osquery/logger/plugins/tls.cpp @@ -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& 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. diff --git a/osquery/remote/enroll/enroll.cpp b/osquery/remote/enroll/enroll.cpp index 82d26a5b..ed1d2970 100644 --- a/osquery/remote/enroll/enroll.cpp +++ b/osquery/remote/enroll/enroll.cpp @@ -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); diff --git a/osquery/remote/enroll/plugins/tls.cpp b/osquery/remote/enroll/plugins/tls.cpp index 823d870a..5a3cc478 100644 --- a/osquery/remote/enroll/plugins/tls.cpp +++ b/osquery/remote/enroll/plugins/tls.cpp @@ -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("enroll_secret", getEnrollSecret()); + params.put(FLAGS_tls_enroll_override, getEnrollSecret()); + params.put("host_identifier", getHostIdentifier()); auto request = Request(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("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"); } } diff --git a/osquery/remote/enroll/tests/enroll_tests.cpp b/osquery/remote/enroll/tests/enroll_tests.cpp index 9e98f3e3..4746c915 100644 --- a/osquery/remote/enroll/tests/enroll_tests.cpp +++ b/osquery/remote/enroll/tests/enroll_tests.cpp @@ -13,11 +13,15 @@ #include #include #include +#include +#include #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 diff --git a/osquery/remote/requests.h b/osquery/remote/requests.h index 43e04a05..312edf91 100644 --- a/osquery/remote/requests.h +++ b/osquery/remote/requests.h @@ -89,6 +89,11 @@ class Transport { return response_params_; } + template + 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 + void setOption(const std::string& name, const T& value) { + transport_->setOption(name, value); + } + private: /// storage for the resource destination std::string destination_; diff --git a/osquery/remote/transports/tls.cpp b/osquery/remote/transports/tls.cpp index cd97591d..276a8d86 100644 --- a/osquery/remote/transports/tls.cpp +++ b/osquery/remote/transports/tls.cpp @@ -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("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) { diff --git a/osquery/remote/transports/tls.h b/osquery/remote/transports/tls.h index 91348a68..1e6c6b01 100644 --- a/osquery/remote/transports/tls.h +++ b/osquery/remote/transports/tls.h @@ -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);