From 4cb4ee95e18fb0f743ee88c8649944e1e5516ee5 Mon Sep 17 00:00:00 2001 From: Dan Fuhry Date: Wed, 2 Jul 2025 18:03:17 -0400 Subject: [PATCH] Initial commit --- .bazelversion | 1 + .gitignore | 4 + BUILD.bazel | 3 + LICENSE | 29 ++++ MODULE.bazel | 25 +++ MODULE.bazel.lock | 240 +++++++++++++++++++++++++++++ README.md | 69 +++++++++ cmd/flatpak/BUILD.bazel | 18 +++ cmd/flatpak/main.go | 10 ++ cmd/pacman/BUILD.bazel | 18 +++ cmd/pacman/main.go | 15 ++ extcommon/BUILD.bazel | 17 +++ extcommon/extcommon.go | 53 +++++++ extcommon/util.go | 79 ++++++++++ flatpak/BUILD.bazel | 17 +++ flatpak/data.go | 152 +++++++++++++++++++ flatpak/plugin.go | 44 ++++++ flatpak/registry.go | 329 ++++++++++++++++++++++++++++++++++++++++ go.mod | 24 +++ go.sum | 116 ++++++++++++++ pacman/BUILD.bazel | 16 ++ pacman/pacman.go | 197 ++++++++++++++++++++++++ pacman/typed_columns.go | 168 ++++++++++++++++++++ 23 files changed, 1644 insertions(+) create mode 100644 .bazelversion create mode 100644 .gitignore create mode 100644 BUILD.bazel create mode 100644 LICENSE create mode 100644 MODULE.bazel create mode 100644 MODULE.bazel.lock create mode 100644 README.md create mode 100644 cmd/flatpak/BUILD.bazel create mode 100644 cmd/flatpak/main.go create mode 100644 cmd/pacman/BUILD.bazel create mode 100644 cmd/pacman/main.go create mode 100644 extcommon/BUILD.bazel create mode 100644 extcommon/extcommon.go create mode 100644 extcommon/util.go create mode 100644 flatpak/BUILD.bazel create mode 100644 flatpak/data.go create mode 100644 flatpak/plugin.go create mode 100644 flatpak/registry.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pacman/BUILD.bazel create mode 100644 pacman/pacman.go create mode 100644 pacman/typed_columns.go diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 0000000..2b0aa21 --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +8.2.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b5b830b --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +bazel-bin +bazel-osquery +bazel-out +bazel-testlogs diff --git a/BUILD.bazel b/BUILD.bazel new file mode 100644 index 0000000..abccfc7 --- /dev/null +++ b/BUILD.bazel @@ -0,0 +1,3 @@ +load("@gazelle//:def.bzl", "gazelle") + +gazelle(name = "gazelle") diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..87b3f0e --- /dev/null +++ b/LICENSE @@ -0,0 +1,29 @@ +Copyright 2025 Dan Fuhry + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in + the documentation and/or other materials provided with the + distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +“AS IS” AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/MODULE.bazel b/MODULE.bazel new file mode 100644 index 0000000..2420ab6 --- /dev/null +++ b/MODULE.bazel @@ -0,0 +1,25 @@ +bazel_dep( + name = "rules_go", + version = "0.55.1", +) + +bazel_dep( + name = "gazelle", + version = "0.44.0", +) + +go_sdk = use_extension("@rules_go//go:extensions.bzl", "go_sdk") +go_sdk.from_file(go_mod = "//:go.mod") + +go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps") +go_deps.from_file(go_mod = "//:go.mod") + +use_repo( + go_deps, + "com_github_gobwas_glob", + "com_github_osquery_osquery_go", + "com_github_chrisportman_go_gvariant", + "com_github_hashicorp_golang_lru_v2", + "com_github_linuxdeepin_go_lib", + "com_github_jguer_go_alpm_v2", +) \ No newline at end of file diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock new file mode 100644 index 0000000..7618ad7 --- /dev/null +++ b/MODULE.bazel.lock @@ -0,0 +1,240 @@ +{ + "lockFileVersion": 18, + "registryFileHashes": { + "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", + "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", + "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", + "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", + "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", + "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", + "https://bcr.bazel.build/modules/bazel_features/1.1.0/MODULE.bazel": "cfd42ff3b815a5f39554d97182657f8c4b9719568eb7fded2b9135f084bf760b", + "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", + "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", + "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", + "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", + "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", + "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", + "https://bcr.bazel.build/modules/bazel_features/1.21.0/source.json": "3e8379efaaef53ce35b7b8ba419df829315a880cb0a030e5bb45c96d6d5ecb5f", + "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", + "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", + "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", + "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", + "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", + "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", + "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", + "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", + "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", + "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", + "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", + "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", + "https://bcr.bazel.build/modules/gazelle/0.32.0/MODULE.bazel": "b499f58a5d0d3537f3cf5b76d8ada18242f64ec474d8391247438bf04f58c7b8", + "https://bcr.bazel.build/modules/gazelle/0.33.0/MODULE.bazel": "a13a0f279b462b784fb8dd52a4074526c4a2afe70e114c7d09066097a46b3350", + "https://bcr.bazel.build/modules/gazelle/0.34.0/MODULE.bazel": "abdd8ce4d70978933209db92e436deb3a8b737859e9354fb5fd11fb5c2004c8a", + "https://bcr.bazel.build/modules/gazelle/0.36.0/MODULE.bazel": "e375d5d6e9a6ca59b0cb38b0540bc9a05b6aa926d322f2de268ad267a2ee74c0", + "https://bcr.bazel.build/modules/gazelle/0.44.0/MODULE.bazel": "fd3177ca0938da57a1e416cad3f39b9c4334defbc717e89aba9d9ddbbb0341da", + "https://bcr.bazel.build/modules/gazelle/0.44.0/source.json": "7fb65ef9c1ce470d099ca27fd478673d9d64c844af28d0d472b0874c7d590cb6", + "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", + "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", + "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", + "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", + "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", + "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/MODULE.bazel": "fb8d25550742674d63d7b250063d4580ca530499f045d70748b1b142081ebb92", + "https://bcr.bazel.build/modules/package_metadata/0.0.2/source.json": "e53a759a72488d2c0576f57491ef2da0cf4aab05ac0997314012495935531b73", + "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", + "https://bcr.bazel.build/modules/platforms/0.0.10/source.json": "f22828ff4cf021a6b577f1bf6341cb9dcd7965092a439f64fc1bb3b7a5ae4bd5", + "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", + "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", + "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", + "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", + "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", + "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", + "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", + "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2.bcr.1/MODULE.bazel": "52f4126f63a2f0bbf36b99c2a87648f08467a4eaf92ba726bc7d6a500bbf770c", + "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", + "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", + "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", + "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", + "https://bcr.bazel.build/modules/protobuf/3.19.2/MODULE.bazel": "532ffe5f2186b69fdde039efe6df13ba726ff338c6bc82275ad433013fa10573", + "https://bcr.bazel.build/modules/protobuf/3.19.6/MODULE.bazel": "9233edc5e1f2ee276a60de3eaa47ac4132302ef9643238f23128fea53ea12858", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", + "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", + "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", + "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", + "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", + "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", + "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", + "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", + "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", + "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", + "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", + "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/MODULE.bazel": "2ae1d8f4238ec67d7185d8861cb0a2cdf4bc608697c331b95bf990e69b62e64a", + "https://bcr.bazel.build/modules/rules_cc/0.0.17/source.json": "4db99b3f55c90ab28d14552aa0632533e3e8e5e9aea0f5c24ac0014282c2a7c5", + "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", + "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", + "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", + "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", + "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", + "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", + "https://bcr.bazel.build/modules/rules_go/0.41.0/MODULE.bazel": "55861d8e8bb0e62cbd2896f60ff303f62ffcb0eddb74ecb0e5c0cbe36fc292c8", + "https://bcr.bazel.build/modules/rules_go/0.42.0/MODULE.bazel": "8cfa875b9aa8c6fce2b2e5925e73c1388173ea3c32a0db4d2b4804b453c14270", + "https://bcr.bazel.build/modules/rules_go/0.46.0/MODULE.bazel": "3477df8bdcc49e698b9d25f734c4f3a9f5931ff34ee48a2c662be168f5f2d3fd", + "https://bcr.bazel.build/modules/rules_go/0.51.0/MODULE.bazel": "b6920f505935bfd69381651c942496d99b16e2a12f3dd5263b90ded16f3b4d0f", + "https://bcr.bazel.build/modules/rules_go/0.55.1/MODULE.bazel": "a57a6fc59a74326c0b440d07cca209edf13c7d1a641e48cfbeab56e79f873609", + "https://bcr.bazel.build/modules/rules_go/0.55.1/source.json": "827a740c8959c9d20616889e7746cde4dcc6ee80d25146943627ccea0736328f", + "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", + "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", + "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", + "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", + "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", + "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", + "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", + "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", + "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", + "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", + "https://bcr.bazel.build/modules/rules_java/8.11.0/MODULE.bazel": "c3d280bc5ff1038dcb3bacb95d3f6b83da8dd27bba57820ec89ea4085da767ad", + "https://bcr.bazel.build/modules/rules_java/8.11.0/source.json": "302b52a39259a85aa06ca3addb9787864ca3e03b432a5f964ea68244397e7544", + "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", + "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", + "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", + "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", + "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", + "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", + "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", + "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", + "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", + "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", + "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", + "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", + "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", + "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", + "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", + "https://bcr.bazel.build/modules/rules_proto/6.0.0/MODULE.bazel": "b531d7f09f58dce456cd61b4579ce8c86b38544da75184eadaf0a7cb7966453f", + "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", + "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", + "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", + "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", + "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", + "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", + "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", + "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", + "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", + "https://bcr.bazel.build/modules/rules_python/0.40.0/source.json": "939d4bd2e3110f27bfb360292986bb79fd8dcefb874358ccd6cdaa7bda029320", + "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/MODULE.bazel": "de4402cd12f4cc8fda2354fce179fdb068c0b9ca1ec2d2b17b3e21b24c1a937b", + "https://bcr.bazel.build/modules/rules_shell/0.3.0/source.json": "c55ed591aa5009401ddf80ded9762ac32c358d2517ee7820be981e2de9756cf3", + "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", + "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", + "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", + "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", + "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", + "https://bcr.bazel.build/modules/stardoc/0.7.1/source.json": "b6500ffcd7b48cd72c29bb67bcac781e12701cc0d6d55d266a652583cfcdab01", + "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", + "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", + "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d", + "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" + }, + "selectedYankedVersions": {}, + "moduleExtensions": { + "@@platforms//host:extension.bzl%host_platform": { + "general": { + "bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=", + "usagesDigest": "SeQiIN/f8/Qt9vYQk7qcXp4I4wJeEC0RnQDiaaJ4tb8=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "host_platform": { + "repoRuleId": "@@platforms//host:extension.bzl%host_platform_repo", + "attributes": {} + } + }, + "recordedRepoMappingEntries": [] + } + }, + "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { + "general": { + "bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=", + "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", + "recordedFileInputs": {}, + "recordedDirentsInputs": {}, + "envVariables": {}, + "generatedRepoSpecs": { + "com_github_jetbrains_kotlin_git": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", + "attributes": { + "urls": [ + "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" + ], + "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" + } + }, + "com_github_jetbrains_kotlin": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", + "attributes": { + "git_repository_name": "com_github_jetbrains_kotlin_git", + "compiler_version": "1.9.23" + } + }, + "com_github_google_ksp": { + "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", + "attributes": { + "urls": [ + "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" + ], + "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", + "strip_version": "1.9.23-1.0.20" + } + }, + "com_github_pinterest_ktlint": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", + "attributes": { + "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", + "urls": [ + "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" + ], + "executable": true + } + }, + "rules_android": { + "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", + "attributes": { + "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", + "strip_prefix": "rules_android-0.1.1", + "urls": [ + "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" + ] + } + } + }, + "recordedRepoMappingEntries": [ + [ + "rules_kotlin+", + "bazel_tools", + "bazel_tools" + ] + ] + } + } + } +} diff --git a/README.md b/README.md new file mode 100644 index 0000000..527ad87 --- /dev/null +++ b/README.md @@ -0,0 +1,69 @@ +# osquery extensions + +Exactly as the name suggests, this is a bunch of table extensions for osquery. + +## How to build + +``` +bazel build //cmd/... +``` + +## How to run + +``` +bazel run //cmd/NAME -- --socket=/path/to/osquery_extensions.sock +``` + +## Plugins + +### `pacman` + +Provides the `pacman_packages` and `pacman_files` tables. + +Schema: + +``` +osquery> .schema pacman_packages +CREATE TABLE pacman_packages( + `name` TEXT, + `version` TEXT, + `description` TEXT, + `arch` TEXT, + `url` TEXT, + `license` TEXT, + `size` BIGINT, + `explicit` INTEGER +); + +osquery> .schema pacman_files +CREATE TABLE pacman_files( + `package` TEXT, + `path` TEXT, + `size` BIGINT +); +``` + +### `flatpak` + +Provides the `flatpak_packages` table. + +Schema: + +``` +osquery> .schema flatpak_packages +CREATE TABLE flatpak_packages( + `id` TEXT, + `type` TEXT, + `name` TEXT, + `version` TEXT, + `hash` TEXT, + `branch` TEXT, + `user` TEXT +); +``` + +## Author/License + +Written by Dan Fuhry + +License: [BSD 3-clause](LICENSE) diff --git a/cmd/flatpak/BUILD.bazel b/cmd/flatpak/BUILD.bazel new file mode 100644 index 0000000..74bec19 --- /dev/null +++ b/cmd/flatpak/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "flatpak_lib", + srcs = ["main.go"], + importpath = "go.fuhry.dev/osquery/flatpak/cmd/flatpak", + visibility = ["//visibility:private"], + deps = [ + "//extcommon", + "//flatpak", + ], +) + +go_binary( + name = "flatpak", + embed = [":flatpak_lib"], + visibility = ["//visibility:public"], +) diff --git a/cmd/flatpak/main.go b/cmd/flatpak/main.go new file mode 100644 index 0000000..3a7d64a --- /dev/null +++ b/cmd/flatpak/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "go.fuhry.dev/osquery/extcommon" + "go.fuhry.dev/osquery/flatpak" +) + +func main() { + extcommon.Main("flatpak_packages", flatpak.Schema, flatpak.Generate) +} diff --git a/cmd/pacman/BUILD.bazel b/cmd/pacman/BUILD.bazel new file mode 100644 index 0000000..d5065b0 --- /dev/null +++ b/cmd/pacman/BUILD.bazel @@ -0,0 +1,18 @@ +load("@rules_go//go:def.bzl", "go_binary", "go_library") + +go_library( + name = "pacman_lib", + srcs = ["main.go"], + importpath = "go.fuhry.dev/osquery/pacman/cmd/pacman", + visibility = ["//visibility:private"], + deps = [ + "//extcommon", + "//pacman", + ], +) + +go_binary( + name = "pacman", + embed = [":pacman_lib"], + visibility = ["//visibility:public"], +) diff --git a/cmd/pacman/main.go b/cmd/pacman/main.go new file mode 100644 index 0000000..e7fa616 --- /dev/null +++ b/cmd/pacman/main.go @@ -0,0 +1,15 @@ +package main + +import ( + "go.fuhry.dev/osquery/extcommon" + "go.fuhry.dev/osquery/pacman" +) + +func main() { + extcommon.MainMulti( + "pacman", + extcommon.Tables{ + "pacman_packages": {pacman.PackagesSchema, pacman.PackagesGenerate}, + "pacman_files": {pacman.FilesSchema, pacman.FilesGenerate}, + }) +} diff --git a/extcommon/BUILD.bazel b/extcommon/BUILD.bazel new file mode 100644 index 0000000..5491761 --- /dev/null +++ b/extcommon/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "extcommon", + srcs = [ + "extcommon.go", + "util.go", + ], + importpath = "go.fuhry.dev/osquery/extcommon", + visibility = ["//visibility:public"], + deps = [ + "@com_github_gobwas_glob//:glob", + "@com_github_hashicorp_golang_lru_v2//:golang-lru", + "@com_github_osquery_osquery_go//:osquery-go", + "@com_github_osquery_osquery_go//plugin/table", + ], +) diff --git a/extcommon/extcommon.go b/extcommon/extcommon.go new file mode 100644 index 0000000..8d82a01 --- /dev/null +++ b/extcommon/extcommon.go @@ -0,0 +1,53 @@ +package extcommon + +import ( + "context" + "flag" + "log" + + "github.com/osquery/osquery-go" + "github.com/osquery/osquery-go/plugin/table" +) + +type SchemaFunc = func() []table.ColumnDefinition +type GenerateFunc = func(context.Context, table.QueryContext) ([]map[string]string, error) +type Tables map[string]struct { + Schema SchemaFunc + Generate GenerateFunc +} + +func Main(name string, s SchemaFunc, g GenerateFunc) { + MainMulti(name, Tables{name: {s, g}}) +} + +func MainMulti(pluginName string, t Tables) { + socket := flag.String("socket", "/opt/fleet-orbit/orbit-osquery.em", "path to osquery extensions socket") + flag.Parse() + + if *socket == "" { + log.Fatal("please specify path to the osquery extensions socket") + } + + server, err := osquery.NewExtensionManagerServer(pluginName, *socket) + if err != nil { + log.Fatalf("failed to connect to osquery socket: %v", err) + } + + for name, t := range t { + server.RegisterPlugin(table.NewPlugin(name, t.Schema(), wrapGenerate(name, t.Generate))) + } + log.Printf("running server for plugin %q", pluginName) + if err := server.Run(); err != nil { + log.Fatalf("failed running server: %v", err) + } +} + +func wrapGenerate(name string, g GenerateFunc) GenerateFunc { + return func(ctx context.Context, q table.QueryContext) ([]map[string]string, error) { + out, err := g(ctx, q) + if err != nil { + log.Printf("error when querying plugin %q: %v", name, err) + } + return out, err + } +} diff --git a/extcommon/util.go b/extcommon/util.go new file mode 100644 index 0000000..b3ed653 --- /dev/null +++ b/extcommon/util.go @@ -0,0 +1,79 @@ +package extcommon + +import ( + "log" + "os/user" + "regexp" + + "github.com/gobwas/glob" + lru "github.com/hashicorp/golang-lru/v2" +) + +const lruSize = 128 + +type cacheKey struct { + pattern string + separators string +} + +func newCacheKey(pattern string, separators []rune) cacheKey { + var s string + for _, c := range separators { + s += string(c) + } + return cacheKey{pattern, s} +} + +var globCache *lru.Cache[cacheKey, glob.Glob] +var regexpCache *lru.Cache[string, *regexp.Regexp] + +// CompileGlob compiles a glob, using the LRU cache to retrieve a previously compiled pattern if +// possible. +func CompileGlob(pattern string, separators ...rune) (glob.Glob, error) { + k := newCacheKey(pattern, separators) + + if g, ok := globCache.Get(k); ok { + return g, nil + } + + g, err := glob.Compile(pattern, separators...) + if err != nil { + return nil, err + } + globCache.Add(k, g) + return g, nil +} + +// CompileRegexp compiles a regexp, using the LRU cache to retrieve a previously compiled pattern if +// possible. +func CompileRegexp(pattern string) (*regexp.Regexp, error) { + if r, ok := regexpCache.Get(pattern); ok { + return r, nil + } + + r, err := regexp.Compile(pattern) + if err != nil { + return nil, err + } + regexpCache.Add(pattern, r) + return r, nil +} + +// ListUsers lists local user accounts. +func ListUsers() ([]*user.User, error) { + return nil, nil +} + +func init() { + var err error + + globCache, err = lru.New[cacheKey, glob.Glob](lruSize) + if err != nil { + log.Fatal(err) + } + + regexpCache, err = lru.New[string, *regexp.Regexp](lruSize) + if err != nil { + log.Fatal(err) + } +} diff --git a/flatpak/BUILD.bazel b/flatpak/BUILD.bazel new file mode 100644 index 0000000..69d9d81 --- /dev/null +++ b/flatpak/BUILD.bazel @@ -0,0 +1,17 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "flatpak", + srcs = [ + "data.go", + "plugin.go", + "registry.go", + ], + importpath = "go.fuhry.dev/osquery/flatpak", + visibility = ["//visibility:public"], + deps = [ + "@com_github_chrisportman_go_gvariant//gvariant", + "@com_github_linuxdeepin_go_lib//users/passwd", + "@com_github_osquery_osquery_go//plugin/table", + ], +) diff --git a/flatpak/data.go b/flatpak/data.go new file mode 100644 index 0000000..af5b6ec --- /dev/null +++ b/flatpak/data.go @@ -0,0 +1,152 @@ +package flatpak + +import ( + "bytes" + "encoding/binary" + "errors" + "fmt" + + "github.com/chrisportman/go-gvariant/gvariant" +) + +type DeployData_Metadata map[string]gvariant.Variant + +// struct for gvariant string "(ssasta{sv})" +// source: https://github.com/flatpak/flatpak/blob/0152272d6caf2622536fad8869573a76001a493b/common/flatpak-dir-private.h#L346 +// FLATPAK_DEPLOY_DATA_GVARIANT_FORMAT: +// s - origin +// s - commit +// as - subpaths +// t - installed size +// a{sv} - Metadata +type DeployData struct { + Origin string + Commit string + Subpaths []string + InstalledSize uint64 + Metadata []DeployData_Metadata +} + +const ( + TypeBool = "b" + TypeInt8 = "y" + TypeInt16 = "n" + TypeUint16 = "q" + TypeInt32 = "i" + TypeUint32 = "u" + TypeInt64 = "x" + TypeUint64 = "t" + TypeFloat = "d" + TypeString = "s" + + kAppName = "appdata-name" + kAppVersion = "appdata-version" + kAppSummary = "appdata-summary" + kRuntime = "runtime" + kContentRating = "appdata-content-rating" + kMetadataVersion = "deploy-version" + kTimestamp = "timestamp" + kLicense = "appdata-license" +) + +var ErrSize = errors.New("variant's data field is the wrong size for the specified type") + +// VariantValue decodes the Data field of a Variant to the native Go type. +func VariantValue(v *gvariant.Variant, endianness binary.ByteOrder) (any, error) { + if v == nil { + return nil, errors.New("variant is nil") + } + switch v.Format { + case TypeBool: + return len(v.Data) > 0 && v.Data[0] > 0, nil + case TypeInt8: + if len(v.Data) != 1 { + return nil, ErrSize + } + return int8(v.Data[0]), nil + case TypeInt16: + if len(v.Data) != 2 { + return nil, ErrSize + } + return int16(endianness.Uint16(v.Data)), nil + case TypeUint16: + if len(v.Data) != 2 { + return nil, ErrSize + } + return endianness.Uint16(v.Data), nil + case TypeInt32: + if len(v.Data) != 4 { + return nil, ErrSize + } + return int32(endianness.Uint32(v.Data)), nil + case TypeUint32: + if len(v.Data) != 4 { + return nil, ErrSize + } + return endianness.Uint32(v.Data), nil + case TypeInt64: + if len(v.Data) != 8 { + return nil, ErrSize + } + return int64(endianness.Uint64(v.Data)), nil + case TypeUint64: + if len(v.Data) != 8 { + return nil, ErrSize + } + return endianness.Uint64(v.Data), nil + case TypeFloat: + switch len(v.Data) { + case 4: + var out float32 + buf := bytes.NewBuffer(v.Data) + if err := binary.Read(buf, endianness, &out); err != nil { + return nil, err + } + return out, nil + case 8: + var out float64 + buf := bytes.NewBuffer(v.Data) + if err := binary.Read(buf, endianness, &out); err != nil { + return nil, err + } + return out, nil + default: + return nil, ErrSize + } + case TypeString: + return string(v.Data), nil + } + + return nil, fmt.Errorf("unsupported format: %q", v.Format) +} + +// Keys returns the list of all metadata keys +func (d *DeployData) Keys() (out []string) { + for _, m := range d.Metadata { + for k, _ := range m { + out = append(out, k) + } + } + return +} + +// GetMetadata searches the metadata of a DeployData struct for the given key. +func (d *DeployData) GetMetadata(key string) (*gvariant.Variant, bool) { + for _, m := range d.Metadata { + if v, ok := m[key]; ok { + return &v, ok + } + } + return nil, false +} + +// LoadDeployData parses and loads the binary `deploy` file. +func LoadDeployData(contents []byte) (*DeployData, error) { + out := &DeployData{} + err := gvariant.UnmarshalBigEndian(contents, out) + if err != nil { + return nil, err + } + + return out, nil +} diff --git a/flatpak/plugin.go b/flatpak/plugin.go new file mode 100644 index 0000000..2d6504f --- /dev/null +++ b/flatpak/plugin.go @@ -0,0 +1,44 @@ +package flatpak + +import ( + "context" + + "github.com/osquery/osquery-go/plugin/table" +) + +const ( + ColumnID = "id" + ColumnType = "type" + ColumnName = "name" + ColumnVersion = "version" + ColumnHash = "hash" + ColumnBranch = "branch" + ColumnUser = "user" +) + +func Schema() (out []table.ColumnDefinition) { + return []table.ColumnDefinition{ + table.TextColumn(ColumnID), + table.TextColumn(ColumnType), + table.TextColumn(ColumnName), + table.TextColumn(ColumnVersion), + table.TextColumn(ColumnHash), + table.TextColumn(ColumnBranch), + table.TextColumn(ColumnUser), + } +} + +func Generate(ctx context.Context, q table.QueryContext) (out []map[string]string, err error) { + for _, pkg := range Packages() { + out = append(out, map[string]string{ + ColumnID: pkg.Id(), + ColumnType: string(pkg.Type()), + ColumnName: pkg.Name(), + ColumnVersion: pkg.Version(), + ColumnHash: pkg.Hash(), + ColumnBranch: pkg.Branch(), + ColumnUser: pkg.User(), + }) + } + return out, err +} diff --git a/flatpak/registry.go b/flatpak/registry.go new file mode 100644 index 0000000..f60fa10 --- /dev/null +++ b/flatpak/registry.go @@ -0,0 +1,329 @@ +package flatpak + +import ( + "encoding/binary" + "errors" + "flag" + "fmt" + "log" + "os" + "os/user" + "path" + "regexp" + "strings" + + "github.com/linuxdeepin/go-lib/users/passwd" +) + +type PackageType string + +type IPackage interface { + Id() string + Name() string + Version() string + Architecture() string + Branch() string + Hash() string + Type() PackageType + User() string +} + +type packagePrimitive struct { + id string + user string + t PackageType + arch string + branch string + hash string +} + +type ArchitectureBranch struct { + Architecture string + Branch string +} + +const ( + TypeApp PackageType = "app" + TypeRuntime PackageType = "runtime" +) + +const ( + SymlinkCurrentArchitecture = "current" + SymlinkActiveHash = "active" +) + +var ( + systemLocation = "/var/lib/flatpak" + userLocation = ".local/share/flatpak" + + subpaths = []PackageType{ + TypeApp, + TypeRuntime, + } + applicationIdRegexp = regexp.MustCompile(`^([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?)(\.([A-Za-z0-9]([A-Za-z0-9-]*[A-Za-z0-9])?))*$`) +) + +func Packages() (out []IPackage) { + type scanLocation struct { + baseDir string + user string + } + + scanLocations := []scanLocation{ + {systemLocation, ""}, + } + + for _, entry := range passwd.GetPasswdEntry() { + userDir := path.Join(entry.Home, userLocation) + if st, err := os.Stat(userDir); err == nil && st.IsDir() { + scanLocations = append(scanLocations, scanLocation{userDir, entry.Name}) + } + } + + for _, loc := range scanLocations { + for _, sub := range subpaths { + dir := path.Join(loc.baseDir, string(sub)) + if entries, err := os.ReadDir(dir); err == nil { + for _, entry := range entries { + if !entry.IsDir() || !applicationIdRegexp.MatchString(entry.Name()) { + continue + } + pp := &packagePrimitive{ + id: entry.Name(), + user: loc.user, + t: sub, + } + + if ab, err := pp.architecturesAndBranches(); err == nil { + for _, ab := range ab { + out = append(out, pp.WithArchBranch(ab.Architecture, ab.Branch)) + } + } + } + } + } + } + + return +} + +// Id implements IPackage +func (pp *packagePrimitive) Id() string { + return pp.id +} + +// Name implements IPackage +func (pp *packagePrimitive) Name() string { + return pp.getMetadataString(kAppName) +} + +// Version implements IPackage +func (pp *packagePrimitive) Version() string { + return pp.getMetadataString(kAppVersion) +} + +func (pp *packagePrimitive) getMetadataString(k string) string { + deploy, err := pp.parseDeployFile() + if err != nil { + log.Printf("failed to parseDeployFile: %+v", err) + return "" + } + + if key, ok := deploy.GetMetadata(k); ok { + if v, err := VariantValue(key, binary.BigEndian); err == nil { + if name, ok := v.(string); ok { + return name + } + } + } + + return "" +} + +// Architecture implements IPackage +func (pp *packagePrimitive) Architecture() string { + a, _, _ := pp.currentArchitectureAndBranch() + return a +} + +// Branch implements IPackage +func (pp *packagePrimitive) Branch() string { + b, _, _ := pp.currentArchitectureAndBranch() + return b +} + +// Hash implements IPackage +func (pp *packagePrimitive) Hash() string { + h, _ := pp.activeHash() + return h +} + +// Type implements IPackage +func (pp *packagePrimitive) Type() PackageType { + return pp.t +} + +// User implements IPackage +func (pp *packagePrimitive) User() string { + return pp.user +} + +func (pp *packagePrimitive) dir() (string, error) { + if pp.user == "" { + return path.Join(systemLocation, string(pp.t), pp.id), nil + } + + u, err := user.Lookup(pp.user) + if err != nil { + return "", err + } + + return path.Join(u.HomeDir, userLocation, string(pp.t), pp.id), nil +} + +func (pp *packagePrimitive) currentArchitectureAndBranch() (string, string, error) { + if pp.arch != "" && pp.branch != "" { + return pp.arch, pp.branch, nil + } + + dir, err := pp.dir() + if err != nil { + return "", "", err + } + + if link, err := os.Readlink(path.Join(dir, SymlinkCurrentArchitecture)); err == nil { + parts := strings.Split(link, string(os.PathSeparator)) + if len(parts) == 2 { + pp.arch = parts[0] + pp.branch = parts[1] + } else { + return "", "", fmt.Errorf("invalid format of %q symlink: %s: expected arch/branch", + SymlinkCurrentArchitecture, link) + } + } else { + archs, err := subdirs(dir) + if err != nil { + return "", "", fmt.Errorf("failed to list architectures for package %s: %v", pp.id, err) + } + if len(archs) != 1 { + return "", "", fmt.Errorf("package %s does not have exactly 1 architecture", pp.id) + } + + pp.arch = archs[0] + + branches, err := subdirs(path.Join(dir, archs[0])) + if err != nil { + return "", "", fmt.Errorf("failed to list branches for package %s and arch %s: %v", pp.id, archs[0], err) + } + + if len(branches) != 1 { + return "", "", fmt.Errorf("package %s does not have exactly 1 branch for arch %s", pp.id, archs[0]) + } + + pp.branch = branches[0] + } + + return pp.arch, pp.branch, err +} + +func (pp *packagePrimitive) architecturesAndBranches() (out []ArchitectureBranch, err error) { + dir, err := pp.dir() + if err != nil { + return + } + + arches, err := subdirs(dir) + if err != nil { + return + } + for _, arch := range arches { + branches, err := subdirs(path.Join(dir, arch)) + if err != nil { + continue + } + for _, branch := range branches { + out = append(out, ArchitectureBranch{arch, branch}) + } + } + + return +} + +func (pp *packagePrimitive) WithArchBranch(architecture, branch string) *packagePrimitive { + npp := *pp + npp.arch = architecture + npp.branch = branch + return &npp +} + +func (pp *packagePrimitive) activeHash() (string, error) { + if pp.hash != "" { + return pp.hash, nil + } + + dir, err := pp.dir() + if err != nil { + return "", err + } + + arch, branch, err := pp.currentArchitectureAndBranch() + if err != nil { + return "", err + } + + hash, err := os.Readlink(path.Join(dir, arch, branch, SymlinkActiveHash)) + pp.hash = hash + return hash, err +} + +func (pp *packagePrimitive) parseDeployFile() (*DeployData, error) { + if pp.branch == "" { + return nil, errors.New("branch is not set") + } + + dir, err := pp.dir() + if err != nil { + return nil, err + } + + arch, branch, err := pp.currentArchitectureAndBranch() + if err != nil { + return nil, err + } + + hash, err := pp.activeHash() + if err != nil { + return nil, err + } + + deployPath := path.Join(dir, arch, branch, hash, "deploy") + contents, err := os.ReadFile(deployPath) + if err != nil { + return nil, err + } + + return LoadDeployData(contents) +} + +func subdirs(dir string) (out []string, err error) { + entries, err := os.ReadDir(dir) + if err != nil { + return + } + + for _, entry := range entries { + if entry.IsDir() && entry.Name() != "." && entry.Name() != ".." { + out = append(out, entry.Name()) + } + } + + return +} + +func init() { + flag.StringVar( + &systemLocation, + "flatpak.system-dir", + systemLocation, + "directory where system-wide flatpak packages are installed") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1dc801f --- /dev/null +++ b/go.mod @@ -0,0 +1,24 @@ +module go.fuhry.dev/osquery + +go 1.24.4 + +require ( + github.com/Jguer/go-alpm/v2 v2.2.2 + github.com/chrisportman/go-gvariant v0.0.4 + github.com/gobwas/glob v0.2.3 + github.com/hashicorp/golang-lru/v2 v2.0.7 + github.com/linuxdeepin/go-lib v0.0.0-20250616085703-5b1a742a7af7 + github.com/osquery/osquery-go v0.0.0-20250131154556-629f995b6947 +) + +require ( + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/apache/thrift v0.20.0 // indirect + github.com/go-logr/logr v1.2.4 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/pkg/errors v0.9.1 // indirect + go.opentelemetry.io/otel v1.16.0 // indirect + go.opentelemetry.io/otel/metric v1.16.0 // indirect + go.opentelemetry.io/otel/trace v1.16.0 // indirect + golang.org/x/sys v0.25.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2484d84 --- /dev/null +++ b/go.sum @@ -0,0 +1,116 @@ +github.com/Jguer/go-alpm/v2 v2.2.2 h1:sPwUoZp1X5Tw6K6Ba1lWvVJfcgVNEGVcxARLBttZnC0= +github.com/Jguer/go-alpm/v2 v2.2.2/go.mod h1:lfe8gSe83F/KERaQvEfrSqQ4n+8bES+ZIyKWR/gm3MI= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Morganamilo/go-pacmanconf v0.0.0-20210502114700-cff030e927a5 h1:TMscPjkb1ThXN32LuFY5bEYIcXZx3YlwzhS1GxNpn/c= +github.com/Morganamilo/go-pacmanconf v0.0.0-20210502114700-cff030e927a5/go.mod h1:Hk55m330jNiwxRodIlMCvw5iEyoRUCIY64W1p9D+tHc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137/go.mod h1:OMCwj8VM1Kc9e19TLln2VL61YJF0x1XFtfdL4JdbSyE= +github.com/apache/thrift v0.20.0 h1:631+KvYbsBZxmuJjYwhezVsrfc/TbqtZV4QcxOX1fOI= +github.com/apache/thrift v0.20.0/go.mod h1:hOk1BQqcp2OLzGsyVXdfMk7YFlMxK3aoEVhjD06QhB8= +github.com/chrisportman/go-gvariant v0.0.4 h1:LHcAoAuSFpTVsGHZCACO7K3xmiYHKrNl9Vq55TZW7N0= +github.com/chrisportman/go-gvariant v0.0.4/go.mod h1:ZiNJGM0tEd/2iqFDXc6Xh29FCNbhxRuP+0uzQ4sP0vI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= +github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= +github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= +github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/hashicorp/golang-lru/v2 v2.0.7 h1:a+bsQ5rvGLjzHuww6tVxozPZFVghXaHOwFs4luLUK2k= +github.com/hashicorp/golang-lru/v2 v2.0.7/go.mod h1:QeFd9opnmA6QUJc5vARoKUSoFhyfM2/ZepoAG6RGpeM= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/linuxdeepin/go-gir v0.0.0-20230331033513-a8d7a9e89f9b/go.mod h1:a0tox5vepTQu5iO6rdKc4diGT+fkyXZlRROM8ULEvaI= +github.com/linuxdeepin/go-lib v0.0.0-20250616085703-5b1a742a7af7 h1:PdCjOLi1QOWF3s5nJpuv2nNmIjA2gRnVdRS8dVRQQcg= +github.com/linuxdeepin/go-lib v0.0.0-20250616085703-5b1a742a7af7/go.mod h1:yNoMFao1mE45M8zW6i83eaGpT9eriCWsXEdcVNQswpw= +github.com/linuxdeepin/go-x11-client v0.0.0-20220830090948-78fe92b727bb/go.mod h1:KwpmRZ47A/0a2l9V0V6aTlkuNaqy5j1fOqMFJONuIMY= +github.com/mozillazg/go-pinyin v0.19.0/go.mod h1:iR4EnMMRXkfpFVV5FMi4FNB6wGq9NV6uDWbUuPhP4Yc= +github.com/osquery/osquery-go v0.0.0-20250131154556-629f995b6947 h1:EDgVELFaHiQXln+fZs9Ib9aXJwBEfa2qBZMVpSUYbYM= +github.com/osquery/osquery-go v0.0.0-20250131154556-629f995b6947/go.mod h1:4cBOmXSmmDULG4bTOq0EFvIy5NUMNJMKbLDBMg6lhJE= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/smartystreets/assertions v1.2.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.7.2/go.mod h1:Vw0tHAZW6lzCRk3xgdin6fKYcG+G3Pg9vgXWeJpQFMM= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/youpy/go-riff v0.1.0/go.mod h1:83nxdDV4Z9RzrTut9losK7ve4hUnxUR8ASSz4BsKXwQ= +github.com/youpy/go-wav v0.3.2/go.mod h1:0FCieAXAeSdcxFfwLpRuEo0PFmAoc+8NU34h7TUvk50= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b/go.mod h1:T2h1zV50R/q0CVYnsQOQ6L7P4a2ZxH47ixWcMXFGyx8= +go.opentelemetry.io/otel v1.16.0 h1:Z7GVAX/UkAXPKsy94IU+i6thsQS4nb7LviLpnaNeW8s= +go.opentelemetry.io/otel v1.16.0/go.mod h1:vl0h9NUa1D5s1nv3A5vZOYWn8av4K8Ml6JDeHrT/bx4= +go.opentelemetry.io/otel/metric v1.16.0 h1:RbrpwVG1Hfv85LgnZ7+txXioPDoh6EdbZHo26Q3hqOo= +go.opentelemetry.io/otel/metric v1.16.0/go.mod h1:QE47cpOmkwipPiefDwo2wDzwJrlfxxNYodqc4xnGCo4= +go.opentelemetry.io/otel/trace v1.16.0 h1:8JRpaObFoW0pxuVPapkgH8UhHQj+bJW8jJsCZEu5MQs= +go.opentelemetry.io/otel/trace v1.16.0/go.mod h1:Yt9vYq1SdNz3xdjZZK7wcXv1qv2pwLkqr2QVwea0ef0= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/image v0.10.0/go.mod h1:jtrku+n79PfroUbvDdeUWMAI+heR786BofxrbiSF+J0= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34= +golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= diff --git a/pacman/BUILD.bazel b/pacman/BUILD.bazel new file mode 100644 index 0000000..1272b65 --- /dev/null +++ b/pacman/BUILD.bazel @@ -0,0 +1,16 @@ +load("@rules_go//go:def.bzl", "go_library") + +go_library( + name = "pacman", + srcs = [ + "pacman.go", + "typed_columns.go", + ], + importpath = "go.fuhry.dev/osquery/pacman", + visibility = ["//visibility:public"], + deps = [ + "//extcommon", + "@com_github_jguer_go_alpm_v2//:go-alpm", + "@com_github_osquery_osquery_go//plugin/table", + ], +) diff --git a/pacman/pacman.go b/pacman/pacman.go new file mode 100644 index 0000000..df0f4c6 --- /dev/null +++ b/pacman/pacman.go @@ -0,0 +1,197 @@ +package pacman + +import ( + "context" + "flag" + "fmt" + "strings" + "sync" + + "github.com/Jguer/go-alpm/v2" + "github.com/osquery/osquery-go/plugin/table" +) + +const ( + ColumnName = "name" + ColumnVersion = "version" + ColumnDescription = "description" + ColumnArchitecture = "arch" + ColumnUrl = "url" + ColumnLicense = "license" + ColumnSize = "size" + ColumnExplicit = "explicit" + + ColumnPackage = "package" + ColumnPath = "path" +) + +var ( + h *alpm.Handle + hMu sync.Mutex + dbPath string = "/var/lib/pacman" +) + +var packagesColumns = []columnDef[alpm.IPackage]{ + stringColumn[alpm.IPackage]{ColumnName, func(p alpm.IPackage) string { return p.Name() }}, + stringColumn[alpm.IPackage]{ColumnVersion, func(p alpm.IPackage) string { return p.Version() }}, + stringColumn[alpm.IPackage]{ColumnDescription, func(p alpm.IPackage) string { return p.Description() }}, + stringColumn[alpm.IPackage]{ColumnArchitecture, func(p alpm.IPackage) string { return p.Architecture() }}, + stringColumn[alpm.IPackage]{ColumnUrl, func(p alpm.IPackage) string { return p.URL() }}, + stringColumn[alpm.IPackage]{ColumnLicense, func(p alpm.IPackage) string { return strings.Join(p.Licenses().Slice(), ",") }}, + intColumn[alpm.IPackage]{ColumnSize, func(p alpm.IPackage) int64 { return p.ISize() }}, + boolColumn[alpm.IPackage]{ColumnExplicit, func(p alpm.IPackage) bool { return p.Reason() == alpm.PkgReasonExplicit }}, +} + +type filesColumnsCtx = struct { + p alpm.IPackage + f alpm.File +} + +var filesColumns = []columnDef[filesColumnsCtx]{ + stringColumn[filesColumnsCtx]{ColumnPackage, func(c filesColumnsCtx) string { return c.p.Name() }}, + stringColumn[filesColumnsCtx]{ColumnPath, func(c filesColumnsCtx) string { return c.f.Name }}, + intColumn[filesColumnsCtx]{ColumnSize, func(c filesColumnsCtx) int64 { return c.f.Size }}, +} + +// PackagesSchema returns the schema for the "pacman_packages" table. +func PackagesSchema() (out []table.ColumnDefinition) { + for _, c := range packagesColumns { + out = append(out, c.def()) + } + return +} + +// PackagesGenerate generates row data for the "pacman_packages" table. +func PackagesGenerate(ctx context.Context, q table.QueryContext) ([]map[string]string, error) { + h, err := handle() + if err != nil { + return nil, err + } + defer release() + + db, err := h.LocalDB() + if err != nil { + return nil, err + } + + var out []map[string]string + err = db.PkgCache().ForEach(func(pkg alpm.IPackage) error { + row := make(map[string]string) + for _, col := range packagesColumns { + if constraints, ok := q.Constraints[col.name()]; ok { + if m, err := col.matches(pkg, constraints); !m { + return err + } + } + rawValue := col.val(pkg) + switch v := rawValue.(type) { + case string: + row[col.name()] = v + case int, int64, uint, uint64: + row[col.name()] = fmt.Sprintf("%d", v) + case bool: + row[col.name()] = "0" + if v { + row[col.name()] = "1" + } + } + } + out = append(out, row) + return nil + }) + + return out, err +} + +// FilesSchema returns the schema for the "pacman_files" table. +func FilesSchema() (out []table.ColumnDefinition) { + for _, c := range filesColumns { + out = append(out, c.def()) + } + return +} + +// FilesGenerate generates row data for the "pacman_files" table. +func FilesGenerate(ctx context.Context, q table.QueryContext) ([]map[string]string, error) { + h, err := handle() + if err != nil { + return nil, err + } + defer release() + + db, err := h.LocalDB() + if err != nil { + return nil, err + } + + var out []map[string]string + err = db.PkgCache().ForEach(func(pkg alpm.IPackage) error { + // filter on package name before iterating the files, which is computationally expensive + if c, ok := q.Constraints[ColumnPackage]; ok { + if m, err := filesColumns[0].matches(filesColumnsCtx{pkg, alpm.File{}}, c); !m { + return err + } + } + + for _, f := range pkg.Files() { + row := make(map[string]string) + for i, col := range filesColumns { + ctx := filesColumnsCtx{pkg, f} + // skip filtering on the `package` column, we did this above before iterating Files + if i > 0 { + if constraints, ok := q.Constraints[col.name()]; ok { + if m, err := col.matches(ctx, constraints); !m { + return err + } + } + } + rawValue := col.val(ctx) + switch v := rawValue.(type) { + case string: + row[col.name()] = v + case int, int64, uint, uint64: + row[col.name()] = fmt.Sprintf("%d", v) + case bool: + row[col.name()] = "0" + if v { + row[col.name()] = "1" + } + } + } + out = append(out, row) + } + return nil + }) + + return out, err +} + +func handle() (*alpm.Handle, error) { + var err error + + hMu.Lock() + defer hMu.Unlock() + + if h != nil { + return h, nil + } + + h, err = alpm.Initialize("/", dbPath) + return h, err +} + +func release() { + hMu.Lock() + defer hMu.Unlock() + + if h == nil { + return + } + + h.Release() + h = nil +} + +func init() { + flag.StringVar(&dbPath, "pacman.db-path", dbPath, "path to pacman database") +} diff --git a/pacman/typed_columns.go b/pacman/typed_columns.go new file mode 100644 index 0000000..74cce91 --- /dev/null +++ b/pacman/typed_columns.go @@ -0,0 +1,168 @@ +package pacman + +import ( + "fmt" + "strconv" + "strings" + + "github.com/Jguer/go-alpm/v2" + "github.com/osquery/osquery-go/plugin/table" + "go.fuhry.dev/osquery/extcommon" +) + +// TODO: +// - make these interfaces public and move to extcommon +// - support the LIKE operator + +type columnDef[T any] interface { + name() string + def() table.ColumnDefinition + val(ctx T) any + matches(T, table.ConstraintList) (bool, error) +} + +type stringColumn[T any] struct { + n string + get func(T) string +} + +type intColumn[T any] struct { + n string + get func(T) int64 +} + +type boolColumn[T any] struct { + n string + get func(T) bool +} + +func (sc stringColumn[T]) name() string { return sc.n } + +func (sc stringColumn[T]) val(ctx T) any { + return sc.get(ctx) +} + +func (sc stringColumn[T]) def() table.ColumnDefinition { + return table.TextColumn(sc.name()) +} + +func (sc stringColumn[T]) matches(ctx T, constraints table.ConstraintList) (bool, error) { + if constraints.Affinity != table.ColumnTypeText { + return false, fmt.Errorf("unable to process constraint: column %q is a text column", sc.name()) + } + val := sc.get(ctx) + for _, c := range constraints.Constraints { + switch c.Operator { + case table.OperatorEquals: + return val == c.Expression, nil + case table.OperatorGreaterThan: + return alpm.VerCmp(val, c.Expression) < 0, nil + case table.OperatorGreaterThanOrEquals: + return val == c.Expression || alpm.VerCmp(val, c.Expression) < 0, nil + case table.OperatorLessThan: + return alpm.VerCmp(c.Expression, val) < 0, nil + case table.OperatorLessThanOrEquals: + return val == c.Expression || alpm.VerCmp(c.Expression, val) < 0, nil + case table.OperatorGlob: + g, err := extcommon.CompileGlob(c.Expression) + if err != nil { + return false, err + } + return g.Match(val), nil + case table.OperatorRegexp: + r, err := extcommon.CompileRegexp(c.Expression) + if err != nil { + return false, err + } + return r.MatchString(val), nil + default: + return false, fmt.Errorf("unsupported operator: %v", c.Operator) + } + } + + // if no constraints, return true + return true, nil +} + +func (ic intColumn[T]) name() string { return ic.n } + +func (ic intColumn[T]) val(p T) any { + return ic.get(p) +} + +func (ic intColumn[T]) def() table.ColumnDefinition { + return table.BigIntColumn(ic.name()) +} + +func (ic intColumn[T]) matches(ctx T, constraints table.ConstraintList) (bool, error) { + if constraints.Affinity != table.ColumnTypeInteger && constraints.Affinity != table.ColumnTypeBigInt { + return false, fmt.Errorf("unable to process constraint: column %q is an integer column", ic.name()) + } + val := ic.get(ctx) + for _, c := range constraints.Constraints { + i, err := strconv.Atoi(c.Expression) + if err != nil { + return false, err + } + exprInt := int64(i) + switch c.Operator { + case table.OperatorEquals: + return val == exprInt, nil + case table.OperatorGreaterThan: + return val > exprInt, nil + case table.OperatorGreaterThanOrEquals: + return val >= exprInt, nil + case table.OperatorLessThan: + return val < exprInt, nil + case table.OperatorLessThanOrEquals: + return val <= exprInt, nil + default: + return false, fmt.Errorf("unsupported operator: %v", c.Operator) + } + } + + // if no constraints, return true + return true, nil +} + +func (bc boolColumn[T]) name() string { return bc.n } + +func (bc boolColumn[T]) val(ctx T) any { + if bc.get(ctx) { + return 1 + } + return 0 +} + +func (bc boolColumn[T]) def() table.ColumnDefinition { + return table.IntegerColumn(bc.name()) +} + +func (bc boolColumn[T]) matches(ctx T, constraints table.ConstraintList) (bool, error) { + val := bc.get(ctx) + for _, c := range constraints.Constraints { + e, err := parseTruthy(c.Expression) + if err != nil { + return false, err + } + switch c.Operator { + case table.OperatorEquals: + return val == e, nil + default: + return false, fmt.Errorf("unsupported operator: %v", c.Operator) + } + } + + // if no constraints, return true + return true, nil +} + +func parseTruthy(val string) (bool, error) { + switch strings.ToLower(val) { + case "yes", "true", "1": + return true, nil + case "no", "false", "0": + return false, nil + } + return false, fmt.Errorf("not a truthy value: %q", val) +} -- 2.50.1