Skip to content

Commit

Permalink
Add new table vscode_extensions (osquery#8150)
Browse files Browse the repository at this point in the history
Co-authored-by: Zach Wasserman <zach@fleetdm.com>
Co-authored-by: Stefano Bonicatti <stefano.bonicatti@gmail.com>
  • Loading branch information
3 people authored Dec 20, 2023
1 parent 52974c7 commit c396d07
Show file tree
Hide file tree
Showing 6 changed files with 213 additions and 0 deletions.
1 change: 1 addition & 0 deletions osquery/tables/applications/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ function(generateOsqueryTablesApplications)
chrome/chrome_extension_content_scripts.cpp
chrome/utils.cpp
browser_firefox.cpp
vscode_extensions.cpp
)

if(DEFINED PLATFORM_POSIX)
Expand Down
128 changes: 128 additions & 0 deletions osquery/tables/applications/vscode_extensions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* Copyright (c) 2014-present, The osquery authors
*
* This source code is licensed as defined by the LICENSE file found in the
* root directory of this source tree.
*
* SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
*/

#include <set>
#include <string>

#include <boost/filesystem.hpp>

#include <osquery/core/tables.h>
#include <osquery/filesystem/filesystem.h>
#include <osquery/logger/logger.h>
#include <osquery/tables/system/system_utils.h>
#include <osquery/utils/json/json.h>

namespace fs = boost::filesystem;

namespace osquery {
namespace tables {

void genReadJSONAndAddExtensionRows(const std::string& uid,
const std::string& path,
QueryData& results) {
std::string json;
if (!readFile(path, json).ok()) {
LOG(INFO) << "Could not read vscode extensions.json from " << path;
return;
}

auto doc = JSON::newArray();
if (!doc.fromString(json) || !doc.doc().IsArray()) {
LOG(WARNING) << "Could not parse vscode extensions.json from " << path;
return;
}

for (const rapidjson::Value& extension : doc.doc().GetArray()) {
if (!(extension.IsObject() && extension.HasMember("identifier") &&
extension.HasMember("metadata") && extension.HasMember("location"))) {
LOG(WARNING) << "Extension entry missing expected subkeys in " << path;
continue;
}
const rapidjson::Value& identifier = extension["identifier"];
const rapidjson::Value& metadata = extension["metadata"];
const rapidjson::Value& location = extension["location"];
if (!(identifier.IsObject() && metadata.IsObject() &&
location.IsObject())) {
LOG(WARNING) << "Extension subkeys are not objects in " << path;
continue;
}

Row r;
r["uid"] = uid;

rapidjson::Value::ConstMemberIterator it = identifier.FindMember("id");
if (it != identifier.MemberEnd() && it->value.IsString()) {
r["name"] = it->value.GetString();
}

it = identifier.FindMember("uuid");
if (it != identifier.MemberEnd() && it->value.IsString()) {
r["uuid"] = it->value.GetString();
}

it = extension.FindMember("version");
if (it != extension.MemberEnd() && it->value.IsString()) {
r["version"] = it->value.GetString();
}

it = location.FindMember("path");
if (it != location.MemberEnd() && it->value.IsString()) {
r["path"] = it->value.GetString();
}

it = metadata.FindMember("publisherDisplayName");
if (it != metadata.MemberEnd() && it->value.IsString()) {
r["publisher"] = it->value.GetString();
}

it = metadata.FindMember("publisherId");
if (it != metadata.MemberEnd() && it->value.IsString()) {
r["publisher_id"] = it->value.GetString();
}

it = metadata.FindMember("installedTimestamp");
if (it != metadata.MemberEnd() && it->value.IsInt64()) {
r["installed_at"] = INTEGER(it->value.GetInt64());
}

it = metadata.FindMember("isPreReleaseVersion");
if (it != metadata.MemberEnd() && it->value.IsBool()) {
r["prerelease"] = INTEGER(it->value.GetBool() ? "1" : "0");
}

results.push_back(r);
}
}

QueryData genVSCodeExtensions(QueryContext& context) {
QueryData results;

// find vscode config directories
std::set<std::pair<std::string, fs::path>> confDirs;
auto users = usersFromContext(context);
for (const auto& row : users) {
auto uid = row.find("uid");
auto directory = row.find("directory");
if (directory == row.end() || uid == row.end()) {
continue;
}
confDirs.insert(
{uid->second, fs::path(directory->second) / ".vscode-server"});
confDirs.insert({uid->second, fs::path(directory->second) / ".vscode"});
}

for (const auto& confDir : confDirs) {
auto path = confDir.second / "extensions" / "extensions.json";
genReadJSONAndAddExtensionRows(confDir.first, path.string(), results);
}

return results;
}
} // namespace tables
} // namespace osquery
1 change: 1 addition & 0 deletions specs/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ function(generateNativeTables)

set(platform_dependent_spec_files
"arp_cache.table:linux,macos,windows"
"vscode_extensions.table:linux,macos,windows"
"cpu_info.table:linux,macos,windows"
"darwin/account_policy_data.table:macos"
"darwin/ad_config.table:macos"
Expand Down
19 changes: 19 additions & 0 deletions specs/vscode_extensions.table
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
table_name("vscode_extensions")
description("Lists all vscode extensions.")
schema([
Column("name", TEXT, "Extension Name"),
Column("uuid", TEXT, "Extension UUID"),
Column("version", TEXT, "Extension version"),
Column("path", TEXT, "Extension path"),
Column("publisher", TEXT, "Publisher Name"),
Column("publisher_id", TEXT, "Publisher ID"),
Column("installed_at", BIGINT, "Installed Timestamp"),
Column("prerelease", INTEGER, "Pre release version"),
Column("uid", BIGINT, "The local user that owns the plugin",
additional=True),
])
attributes(user_data=True)
implementation("applications/vscode_extensions@genVSCodeExtensions")
examples([
"select * from vscode_extensions",
])
1 change: 1 addition & 0 deletions tests/integration/tables/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ function(generateTestsIntegrationTablesTestsTest)
device_hash.cpp
device_partitions.cpp
ycloud_instance_metadata.cpp
vscode_extensions.cpp
)

if(NOT PLATFORM_LINUX OR OSQUERY_BUILD_ROOT_TESTS)
Expand Down
63 changes: 63 additions & 0 deletions tests/integration/tables/vscode_extensions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/**
* Copyright (c) 2014-present, The osquery authors
*
* This source code is licensed as defined by the LICENSE file found in the
* root directory of this source tree.
*
* SPDX-License-Identifier: (Apache-2.0 OR GPL-2.0-only)
*/

// Sanity check integration test for vscode_extensions
// Spec file: specs/vscode_extensions.table

#include <osquery/dispatcher/dispatcher.h>
#include <osquery/logger/logger.h>
#include <osquery/tests/integration/tables/helper.h>
#include <osquery/tests/test_util.h>

namespace osquery {
namespace table_tests {

class vscodeExtensions : public testing::Test {
protected:
void SetUp() override {
setUpEnvironment();
}

#ifdef OSQUERY_WINDOWS
static void SetUpTestSuite() {
initUsersAndGroupsServices(true, false);
}

static void TearDownTestSuite() {
Dispatcher::stopServices();
Dispatcher::joinServices();
deinitUsersAndGroupsServices(true, false);
Dispatcher::instance().resetStopping();
}
#endif
};

TEST_F(vscodeExtensions, test_sanity) {
auto const data = execute_query("select * from vscode_extensions");
if (data.empty()) {
LOG(WARNING)
<< "Empty results of query from 'vscode_extensions', assume there "
"is no vscode on the system";
return;
}

ValidationMap row_map = {
{"id", NormalType},
{"version", NormalType},
{"path", NormalType},
{"publisher", NormalType},
{"installed_at", NonNegativeInt},
{"prerelease", Bool},
{"uid", NonNegativeInt},
};
validate_rows(data, row_map);
}

} // namespace table_tests
} // namespace osquery

0 comments on commit c396d07

Please sign in to comment.