├── .clang-format ├── .clang-tidy ├── .clang-tidy.ignore ├── .github ├── dependabot.yml └── workflows │ └── main.yml ├── .gitignore ├── .gitmodules ├── .pre-commit-config.yaml ├── .swift-format ├── .update-changes.cfg ├── 3rdparty ├── CMakeLists.txt ├── LICENSE.3rdparty ├── bsd-getopt-long.c ├── bsd-getopt-long.h └── sqlite │ ├── shell.c │ ├── sqlite │ ├── sqlite3.c │ ├── sqlite3.h │ └── sqlite3ext.h ├── CHANGES ├── CMakeLists.txt ├── CPPLINT.cfg ├── LICENSE ├── Makefile ├── Package.swift ├── README.md ├── VERSION ├── auxil ├── autodoc-to-md ├── autodoc-to-zeek ├── format-zeek-script ├── log-example.png ├── md-to-toc ├── pycodestyle.cfg ├── run-clang-tidy ├── update-license-headers ├── update-readme ├── zeek-agent.excalidraw └── zeek-agent.png ├── cmake ├── BPF.cmake ├── CheckCompiler.cmake ├── CheckFunctions.cmake ├── TargetOptions.cmake └── Util.cmake ├── configure ├── packaging ├── CMakeLists.txt ├── darwin │ ├── codesign-wrapper │ ├── hdiutil-with-codesign │ ├── notarize │ └── post-build.cmake ├── windows │ ├── appicon.rc │ ├── banner.png │ ├── banner.svg │ ├── icon.ico │ ├── sidebar.png │ ├── sidebar.svg │ ├── wix-extra.xml.in │ ├── wix-patch.xml │ └── wix-string-overrides.wxl └── zeek-agent.cfg.template ├── src ├── CMakeLists.txt ├── config.h.in ├── core │ ├── CMakeLists.txt │ ├── configuration.cc │ ├── configuration.h │ ├── database.cc │ ├── database.h │ ├── logger.cc │ ├── logger.h │ ├── scheduler.cc │ ├── scheduler.h │ ├── signal.h │ ├── signal.posix.cc │ ├── signal.windows.cc │ ├── sqlite.cc │ ├── sqlite.h │ ├── table.cc │ └── table.h ├── io │ ├── CMakeLists.txt │ ├── console.cc │ ├── console.h │ ├── zeek.cc │ └── zeek.h ├── main.cc ├── platform │ ├── CMakeLists.txt │ ├── darwin │ │ ├── CMakeLists.txt │ │ ├── CPPLINT.cfg │ │ ├── ZeekAgent.app │ │ │ ├── CMakeLists.txt │ │ │ ├── ExtensionManager.swift │ │ │ ├── Info.plist.agent.in │ │ │ ├── Info.plist.app.in │ │ │ ├── XPC.swift │ │ │ ├── ZeekAgent.icns │ │ │ ├── ZeekAgent.iconset │ │ │ │ ├── icon_128x128.png │ │ │ │ ├── icon_256x256.png │ │ │ │ ├── icon_32x32.png │ │ │ │ ├── icon_512x512.png │ │ │ │ └── icon_64x64.png │ │ │ ├── embedded.provisionprofile.agent │ │ │ ├── embedded.provisionprofile.app │ │ │ ├── entitlements.plist.agent │ │ │ ├── entitlements.plist.app │ │ │ ├── main.swift │ │ │ └── zeek.png │ │ ├── endpoint-security.h │ │ ├── endpoint-security.mm │ │ ├── network-extension.h │ │ ├── network-extension.mm │ │ ├── os-log-sink.h │ │ ├── os-log-sink.mm │ │ ├── platform.h │ │ ├── platform.mm │ │ ├── xpc.h │ │ └── xpc.mm │ ├── linux │ │ ├── CMakeLists.txt │ │ ├── bpf.cc │ │ ├── bpf.h │ │ ├── platform.cc │ │ └── platform.h │ ├── platform.h │ ├── testing.cc │ └── windows │ │ ├── CMakeLists.txt │ │ ├── platform.cc │ │ └── platform.h ├── tables │ ├── CMakeLists.txt │ ├── files │ │ ├── CMakeLists.txt │ │ ├── files.cc │ │ ├── files.h │ │ ├── files.posix.cc │ │ └── files.windows.cc │ ├── processes │ │ ├── CMakeLists.txt │ │ ├── processes.darwin.cc │ │ ├── processes.h │ │ ├── processes.linux.bpf.c │ │ ├── processes.linux.cc │ │ ├── processes.linux.event.h │ │ ├── processes.test.cc │ │ └── processes.windows.cc │ ├── sockets │ │ ├── CMakeLists.txt │ │ ├── sockets.darwin.cc │ │ ├── sockets.h │ │ ├── sockets.linux.bpf.c │ │ ├── sockets.linux.cc │ │ ├── sockets.linux.event.h │ │ ├── sockets.test.cc │ │ └── sockets.windows.cc │ ├── system_logs │ │ ├── ActivityStreamSPI.h │ │ ├── CMakeLists.txt │ │ ├── system_logs.darwin.cc │ │ ├── system_logs.h │ │ ├── system_logs.linux.cc │ │ ├── system_logs.test.cc │ │ └── system_logs.windows.cc │ ├── users │ │ ├── CMakeLists.txt │ │ ├── users.darwin.mm │ │ ├── users.h │ │ ├── users.linux.cc │ │ ├── users.test.cc │ │ └── users.windows.cc │ └── zeek_agent │ │ ├── CMakeLists.txt │ │ ├── zeek_agent.darwin.mm │ │ ├── zeek_agent.h │ │ ├── zeek_agent.linux.cc │ │ ├── zeek_agent.test.cc │ │ └── zeek_agent.windows.cc └── util │ ├── CMakeLists.txt │ ├── ascii-table.cc │ ├── ascii-table.h │ ├── color.h │ ├── filesystem.h │ ├── fmt.h │ ├── helpers.cc │ ├── helpers.h │ ├── pimpl.h │ ├── result.cc │ ├── result.h │ ├── socket.cc │ ├── socket.h │ ├── socket.no-ipc.cc │ ├── socket.posix.cc │ ├── testing.h │ └── variant.h ├── tests ├── .gitignore ├── Baseline │ ├── zeek.error │ │ └── reporter.log │ ├── zeek.if-missing-table │ │ └── zeek..stdout │ ├── zeek.log │ │ └── changes │ ├── zeek.query │ │ └── zeek.output │ ├── zeek.requires-table │ │ └── zeek..stdout │ ├── zeek.scheduled │ │ └── zeek.output │ ├── zeek.table.files │ │ └── zeek.zeek-agent-files.log │ ├── zeek.table.processes │ │ └── zeek.zeek-agent-processes.log │ ├── zeek.table.sockets │ │ └── zeek.zeek-agent-sockets.log │ ├── zeek.table.ssh │ │ └── zeek.zeek-agent-ssh-authorized-keys.log │ ├── zeek.table.system_logs │ │ └── zeek.zeek-agent-system-logs.log │ └── zeek.table.users │ │ └── zeek.zeek-agent-users.log ├── Files │ └── random.seed ├── Makefile ├── Scripts │ ├── canonifier │ ├── diff-remove-timestamps │ └── get-zeek-env ├── agent │ └── ctest.sh ├── btest.cfg ├── test-setup.zeek ├── zeek-agent.cfg └── zeek │ ├── error.zeek │ ├── if-missing-table.zeek │ ├── log.zeek │ ├── query.zeek │ ├── requires-table.zeek │ ├── scheduled.zeek │ └── table │ ├── files.zeek │ ├── processes.zeek │ ├── sockets.zeek │ ├── ssh.zeek │ ├── system_logs.zeek │ └── users.zeek └── vcpkg.json /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -4 3 | AlignAfterOpenBracket: Align 4 | AlignConsecutiveAssignments: false 5 | AlignConsecutiveDeclarations: false 6 | AlignEscapedNewlines: Right 7 | AlignOperands: true 8 | AlignTrailingComments: true 9 | AllowAllParametersOfDeclarationOnNextLine: false 10 | AllowShortBlocksOnASingleLine: false 11 | AllowShortCaseLabelsOnASingleLine: true 12 | AllowShortFunctionsOnASingleLine: true 13 | AllowShortIfStatementsOnASingleLine: false 14 | AllowShortLoopsOnASingleLine: false 15 | AlwaysBreakAfterDefinitionReturnType: None 16 | AlwaysBreakAfterReturnType: None 17 | AlwaysBreakBeforeMultilineStrings: true 18 | AlwaysBreakTemplateDeclarations: Yes 19 | BinPackArguments: true 20 | BinPackParameters: true 21 | BraceWrapping: 22 | AfterClass: false 23 | AfterControlStatement: false 24 | AfterEnum: false 25 | AfterFunction: false 26 | AfterNamespace: false 27 | AfterObjCDeclaration: false 28 | AfterStruct: false 29 | AfterUnion: false 30 | AfterExternBlock: false 31 | BeforeCatch: false 32 | BeforeElse: true 33 | IndentBraces: false 34 | SplitEmptyFunction: false 35 | SplitEmptyRecord: false 36 | SplitEmptyNamespace: false 37 | BreakBeforeBinaryOperators: None 38 | BreakBeforeBraces: Custom 39 | BreakBeforeInheritanceComma: false 40 | BreakInheritanceList: BeforeColon 41 | BreakBeforeTernaryOperators: false 42 | BreakConstructorInitializersBeforeComma: false 43 | BreakConstructorInitializers: BeforeColon 44 | BreakAfterJavaFieldAnnotations: false 45 | BreakStringLiterals: true 46 | ColumnLimit: 120 47 | CommentPragmas: 'NOLINT' 48 | CompactNamespaces: false 49 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 50 | ConstructorInitializerIndentWidth: 4 51 | ContinuationIndentWidth: 4 52 | Cpp11BracedListStyle: true 53 | DerivePointerAlignment: false 54 | DisableFormat: false 55 | ExperimentalAutoDetectBinPacking: false 56 | FixNamespaceComments: true 57 | ForEachMacros: 58 | - foreach 59 | - Q_FOREACH 60 | - BOOST_FOREACH 61 | IncludeBlocks: Regroup 62 | IncludeCategories: 63 | - Regex: '".*\.h"' 64 | Priority: 1 65 | - Regex: '^<([[:alnum:]]|_)+>' 66 | Priority: 2 67 | - Regex: '^<[[:alnum:]]*\.(h|hxx|hpp)>' 68 | Priority: 3 69 | - Regex: '^<.*/.*\.(h|hxx|hpp)>' 70 | Priority: 4 71 | - Regex: '.*' 72 | Priority: 10 73 | IncludeIsMainRegex: '$' 74 | IndentCaseLabels: true 75 | IndentPPDirectives: None 76 | IndentWidth: 4 77 | IndentWrappedFunctionNames: false 78 | JavaScriptQuotes: Leave 79 | JavaScriptWrapImports: true 80 | KeepEmptyLinesAtTheStartOfBlocks: false 81 | MacroBlockBegin: '^BEGIN_' 82 | MacroBlockEnd: '^END_' 83 | MaxEmptyLinesToKeep: 2 84 | NamespaceIndentation: None 85 | ObjCBinPackProtocolList: Auto 86 | ObjCBlockIndentWidth: 2 87 | ObjCSpaceAfterProperty: false 88 | ObjCSpaceBeforeProtocolList: true 89 | PenaltyBreakAssignment: 2 90 | PenaltyBreakBeforeFirstCallParameter: 500 91 | PenaltyBreakComment: 300 92 | PenaltyBreakFirstLessLess: 120 93 | PenaltyBreakString: 1000 94 | PenaltyBreakTemplateDeclaration: 10 95 | PenaltyExcessCharacter: 1000000 96 | PenaltyReturnTypeOnItsOwnLine: 1000 97 | PointerAlignment: Left 98 | ReflowComments: true 99 | SortIncludes: true 100 | SortUsingDeclarations: true 101 | SpaceAfterCStyleCast: false 102 | SpaceAfterTemplateKeyword: false 103 | SpaceAfterLogicalNot: true 104 | SpaceBeforeAssignmentOperators: true 105 | SpaceBeforeCpp11BracedList: false 106 | SpaceBeforeCtorInitializerColon: true 107 | SpaceBeforeInheritanceColon: true 108 | SpaceBeforeParens: ControlStatements 109 | SpaceBeforeRangeBasedForLoopColon: true 110 | SpaceInEmptyParentheses: false 111 | SpacesBeforeTrailingComments: 1 112 | SpacesInAngles: false 113 | SpacesInContainerLiterals: true 114 | SpacesInCStyleCastParentheses: false 115 | SpacesInParentheses: false 116 | SpacesInSquareBrackets: false 117 | SpacesInConditionalStatement: true 118 | Standard: Cpp11 119 | StatementMacros: 120 | - STANDARD_OPERATOR_1 121 | TabWidth: 4 122 | UseTab: Never 123 | ... 124 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: 'bugprone-*, 3 | cert-*, 4 | clang-*, 5 | concurrency-*, 6 | misc-*, 7 | modernize-*, 8 | performance-*, 9 | portability-*, 10 | readability-*, 11 | 12 | -bugprone-assignment-in-if-condition, 13 | -bugprone-easily-swappable-parameters, 14 | -bugprone-reserved-identifier, 15 | -bugprone-unchecked-optional-access, 16 | -cert-err58-cpp, 17 | -clang-analyzer-cplusplus.NewDeleteLeaks, 18 | -clang-diagnostic-c++2a-designator, 19 | -clang-diagnostic-deprecated-copy, 20 | -clang-diagnostic-range-loop-analysis, 21 | -concurrency-mt-unsafe, 22 | -misc-const-correctnes, 23 | -misc-macro-parentheses, 24 | -misc-no-recursion, 25 | -misc-non-private-member-variables-in-classes, 26 | -misc-suspicious-semicolon, 27 | -misc-unused-parameters, 28 | -misc-use-anonymous-namespace 29 | -modernize-avoid-c-arrays, 30 | -modernize-macro-to-enum, 31 | -modernize-use-equals-default, 32 | -modernize-use-nodiscard, 33 | -modernize-use-trailing-return-type, 34 | -performance-no-int-to-ptr, 35 | -readability-braces-around-statements, 36 | -readability-container-size-empty, 37 | -readability-convert-member-functions-to-static, 38 | -readability-else-after-return, 39 | -readability-function-cognitive-complexity, 40 | -readability-function-size, 41 | -readability-identifier-length, 42 | -readability-implicit-bool-conversion, 43 | -readability-isolate-declaration, 44 | -readability-magic-numbers, 45 | -readability-make-member-function-const, 46 | -readability-named-parameter, 47 | -readability-qualified-auto, 48 | -readability-static-definition-in-anonymous-namespace,÷ 49 | ' 50 | 51 | HeaderFilterRegex: '/src' 52 | WarningsAsErrors: '*' 53 | -------------------------------------------------------------------------------- /.clang-tidy.ignore: -------------------------------------------------------------------------------- 1 | # Paths to source files to ignore when running clang-tidy. This is 2 | # evaluated by auxil/run-clang-tidy. 3 | # 4 | # Note that this doesn't catch header files to ignore. We use a 5 | # combination of HeaderFilterRegex and file names starting with "__" to 6 | # select any headers to skip (if necessary). 7 | 8 | .*3rdparty/ 9 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2020-2023 by the Zeek Project. See LICENSE for details. 2 | 3 | version: 2 4 | updates: 5 | - package-ecosystem: "gitsubmodule" 6 | directory: "/" 7 | schedule: 8 | interval: "weekly" 9 | 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "weekly" 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /build* 2 | tmp 3 | *.pyc 4 | .##* 5 | compile_commands.json 6 | .clangd 7 | .cache 8 | .build 9 | *.swp 10 | old 11 | .DS_Store 12 | .ccache 13 | 14 | # Windows port 15 | /.vs 16 | /out 17 | /CMakeSettings.json 18 | /packaging/windows/extra_install.cmake 19 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "3rdparty/doctest"] 2 | path = 3rdparty/doctest 3 | url = https://github.com/onqtam/doctest 4 | [submodule "3rdparty/filesystem"] 5 | path = 3rdparty/filesystem 6 | url = https://github.com/gulrak/filesystem.git 7 | [submodule "3rdparty/spdlog"] 8 | path = 3rdparty/spdlog 9 | url = https://github.com/gabime/spdlog 10 | [submodule "3rdparty/fmt"] 11 | path = 3rdparty/fmt 12 | url = https://github.com/fmtlib/fmt 13 | [submodule "3rdparty/tomlplusplus"] 14 | path = 3rdparty/tomlplusplus 15 | url = https://github.com/marzer/tomlplusplus.git 16 | [submodule "3rdparty/pathfind"] 17 | path = 3rdparty/pathfind 18 | url = https://github.com/bkloppenborg/pathfind 19 | [submodule "3rdparty/replxx"] 20 | path = 3rdparty/replxx 21 | url = https://github.com/AmokHuginnsson/replxx.git 22 | [submodule "3rdparty/stduuid"] 23 | path = 3rdparty/stduuid 24 | url = https://github.com/mariusbancila/stduuid 25 | [submodule "3rdparty/pfs"] 26 | path = 3rdparty/pfs 27 | url = https://github.com/dtrugman/pfs 28 | [submodule "3rdparty/reproc"] 29 | path = 3rdparty/reproc 30 | url = https://github.com/DaanDeMeyer/reproc 31 | [submodule "3rdparty/json"] 32 | path = 3rdparty/json 33 | url = https://github.com/nlohmann/json 34 | [submodule "3rdparty/broker"] 35 | path = 3rdparty/broker 36 | url = https://github.com/zeek/broker.git 37 | [submodule "zeek-agent"] 38 | path = zeek-agent 39 | url = https://github.com/zeek-packages/zeek-agent-v2 40 | [submodule "3rdparty/out_ptr"] 41 | path = 3rdparty/out_ptr 42 | url = https://github.com/soasis/out_ptr 43 | [submodule "3rdparty/IXWebSocket"] 44 | path = 3rdparty/IXWebSocket 45 | url = https://github.com/machinezone/IXWebSocket 46 | [submodule "3rdparty/vcpkg"] 47 | path = 3rdparty/vcpkg 48 | url = https://github.com/microsoft/vcpkg 49 | [submodule "3rdparty/glob"] 50 | path = 3rdparty/glob 51 | url = https://github.com/p-ranav/glob.git 52 | [submodule "3rdparty/bpftool"] 53 | path = 3rdparty/bpftool 54 | url = https://github.com/libbpf/bpftool 55 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | 5 | - repo: local 6 | hooks: 7 | - id: autogen-readme 8 | name: Update README 9 | entry: ./auxil/update-readme 10 | language: script 11 | pass_filenames: false 12 | 13 | - repo: local 14 | hooks: 15 | - id: zeek-script 16 | name: Format Zeek scripts 17 | entry: auxil/format-zeek-script 18 | files: \.zeek$ 19 | language: script 20 | 21 | - repo: https://github.com/pre-commit/mirrors-clang-format 22 | rev: 'v13.0.0' 23 | hooks: 24 | - id: clang-format 25 | 26 | - repo: https://github.com/pre-commit/pre-commit-hooks 27 | rev: v4.0.1 28 | hooks: 29 | - id: trailing-whitespace 30 | - id: end-of-file-fixer 31 | - id: check-yaml 32 | - id: check-added-large-files 33 | 34 | - repo: https://github.com/cpplint/cpplint 35 | rev: 1.6.1 36 | hooks: 37 | - id: cpplint 38 | args: ["--quiet"] 39 | 40 | - repo: https://github.com/jorisroovers/gitlint 41 | rev: v0.17.0 42 | hooks: 43 | - id: gitlint 44 | 45 | exclude: 3rdparty/|/Baseline/ 46 | -------------------------------------------------------------------------------- /.swift-format: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "indentation": { 4 | "spaces": 4 5 | }, 6 | "tabWidth" : 4, 7 | "lineLength" : 120, 8 | } 9 | -------------------------------------------------------------------------------- /.update-changes.cfg: -------------------------------------------------------------------------------- 1 | show_authors=0 2 | -------------------------------------------------------------------------------- /3rdparty/sqlite/sqlite: -------------------------------------------------------------------------------- 1 | sqlite -------------------------------------------------------------------------------- /CPPLINT.cfg: -------------------------------------------------------------------------------- 1 | filter=-,+build/include_what_you_use,+readability/casting 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2021-2024 by the Zeek Project through the International 2 | Computer Science Institute. All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | (1) Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | (2) Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | (3) Neither the name of the Zeek Project, the International Computer 15 | Science Institute, nor the names of contributors may be used to 16 | endorse or promote products derived from this software without 17 | specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 20 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 21 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 22 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 23 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 | POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # Helper Makefile assuming build/ as the build directory, as configure does by default. 2 | 3 | all: build 4 | 5 | .PHONY: build install test 6 | 7 | build: 8 | @if [ -e build/Makefile ]; then $(MAKE) -j 4 -C build; else true; fi 9 | @if [ -e build/build.ninja ]; then ninja -C build; else true; fi 10 | 11 | install: 12 | @if [ -e build/Makefile ]; then $(MAKE) -j 4 -C build install; else true; fi 13 | @if [ -e build/build.ninja ]; then ninja -C build install; else true; fi 14 | 15 | test: 16 | @if command -v zeek >/dev/null 2>&1; then \ 17 | $(MAKE) -C zeek-agent test; \ 18 | $(MAKE) -C tests test; \ 19 | else \ 20 | $(MAKE) -C tests test-no-zeek; \ 21 | fi 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.3 2 | // 3 | // We don't use this for the actualy build. We have it only so that 4 | // sourcekit-lsp knows about our Swift code. 5 | 6 | import PackageDescription 7 | 8 | let package = Package( 9 | name: "ZeekAgent", 10 | platforms: [.macOS("12.0")], 11 | targets: [ 12 | .target(name: "ZeekAgent", path: "packaging/darwin/ZeekAgent.app"), 13 | ] 14 | ) 15 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.3.0-19 2 | -------------------------------------------------------------------------------- /auxil/autodoc-to-md: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # 3 | # Generates a Markdown reference from the agent's --autodoc output, which 4 | # must be piped in on stdin. 5 | 6 | import json 7 | import re 8 | import sys 9 | import textwrap 10 | 11 | def fatalError(message: str): 12 | print(message, file=sys.stderr) 13 | sys.exit(1) 14 | 15 | def fmtDoc(doc): 16 | n = [] 17 | doc = doc.split("\n\n") 18 | for i in doc: 19 | x = textwrap.dedent(i).strip() 20 | wrapped = textwrap.fill(x) 21 | if wrapped: 22 | n += [wrapped] 23 | 24 | return "\n\n".join(n) 25 | 26 | def renderTable(name, meta): 27 | platforms = [] 28 | for p in meta["platforms"]: 29 | if p == "linux": 30 | platforms.append("Linux") 31 | elif p == "darwin": 32 | platforms.append("macOS") 33 | elif p == "windows": 34 | platforms.append("Windows") 35 | else: 36 | platforms.append(p) 37 | 38 | print("
") 39 | print("{}: {} [{}]
".format(name, meta["summary"], ", ".join(sorted(platforms)))) 40 | print() 41 | 42 | description = fmtDoc(meta["description"]) 43 | if len(description): 44 | print(description) 45 | print() 46 | 47 | 48 | params = [c for c in meta["columns"] if c["is_parameter"]] 49 | if params: 50 | print("| Parameter | Type | Description | Default") 51 | print("| --- | --- | --- | --- |") 52 | 53 | for column in params: 54 | name = re.sub("^_+", "", column["name"]) 55 | type = column["type"] 56 | comment = column["summary"] 57 | 58 | default = column["default"] 59 | if default != None: 60 | if default == "": 61 | default = '' 62 | 63 | default = "`{}`".format(default.replace("|", "\\|")) 64 | else: 65 | default = "" 66 | 67 | print("| `{}` | {} | {} | {} |".format(name, type, comment, default)) 68 | 69 | print() 70 | 71 | 72 | print("| Column | Type | Description") 73 | print("| --- | --- | --- |") 74 | 75 | for column in [c for c in meta["columns"] if not c["is_parameter"]]: 76 | name = column["name"] 77 | type = column["type"] 78 | comment = column["summary"] 79 | 80 | print("| `{}` | {} | {} |".format(name, type, comment)) 81 | 82 | print("
") 83 | print() 84 | 85 | ### Main 86 | 87 | new_stdout = open(sys.__stdout__.fileno(), 88 | mode=sys.__stdout__.mode, 89 | buffering=1, 90 | encoding=sys.__stdout__.encoding, 91 | errors=sys.__stdout__.errors, 92 | newline='\n', 93 | closefd=False) 94 | sys.stdout = new_stdout 95 | 96 | try: 97 | data = json.load(sys.stdin) 98 | except ValueError as e: 99 | fatalError("cannot parse input: {}".format(e)) 100 | 101 | for tbl in sorted(data["tables"].keys()): 102 | renderTable(tbl, data["tables"][tbl]) 103 | -------------------------------------------------------------------------------- /auxil/autodoc-to-zeek: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | # 3 | # Generates a Zeek record definition from the agent's --autodoc output, which 4 | # must be piped in on stdin.ä 5 | 6 | import json 7 | import os 8 | import os.path 9 | import sys 10 | import textwrap 11 | 12 | def fatalError(message: str): 13 | print(message, file=sys.stderr) 14 | sys.exit(1) 15 | 16 | def snakeToCamel(x): 17 | return ''.join(x.title() for x in x.split("_")) 18 | 19 | def fmtComment(txt, prefix, single_line=False): 20 | txt = txt.strip() 21 | if not txt: 22 | return "" 23 | 24 | prefix += " " 25 | 26 | if single_line: 27 | return "{} {}".format(prefix, txt) 28 | else: 29 | return textwrap.fill(txt, initial_indent=prefix, subsequent_indent=prefix) 30 | 31 | def fmtID(id): 32 | if id in ("time", "type"): 33 | return "%s_" % id 34 | 35 | return id 36 | 37 | ### Main 38 | 39 | if len(sys.argv) != 2: 40 | fatalError("usage: {} ".format(os.path.basename(sys.argv[0]))) 41 | sys.exit(1) 42 | 43 | try: 44 | data = json.load(sys.stdin) 45 | except ValueError as e: 46 | fatalError("cannot parse input: {}".format(e)) 47 | 48 | table_name = sys.argv[1] 49 | 50 | try: 51 | table = data["tables"][table_name] 52 | except Exception: 53 | fatalError("no such table") 54 | 55 | new_stdout = open(sys.__stdout__.fileno(), 56 | mode=sys.__stdout__.mode, 57 | buffering=1, 58 | encoding=sys.__stdout__.encoding, 59 | errors=sys.__stdout__.errors, 60 | newline='\n', 61 | closefd=False) 62 | sys.stdout = new_stdout 63 | 64 | indent = "\t" 65 | 66 | summary = table["summary"].capitalize() 67 | if not summary.endswith("."): 68 | summary += "." 69 | 70 | print("{}{}".format(indent, fmtComment(summary, "##"))) 71 | print("{}type Columns: record {{".format(indent)) 72 | 73 | for column in table["columns"]: 74 | if column["is_parameter"]: 75 | continue 76 | 77 | name = fmtID(column["name"]) 78 | comment = column["summary"] 79 | type = column["type"] 80 | 81 | if type == "int": 82 | type = "int" 83 | elif type == "text" or type == "blob": 84 | type = "string" 85 | elif type == "real": 86 | type = "double" 87 | elif type == "null": 88 | # ignore 89 | continue 90 | else: 91 | fatalError("unknown column type '{}'".format(type)) 92 | 93 | print("{}{}{}: {} &optional &log;{}".format(indent, indent, name, type, fmtComment(comment, "\t##<", True))) 94 | 95 | print("{}}};".format(indent)) 96 | -------------------------------------------------------------------------------- /auxil/format-zeek-script: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | type -P zeek-script >/dev/null 2>&1 || exit 0 4 | 5 | for i in $@; do 6 | zeek-script format ${i} >${i}.tmp && mv -f ${i}.tmp ${i} 7 | done 8 | -------------------------------------------------------------------------------- /auxil/log-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/auxil/log-example.png -------------------------------------------------------------------------------- /auxil/md-to-toc: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | gawk ' 4 | 5 | function fmt_entry(prefix, line) { 6 | title = gensub("^#* *", "", 1, line); 7 | link = tolower(gensub(" ", "-", "g", title)); 8 | printf("%s [%s](#%s)\n", prefix, title, link); 9 | } 10 | 11 | /^#.* Contents/ { next; } 12 | /^## / { fmt_entry("-", $0); } 13 | /^### / { fmt_entry(" -", $0); } 14 | #/^#### / { fmt_entry(" -", $0); } 15 | ' 16 | -------------------------------------------------------------------------------- /auxil/pycodestyle.cfg: -------------------------------------------------------------------------------- 1 | [pycodestyle] 2 | ignore = E501,E302,E305,E266 3 | -------------------------------------------------------------------------------- /auxil/run-clang-tidy: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 4 | 5 | usage() { 6 | echo "Usage: $(basename $0) [--fixit] [-j ] [--clang-tidy-path ] [--clang-tidy-arg ] []" 7 | exit 1 8 | } 9 | 10 | error() { 11 | echo "$@" >&2 12 | } 13 | 14 | abspath() { 15 | printf "%s/%s\n" "$(cd $(dirname $1) && pwd)" "$(basename $1)" 16 | } 17 | 18 | cleanup() { 19 | rm -rf "${tmpdir} ${error}" 20 | } 21 | 22 | fix="" 23 | clang_tidy_args="" 24 | files="" 25 | parallel=1 26 | 27 | if [ -n "${CLANG_TIDY}" ]; then 28 | clang_tidy_path=${CLANG_TIDY} 29 | else 30 | clang_tidy_path=$(which clang-tidy 2>/dev/null) 31 | fi 32 | 33 | while true; do 34 | case "$1" in 35 | --fixit) fix=1; shift;; 36 | --clang-tidy-path) clang_tidy_path="$2"; shift; shift;; 37 | --clang-tidy-arg) clang_tidy_args="${clang_tidy_args} $2"; shift; shift;; 38 | --ignore) ignores="${ignores:+${ignores}|}$2"; shift; shift;; 39 | -j) parallel="$2"; shift; shift;; 40 | -h | --help) usage;; 41 | --) shift; break;; 42 | --*) usage;; 43 | *) break;; 44 | esac 45 | done 46 | 47 | if [ $# -lt 1 ]; then 48 | usage 49 | fi 50 | 51 | build=$(cd $1 && pwd) 52 | root=$(cd ${build}/.. && pwd) 53 | shift 54 | 55 | for f in $@; do 56 | files=$(printf "%s %s\n" ${files} $(abspath ${f})) 57 | done 58 | 59 | cd ${build} 60 | 61 | cmake_cache=CMakeCache.txt 62 | compile_json=compile_commands.json 63 | clang_tidy_ignore=${root}/.clang-tidy.ignore 64 | 65 | if ! which jq >/dev/null 2>&1; then 66 | error "Need jq in PATH, aborting." && exit 1 67 | fi 68 | 69 | for i in ${cmake_cache} ${compile_json}; do 70 | if [ ! -f ${i} ]; then 71 | error "${i} not found, did you configure and build?" && exit 1 72 | fi 73 | done 74 | 75 | if [ -z "${clang_tidy_path}" ]; then 76 | clang_tidy_path=$(cat ${cmake_cache} | grep CMAKE_CXX_COMPILER:PATH | cut -d = -f 2 | sed 's/clang+\{0,2\}/clang-tidy/') 77 | fi 78 | 79 | if [ ! -x "${clang_tidy_path}" ]; then 80 | error "cannot find clang-tidy" && exit 1 81 | fi 82 | 83 | if [ "${fix}" = 1 -a -n "$(git status --porcelain | egrep -v '^(M|\?\?) ')" ]; then 84 | echo >&2 85 | echo "error: uncommitted changes in working directory, won't apply changes" >&2 86 | echo >&2 87 | git status -sb >&2 88 | exit 1 89 | fi 90 | 91 | clang_apply_replacements_path=$(echo ${clang_tidy_path} | sed 's/clang-tidy/clang-apply-replacements/') 92 | 93 | if [ -n "${fix}" -a ! -x "${clang_apply_replacements_path}" ]; then 94 | error "cannot find clang-apply-replacements" && exit 1 95 | fi 96 | 97 | if [ -f "${clang_tidy_ignore}" ]; then 98 | x=$(cat ${clang_tidy_ignore} | egrep -v '^ *(#.*)?$' | awk '{printf "%s|", $1}' | sed 's/|$//g') 99 | ignores="${ignores:+${ignores}|}${x}" 100 | fi 101 | 102 | if [ -z "${ignores}" ]; then 103 | ignores="__NEVER_MATCHES__" 104 | fi 105 | 106 | if [ -z "${files}" ]; then 107 | files=$(cat ${compile_json} | jq -r '.[].file' | egrep '\.(cc|c|h)$' | grep -v autogen/__ | grep -v '\.bif\.' | egrep -v "${root}/(${ignores})") 108 | fi 109 | 110 | cmd="${clang_tidy_path} -quiet -p=${build} ${clang_tidy_args}" 111 | 112 | error=/tmp/$(basename $0).$$.error.tmp 113 | tmpdir=/tmp/$(basename $0).$$.tmp 114 | rm -rf "${tmpdir}" 115 | mkdir -p "${tmpdir}" 116 | trap cleanup EXIT 117 | 118 | base=$(cd ${build}/.. && pwd) 119 | rm -f ${error} 120 | 121 | echo "${files}" | awk -v "cmd=${cmd}" -v "tmp=${tmpdir}" '{t=$1; gsub("[/]", "_", t); printf("%s -export-fixes=%s/%s.yaml %s\n", cmd, tmp, t, $1);}' \ 122 | | tr '\n' '\0' | (xargs -0 -n 1 -P ${parallel} sh -c 2>&1 || touch ${error}) | grep -v 'warnings\? generated\.$' | sed "s#^../\(.*:\)#${base}/\1#g" 123 | 124 | if [ -e "${error}" ]; then 125 | rc=1 126 | else 127 | rc=0 128 | fi 129 | 130 | if [ ${rc} != 0 -a -n "${fix}" ]; then 131 | # It looks like clang-apply-replacements can merge & unique changes from 132 | # multiple files. In case that turns out not to work, we could borrow 133 | # from LLVM's run-clang-tidy.py script). That has Python code to merge 134 | # replacements ahead of time. 135 | ${clang_apply_replacements_path} ${tmpdir} 136 | fi 137 | 138 | exit ${rc} 139 | -------------------------------------------------------------------------------- /auxil/update-license-headers: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | find . -type d -mindepth 1 -maxdepth 1 | grep -E -v '^\.(/build.*|/\.git|/3rdparty|/\.cache|/\.build)(/|$)' | while read dir; do 4 | echo ${dir} >/dev/tty 5 | find "${dir}" -type f | while read file; do 6 | echo ${file} | grep -E -q '/3rdparty/|/\..*/|update-license-headers' && continue 7 | cat ${file} | grep -q Copyright || continue 8 | gsed -i'' 's/Copyright .* by the Zeek Project\..* details\./Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details./' "${file}" 9 | done 10 | done 11 | -------------------------------------------------------------------------------- /auxil/update-readme: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | cd $(dirname $0) 4 | 5 | if [ ! -x ../build/bin/zeek-agent ]; then 6 | echo "no build/bin/zeek-agent, cannot update README" >&2 7 | exit 0 # don't fail pre-commit 8 | fi 9 | 10 | README=../README.md 11 | 12 | tmp=/tmp/README.$$.tmp 13 | trap "rm -f ${tmp}" EXIT 14 | 15 | gawk ' 16 | BEGIN { p = 1 } 17 | 18 | // { print; 19 | system("cat ../README.md | ./md-to-toc"); 20 | p = 0; } 21 | // { p = 1; } 22 | 23 | // { print; 24 | system("../build/bin/zeek-agent --autodoc | ./autodoc-to-md"); 25 | p = 0; } 26 | // { p = 1; } 27 | 28 | p != 0 { print; } 29 | ' <${README} >${tmp} 30 | 31 | cmp -s ${README} ${tmp} || mv -f ${tmp} ${README} 32 | -------------------------------------------------------------------------------- /auxil/zeek-agent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/auxil/zeek-agent.png -------------------------------------------------------------------------------- /cmake/BPF.cmake: -------------------------------------------------------------------------------- 1 | 2 | if ( NOT BPFTOOL ) 3 | message(FATAL_ERROR "BPFTOOL not available (but should be set automatically by build when on Linux") 4 | endif () 5 | 6 | if ( NOT CMAKE_C_COMPILER_ID STREQUAL "Clang" ) 7 | message(FATAL_ERROR "Must compile with clang as C compiler because of BPF support (have: ${CMAKE_C_COMPILER_ID}).") 8 | endif () 9 | 10 | file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/include/autogen/bpf") 11 | 12 | # Map target architecture (per CMAKE_SYSTEM_PROCESSOR) to architecture define 13 | # as expected by bpf_traching.h. Add missing architectures as needed. 14 | set(bpf_arch_aarch64 arm64) 15 | set(bpf_arch_i386 x86) 16 | set(bpf_arch_i686 x86) 17 | set(bpf_arch_x86_64 x86) 18 | set(bpf_arch "${bpf_arch_${CMAKE_SYSTEM_PROCESSOR}}") 19 | 20 | if ( "${bpf_arch}" STREQUAL "" ) 21 | message(FATAL_ERROR "Unknown architecture ${CMAKE_SYSTEM_PROCESSOR} for BPF, update BPF.cmake for ${CMAKE_SYSTEM_PROCESSOR}") 22 | endif () 23 | 24 | # Send the object code through bpftool to get the header skeleton, which 25 | # will contain the compiled BPF byte code. The caller will need to `#include` 26 | # this header somewhere. 27 | macro (generate_bpf_code target name src) 28 | set(lib "bpf_${name}") 29 | add_library(${lib} ${src}) 30 | set_property(SOURCE ${src} APPEND PROPERTY OBJECT_DEPENDS bpftool) 31 | target_include_directories(${lib} PRIVATE ${BPF_INCLUDE_DIR}) 32 | target_compile_options(${lib} PRIVATE -target bpf -O2 -D__TARGET_ARCH_${bpf_arch}) # -O2 because of https://bugzilla.redhat.com/show_bug.cgi?id=1618958 33 | 34 | set(skel_h "${CMAKE_BINARY_DIR}/include/autogen/bpf/${name}.skel.h") 35 | set(lib_skel "bpf_${name}_skel") 36 | 37 | add_custom_command( 38 | COMMAND ${BPFTOOL} gen skeleton $ name ${name} >${skel_h} 39 | DEPENDS bpftool ${lib} 40 | OUTPUT ${skel_h} 41 | ) 42 | 43 | add_custom_target(${lib_skel} DEPENDS ${skel_h}) 44 | add_dependencies(${target} ${lib_skel}) 45 | endmacro () 46 | -------------------------------------------------------------------------------- /cmake/CheckCompiler.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | # 3 | # Adapted from Zeek. 4 | 5 | set(clang_minimum_version "9.0") 6 | set(apple_clang_minimum_version "11.0") 7 | set(gcc_minimum_version "9.0") 8 | set(msvc_toolset_minimum_version "143") # Visual Studio 2022 9 | 10 | include(CheckCXXSourceCompiles) 11 | 12 | macro(cxx17_compile_test) 13 | check_cxx_source_compiles(" 14 | #include 15 | int main() { std::optional a; }" 16 | cxx17_works) 17 | 18 | if (NOT cxx17_works) 19 | message(FATAL_ERROR "failed using C++17 for compilation") 20 | endif () 21 | endmacro() 22 | 23 | set(HAVE_GCC no) 24 | set(HAVE_CLANG no) 25 | set(HAVE_MSVC no) 26 | 27 | if ( CMAKE_CXX_COMPILER_ID STREQUAL "GNU" ) 28 | set(HAVE_GCC yes) 29 | if ( CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${gcc_minimum_version} ) 30 | message(FATAL_ERROR "GCC version must be at least " 31 | "${gcc_minimum_version} for C++17 support, detected: " 32 | "${CMAKE_CXX_COMPILER_VERSION}") 33 | endif () 34 | 35 | elseif ( CMAKE_CXX_COMPILER_ID STREQUAL "Clang" ) 36 | set(HAVE_CLANG yes) 37 | if ( CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${clang_minimum_version} ) 38 | message(FATAL_ERROR "Clang version must be at least " 39 | "${clang_minimum_version} for C++17 support, detected: " 40 | "${CMAKE_CXX_COMPILER_VERSION}") 41 | endif () 42 | if ( CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5 ) 43 | set(cxx17_flag "-std=c++1z") 44 | endif () 45 | elseif ( CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" ) 46 | set(HAVE_CLANG yes) 47 | if ( CMAKE_CXX_COMPILER_VERSION VERSION_LESS ${apple_clang_minimum_version} ) 48 | message(FATAL_ERROR "Apple Clang version must be at least " 49 | "${apple_clang_minimum_version} for C++17 support, detected: " 50 | "${CMAKE_CXX_COMPILER_VERSION}") 51 | endif () 52 | elseif ( CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") 53 | set(HAVE_MSVC yes) 54 | if ( MSVC_TOOLSET_VERSION LESS ${msvc_toolset_minimum_version} ) 55 | message(FATAL_ERROR "MSVC Toolset version must be at least " 56 | "${msvc_clang_minimum_version} for C++20 support, detected: " 57 | "${MSVC_TOOLSET_VERSION}") 58 | endif() 59 | else() 60 | # Unrecognized compiler: fine to be permissive of other compilers as long 61 | # as they are able to support C++17 and can compile the test program, but 62 | # we just won't be able to give specific advice on what compiler version a 63 | # user needs in the case it actually doesn't support C++17. 64 | endif () 65 | 66 | cxx17_compile_test() 67 | -------------------------------------------------------------------------------- /cmake/CheckFunctions.cmake: -------------------------------------------------------------------------------- 1 | include (CheckFunctionExists) 2 | 3 | check_function_exists(getopt_long HAVE_GETOPT_LONG) 4 | -------------------------------------------------------------------------------- /cmake/TargetOptions.cmake: -------------------------------------------------------------------------------- 1 | 2 | # Adds our compile and link options to an executble target. 3 | macro(add_target_options target scope) 4 | if ( NOT HAVE_WINDOWS ) 5 | if ( CMAKE_BUILD_TYPE STREQUAL "Debug" ) 6 | target_compile_options(${target} ${scope} -O0) 7 | endif() 8 | 9 | target_compile_options(${target} ${scope} $<$:-Werror>) 10 | target_compile_options(${target} ${scope} -Wno-unused-parameter) 11 | endif() 12 | 13 | if ( HAVE_CLANG ) 14 | target_compile_options(${target} ${scope} -Wpedantic) 15 | target_compile_options(${target} ${scope} -Wno-c99-designator) 16 | target_compile_options(${target} ${scope} -Wno-vla-extension) 17 | endif () 18 | 19 | if ( HAVE_GCC ) 20 | target_compile_options(${target} ${scope} -Wno-missing-field-initializers) 21 | endif () 22 | 23 | if ( HAVE_WINDOWS ) 24 | # Keep min and max macros from being defined, which breaks std::min and std::max. 25 | target_compile_options(${target} ${scope} /DNOMINMAX) 26 | # Reduce the amount of stuff that gets included with windows.h. 27 | target_compile_options(${target} ${scope} /DWIN32_LEAN_AND_MEAN) 28 | 29 | target_compile_options(${target} ${scope} $<$:/WX>) 30 | # TODO: enable this eventually after figuring out how to disable it for third party code. 31 | # Equivalent to -Wpedantic but on Windows 32 | # target_compile_options(${target} ${scope} /Wall) 33 | 34 | # This is needed so that the console window pops up when you run the exe. 35 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /SUBSYSTEM:WINDOWS /ENTRY:mainCRTStartup") 36 | 37 | set(APP_ICON_RESOURCE_WINDOWS "${CMAKE_SOURCE_DIR}/packaging/windows/appicon.rc") 38 | endif () 39 | 40 | if ( NOT "${USE_DOCTEST}" ) 41 | add_compile_definitions(DOCTEST_CONFIG_DISABLE) 42 | endif () 43 | 44 | if ( USE_SANITIZERS ) 45 | target_compile_options(${target} ${scope} -fsanitize=${USE_SANITIZERS}) 46 | 47 | if ( NOT HAVE_WINDOWS ) 48 | # Recommended flags per https://github.com/google/sanitizers/wiki/AddressSanitizer 49 | # GCC vs clang: https://stackoverflow.com/a/47022141 50 | target_compile_options(${target} ${scope} -fno-omit-frame-pointer -fno-optimize-sibling-calls) # Removed -O1 51 | target_link_options(${target} ${scope} -fsanitize=${USE_SANITIZERS}) 52 | endif () 53 | 54 | if ( HAVE_CLANG ) 55 | target_compile_options(${target} ${scope} -shared-libsan) 56 | target_link_options(${target} ${scope} -shared-libsan -frtlib-add-rpath) 57 | endif () 58 | endif() 59 | 60 | endmacro() 61 | -------------------------------------------------------------------------------- /cmake/Util.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | # 3 | # A collection of small helpers for the Zeek Agent build system. 4 | 5 | # Warn or abort if we don't a given version isn't recent enough. 6 | function(require_version name found have need require) 7 | if ( NOT ${found} ) 8 | if ( require ) 9 | message(FATAL_ERROR "${name} required, but not found") 10 | else () 11 | set(${found} no PARENT_SCOPE) 12 | set(${have} "not found") 13 | endif () 14 | else () 15 | if ( ${have} VERSION_LESS "${need}" ) 16 | if ( require ) 17 | message(FATAL_ERROR "Need ${name} version >= ${need}, found ${${have}}") 18 | endif () 19 | 20 | message(STATUS "Warning: Need ${name} version >= ${need}, found ${${have}}") 21 | set(${found} no PARENT_SCOPE) 22 | set(${have} "${${have}} (error: too old, must be at least ${zeek_mininum_version})" PARENT_SCOPE) 23 | endif() 24 | endif() 25 | endfunction () 26 | -------------------------------------------------------------------------------- /packaging/darwin/codesign-wrapper: -------------------------------------------------------------------------------- 1 | # /usr/bin/env bash 2 | 3 | if [ -z "${MACOS_CERTIFICATE_APPLICATION_ID}" ]; then 4 | echo "=== Skipping codesign execution, MACOS_CERTIFICATE_APPLICATION_ID is not set" 5 | exit 0 6 | fi 7 | 8 | #echo "== CODESIGN: $@" >/dev/tty 9 | /usr/bin/codesign "$@" 10 | -------------------------------------------------------------------------------- /packaging/darwin/hdiutil-with-codesign: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # CMake doesn't provide a hook running after the app bundle has been fully 4 | # assembled, but before the the DMG is being created. To get in there, we 5 | # replace hdituil with this script, so that we can codesign the final state of 6 | # the bundle the way we need to. 7 | # 8 | # Note to future self on logging: CPack doesn't seem to setup stdout/stderr, so 9 | # by default we don't see any output. That's why we redirect to /dev/tty if we have it. 10 | # However, in GH actions, we don't have that either, so we fall back to a log file. 11 | 12 | args=("$@") 13 | base=$(cd $(dirname $0)/../.. && pwd) 14 | app_src=${base}/src/platform/darwin/ZeekAgent.app 15 | log_no_tty="/tmp/zeek-agent-hdiutil.log" 16 | 17 | codesign="/usr/bin/codesign -s '${MACOS_CERTIFICATE_APPLICATION_ID}' -v --force --options=runtime --strict --timestamp" 18 | 19 | if sh -c ": >/dev/tty" >/dev/null 2>/dev/null; then 20 | have_tty=1 21 | else 22 | have_tty=0 23 | rm -f ${log_no_tty} 24 | fi 25 | 26 | log() { 27 | if [ "${have_tty}" = "1" ]; then 28 | echo "$@" >/dev/tty 29 | else 30 | echo "$@" >>${log_no_tty} 31 | fi 32 | } 33 | 34 | log_pipe() { 35 | if [ "${have_tty}" = "1" ]; then 36 | cat >/dev/tty 37 | else 38 | cat >>${log_no_tty} 39 | fi 40 | } 41 | 42 | codesign() { 43 | cd "$1" 44 | app="ZeekAgent.app" 45 | 46 | if [ -n "${MACOS_CERTIFICATE_APPLICATION_ID}" ]; then 47 | log "-- Signing ${app}" 48 | else 49 | log "-- Not signing ${app}, MACOS_CERTIFICATE_APPLICATION_ID not set" 50 | return 51 | fi 52 | 53 | eval ${codesign} --entitlements "${app_src}/entitlements.plist.agent" "${app}/Contents/Library/SystemExtensions/org.zeek.zeek-agent.agent.systemextension" 2>&1 | log_pipe 54 | eval ${codesign} --entitlements "${app_src}/entitlements.plist.app" "${app}" 2>&1 | log_pipe 55 | } 56 | 57 | log "== Running hdiutil" 58 | log "-- cmdline: $@" 59 | 60 | if [ "$1" == "create" ]; then 61 | while [ "$#" != 0 ]; do 62 | if [ "$1" = "-srcfolder" -a "$2" != "" ]; then 63 | codesign "$2" 64 | break 65 | fi 66 | shift 67 | done 68 | fi 69 | 70 | set -- "${args[@]}" 71 | /usr/bin/hdiutil "$@" 72 | -------------------------------------------------------------------------------- /packaging/darwin/notarize: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | # 3 | # Submits a bundle (e.g., a DMG or ZIP) for Apple notarization. 4 | # 5 | # This script can sign with either application or installers certificates. We 6 | # currently don't use the latter, but we're keeping in the functionality in 7 | # case it'll come in handy some day. 8 | # 9 | # For notarization, store the App Store Connect credentials in the default 10 | # keychain through `xcrun notarytool store-credentials "App Store Connect API - 11 | # zeek-agent"`. 12 | 13 | set -e 14 | 15 | function usage { 16 | echo "Usage: $(basename $0) application|installer " >&2 17 | } 18 | 19 | if [ $# != 2 ]; then 20 | usage 21 | exit 1 22 | fi 23 | 24 | base=$(cd $(dirname $0)/../.. && pwd) 25 | app_src=${base}/src/platform/darwin/ZeekAgent.app 26 | sign_type=$1 27 | bundle=$2 28 | 29 | echo "== Notarizing bundle ..." 30 | 31 | if [ "${sign_type}" == "installer" ]; then 32 | if [ -z "${MACOS_CERTIFICATE_INSTALLER_ID}" ]; then 33 | echo "Error: MACOS_CERTIFICATE_INSTALLER_ID not set" >&2 34 | exit 1 35 | fi 36 | 37 | echo "-- Signing installer ..." 38 | productsign --sign "${MACOS_CERTIFICATE_INSTALLER_ID}" "${bundle}" "${bundle}.tmp" 39 | mv "${bundle}.tmp" "${bundle}" 40 | 41 | elif [ "$1" == "application" ]; then 42 | if [ -z "${MACOS_CERTIFICATE_APPLICATION_ID}" ]; then 43 | echo "Error: MACOS_CERTIFICATE_APPLICATION_ID not set" >&2 44 | exit 1 45 | fi 46 | 47 | echo "-- Signing bundle ..." 48 | codesign -f -s "${MACOS_CERTIFICATE_APPLICATION_ID}" --timestamp --entitlements ${app_src}/entitlements.plist.app ${bundle} 49 | 50 | else 51 | usage 52 | exit 1 53 | fi 54 | 55 | echo "-- Uploading for notarization ..." 56 | 57 | xcrun notarytool submit --keychain-profile "App Store Connect API - zeek-agent" --wait --timeout 20m "${bundle}" 58 | 59 | echo "-- Stapling ..." 60 | 61 | xcrun stapler staple ${bundle} 62 | 63 | echo "-- Verifying ..." 64 | 65 | spctl -a -vv -t install ${bundle} 66 | xcrun stapler validate ${bundle} 67 | -------------------------------------------------------------------------------- /packaging/darwin/post-build.cmake: -------------------------------------------------------------------------------- 1 | # Validate/display signatures and entitlements to the degree we can. 2 | foreach (pkg "${CPACK_PACKAGE_FILES}") 3 | string(REPLACE ".dmg" "" bundle "${pkg}") 4 | foreach (path 5 | ZeekAgent.app 6 | ZeekAgent.app/Contents/Library/SystemExtensions/org.zeek.zeek-agent.agent.systemextension 7 | ) 8 | get_filename_component(name "${path}" NAME) 9 | message(STATUS "Validating ${name}") 10 | execute_process(COMMAND /usr/bin/codesign -vv --strict "${bundle}/${path}") 11 | execute_process(COMMAND /usr/bin/codesign -dv --strict "${bundle}/${path}") 12 | execute_process(COMMAND /usr/bin/codesign -d --entitlements - "${bundle}/${path}") 13 | endforeach() 14 | endforeach() 15 | 16 | if ( NOT "$ENV{MACOS_NOTARIZE}" STREQUAL "") 17 | foreach (pkg "${CPACK_PACKAGE_FILES}") 18 | execute_process(COMMAND ${CMAKE_CURRENT_LIST_DIR}/notarize application "${pkg}" COMMAND_ERROR_IS_FATAL ANY) 19 | endforeach() 20 | else () 21 | message(STATUS "Not notarizing application, MACOS_NOTARIZE not set") 22 | endif() 23 | -------------------------------------------------------------------------------- /packaging/windows/appicon.rc: -------------------------------------------------------------------------------- 1 | ID1_ICON1 ICON DISCARDABLE "icon.ico" 2 | -------------------------------------------------------------------------------- /packaging/windows/banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/packaging/windows/banner.png -------------------------------------------------------------------------------- /packaging/windows/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/packaging/windows/icon.ico -------------------------------------------------------------------------------- /packaging/windows/sidebar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/packaging/windows/sidebar.png -------------------------------------------------------------------------------- /packaging/windows/wix-extra.xml.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /packaging/windows/wix-patch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /packaging/windows/wix-string-overrides.wxl: -------------------------------------------------------------------------------- 1 | 2 | Thank you for installing [ProductName]. To complete the installation, modify the zeek-agent.cfg file in the %PROGRAMDATA%\[ProductName] directory and reboot your computer. 3 | 4 | Click the Finish button to exit the Setup. 5 | 6 | -------------------------------------------------------------------------------- /packaging/zeek-agent.cfg.template: -------------------------------------------------------------------------------- 1 | # Configuration file for ZeekAgent. Most of these options should work with their 2 | # normal defaults. The main option that should be modified is the zeek.destinations 3 | # option. See the documentation below for the valid values for each option. Some of 4 | # these options (such as the log level) can be overridden with command-line flags. 5 | # See zeek-agent --help for more information about those flags. 6 | 7 | [log] 8 | # The granulatity of the log information. 9 | # Valid values: "trace", "debug", "info", "warning", "error", "critical", "off" 10 | # "trace" will return the most log data, while "critical" returns the least. 11 | # Setting this option to "off" will disable all logging. 12 | #level = "info" 13 | 14 | # Valid values: 15 | # "file": send all logs to the file defined in the log.path option 16 | # "system": send logs the appropriate system logging facility (e.g. syslog) 17 | # "stdout": send all logs to stdout 18 | #type = "stdout" 19 | 20 | # A path to a file to store log data if the log.type option is set to "file". 21 | #path = "" 22 | 23 | [zeek] 24 | # A bracketed list of hostname/ip:port values that define what hosts running Zeek 25 | # that the agent should send data to. This option must be set for zeek-agent to 26 | # function properly. 27 | #destination = [] 28 | 29 | # The interval in seconds to expire any state from a connected Zeek instance if 30 | # no activity has been seen from it. 31 | #timeout = 120 32 | 33 | # The interval in seconds for when "hello" pings are sent. 34 | #hello_interval = 60 35 | 36 | # The amount of time in seconds to wait to reconnect to a Zeek instance if the 37 | # connection is closed. 38 | #reconnect_interval = 30 39 | 40 | #groups = 41 | 42 | # If true, the agent will not use SSL for network connections. By default, 43 | # SSL will be used even if no certificates / CAs have been configured, so 44 | # that the communication will always be encrypted. 45 | #ssl_disable = false 46 | 47 | # Path to a file containing concatenated trusted certificates in PEM format. 48 | # If set, the agent will require valid certificates for all peers. 49 | #ssl_cafile = "" 50 | 51 | # Path to an OpenSSL-style directory of trusted certfiicates. If set, the 52 | # agent will rqeuire valid certificates from all peers. 53 | #ssl_capath = "" 54 | 55 | # Path to a file containing an X.509 certificat for this node in PEM format. 56 | # If set, the agent will require valid certificates for all peers. 57 | #ssl_certificate = "" 58 | 59 | # Path to the file containing the private key for this node's certificate. 60 | # If set, the agent will require valid certificates for all peers. 61 | #ssl_keyfile = "" 62 | 63 | # Passphrase to decrypt the private key specified by the ssl_keyfile option. 64 | # If set, the agent will require valid certificates for all peers. 65 | #ssl_passphrase = 66 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | ### Setup compiler 4 | 5 | if ( HAVE_WINDOWS ) 6 | # We require C++20 on MSVC because designated-initializers don't exist in C++17 mode. 7 | set(CMAKE_CXX_STANDARD 20) 8 | else() 9 | set(CMAKE_CXX_STANDARD 17) 10 | endif() 11 | 12 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 13 | set(CMAKE_CXX_EXTENSIONS OFF) 14 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 15 | 16 | include(CheckCompiler) 17 | include(CheckFunctions) 18 | include(TargetOptions) 19 | 20 | include_directories(${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_BINARY_DIR}/include) 21 | 22 | add_executable(zeek-agent main.cc ${APP_ICON_RESOURCE_WINDOWS}) 23 | add_target_options(zeek-agent PRIVATE) 24 | 25 | add_subdirectory(core) 26 | add_subdirectory(io) 27 | add_subdirectory(platform) 28 | add_subdirectory(tables) 29 | add_subdirectory(util) 30 | 31 | if ( USE_BROKER ) 32 | # Ensure that we include our Broker version even if existing includes may lead us elswhere. 33 | target_include_directories(zeek-agent PRIVATE $) 34 | endif () 35 | 36 | target_link_libraries(zeek-agent PRIVATE ${CMAKE_DL_LIBS}) 37 | target_link_libraries(zeek-agent PRIVATE doctest::doctest) 38 | target_link_libraries(zeek-agent PRIVATE fmt::fmt) 39 | target_link_libraries(zeek-agent PRIVATE ghcFilesystem::ghc_filesystem) 40 | target_link_libraries(zeek-agent PRIVATE nlohmann_json) 41 | target_link_libraries(zeek-agent PRIVATE ixwebsocket::ixwebsocket) 42 | target_link_libraries(zeek-agent PRIVATE pathfind::pathfind) 43 | target_link_libraries(zeek-agent PRIVATE replxx::replxx) 44 | target_link_libraries(zeek-agent PRIVATE reproc++) 45 | target_link_libraries(zeek-agent PRIVATE spdlog::spdlog) 46 | target_link_libraries(zeek-agent PRIVATE sqlite::sqlite) 47 | target_link_libraries(zeek-agent PRIVATE stduuid) 48 | target_link_libraries(zeek-agent PRIVATE tomlplusplus::tomlplusplus) 49 | target_link_libraries(zeek-agent PRIVATE ztd::out_ptr) 50 | target_link_libraries(zeek-agent PRIVATE Glob) 51 | 52 | if ( USE_BROKER ) 53 | target_link_libraries(zeek-agent PRIVATE broker_static) 54 | endif () 55 | 56 | if ( NOT HAVE_GETOPT_LONG ) 57 | target_link_libraries(zeek-agent PRIVATE 3rdparty:3rdparty) 58 | endif() 59 | 60 | if ( HAVE_WINDOWS ) 61 | target_link_libraries(zeek-agent PRIVATE ntdll) 62 | target_link_libraries(zeek-agent PRIVATE ws2_32) 63 | target_link_libraries(zeek-agent PRIVATE iphlpapi) 64 | target_link_libraries(zeek-agent PRIVATE wbemuuid) 65 | target_link_libraries(zeek-agent PRIVATE shlwapi) 66 | endif() 67 | 68 | if ( NOT HAVE_DARWIN ) # On Darwin, we customize the install location later 69 | install(TARGETS zeek-agent) 70 | endif () 71 | 72 | configure_file(config.h.in "${CMAKE_BINARY_DIR}/include/autogen/config.h") 73 | 74 | # Create a test with a dependency on zeek-agent. 75 | # See https://stackoverflow.com/a/56448477 76 | add_test(build-zeek-agent "${CMAKE_COMMAND}" --build "${CMAKE_BINARY_DIR}" --config "$" --target zeek-agent) 77 | set_tests_properties(build-zeek-agent PROPERTIES FIXTURES_SETUP test_fixture) 78 | add_test(NAME zeek-agent COMMAND ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/zeek-agent --test) 79 | set_tests_properties(zeek-agent PROPERTIES FIXTURES_REQUIRED test_fixture) 80 | -------------------------------------------------------------------------------- /src/config.h.in: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #cmakedefine HAVE_POSIX 6 | #cmakedefine HAVE_DARWIN 7 | #cmakedefine HAVE_LINUX 8 | #cmakedefine HAVE_WINDOWS 9 | #cmakedefine HAVE_GCC 10 | #cmakedefine HAVE_CLANG 11 | #cmakedefine HAVE_MSVC 12 | #cmakedefine HAVE_GETOPT_LONG 13 | #cmakedefine HAVE_BROKER 14 | 15 | namespace zeek::agent { 16 | inline const auto Version = "${ZEEK_AGENT_VERSION}"; 17 | inline const auto VersionLong = "${ZEEK_AGENT_VERSION_LONG}"; 18 | inline const auto InstallLibDir = "${CMAKE_INSTALL_LIBDIR}"; 19 | inline const auto InstallPrefix = "${CMAKE_INSTALL_PREFIX}"; 20 | } // namespace zeek::agent 21 | -------------------------------------------------------------------------------- /src/core/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent 4 | PRIVATE 5 | configuration.cc 6 | database.cc 7 | logger.cc 8 | scheduler.cc 9 | sqlite.cc 10 | table.cc 11 | ) 12 | 13 | if ( HAVE_POSIX ) 14 | target_sources(zeek-agent PRIVATE signal.posix.cc) 15 | elseif ( HAVE_WINDOWS ) 16 | target_sources(zeek-agent PRIVATE signal.windows.cc) 17 | endif () 18 | -------------------------------------------------------------------------------- /src/core/logger.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "logger.h" 4 | 5 | #include "autogen/config.h" 6 | #include "core/configuration.h" 7 | #include "util/filesystem.h" 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #ifdef HAVE_DARWIN 19 | #include 20 | #endif 21 | 22 | #ifndef HAVE_WINDOWS 23 | #include 24 | #endif 25 | 26 | #ifdef HAVE_WINDOWS 27 | #define STDOUT_FILENO 0 28 | #endif 29 | 30 | using namespace zeek::agent; 31 | 32 | namespace { 33 | std::shared_ptr global_logger = {}; 34 | } 35 | 36 | Result zeek::agent::setGlobalLogger(options::LogType type, options::LogLevel level, 37 | const std::optional& path) { 38 | spdlog::sink_ptr sink{}; 39 | 40 | // Note we are creating thread-safe logger here (`*_mt`). 41 | switch ( type ) { 42 | case options::LogType::Stderr: sink = std::make_shared(); break; 43 | 44 | case options::LogType::Stdout: 45 | if ( isatty(STDOUT_FILENO) ) 46 | sink = std::make_shared(); 47 | else 48 | sink = std::make_shared(); 49 | 50 | break; 51 | 52 | case options::LogType::System: 53 | #if defined(HAVE_LINUX) 54 | sink = std::make_shared("zeek-agent", 0, LOG_USER, false); 55 | #elif defined(HAVE_DARWIN) 56 | sink = std::make_shared(); 57 | #elif defined(HAVE_WINDOWS) 58 | // TODO: Where should Windows system logging go? The event log? 59 | logger()->warn("system logging currently not supported on Windows"); 60 | #else 61 | #error "Unsupported platform in setGlobalLogger()" 62 | #endif 63 | break; 64 | 65 | case options::LogType::File: 66 | if ( ! path ) 67 | return result::Error("file logging requires a path"); 68 | 69 | sink = std::make_shared(path_to_spdlog_filename(*path)); 70 | break; 71 | } 72 | 73 | global_logger = std::make_shared("Zeek Agent", std::move(sink)); 74 | global_logger->set_level(level); 75 | 76 | return Nothing(); 77 | } 78 | 79 | spdlog::logger* zeek::agent::logger() { 80 | if ( ! global_logger ) 81 | // Default until something else is set. 82 | setGlobalLogger(options::LogType::Stdout, options::default_log_level); 83 | 84 | return global_logger.get(); 85 | } 86 | -------------------------------------------------------------------------------- /src/core/logger.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "core/configuration.h" 6 | 7 | #include 8 | 9 | namespace zeek::agent { 10 | 11 | Result setGlobalLogger(options::LogType type, options::LogLevel level, 12 | const std::optional& path = {}); 13 | 14 | /** Returns the global logger instance. Use of the logger is thread-safe. */ 15 | extern spdlog::logger* logger(); 16 | 17 | #define __ZEEK_AGENT_LOG(level, component, ...) /* NOLINT */ \ 18 | logger()->log(level, frmt("[{}] ", component) + frmt(__VA_ARGS__)) 19 | 20 | #ifndef NDEBUG 21 | #define ZEEK_AGENT_DEBUG(component, ...) __ZEEK_AGENT_LOG(spdlog::level::debug, component, __VA_ARGS__) 22 | #define ZEEK_AGENT_TRACE(component, ...) __ZEEK_AGENT_LOG(spdlog::level::trace, component, __VA_ARGS__) 23 | #else 24 | #define ZEEK_AGENT_DEBUG(component, ...) __ZEEK_AGENT_LOG(spdlog::level::debug, component, __VA_ARGS__) 25 | #define ZEEK_AGENT_TRACE(component, ...) 26 | #endif 27 | 28 | } // namespace zeek::agent 29 | -------------------------------------------------------------------------------- /src/core/scheduler.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "util/helpers.h" 6 | #include "util/pimpl.h" 7 | 8 | #include 9 | 10 | namespace zeek::agent { 11 | 12 | namespace timer { 13 | /** Unique ID for a scheduled timer. */ 14 | using ID = uint64_t; 15 | 16 | /** 17 | * Callback triggering when a timer expires. If the callback returns a non-zero interval, the 18 | * scheduler will automatically reschedule the timer the given amount of time into the future. 19 | * The reschedule timer will keep its ID. 20 | */ 21 | using Callback = std::function; 22 | } // namespace timer 23 | 24 | namespace task { 25 | /** Callback for an operation queued for execution. */ 26 | using Callback = std::function; 27 | } // namespace task 28 | 29 | /** 30 | * Manages a set of scheduled timers with associated callbacks to eventually 31 | * execute. The scheduler has an internal notion of time and will execute the 32 | * callback associated with a timer once its expiration time has been reached. 33 | * Timer and callback will then be deleted afterwards. 34 | * 35 | * A scheduler doesn't automatically advances it's time; that must be driven 36 | * externally by calls to `advance()`. That allows the caller to decide the 37 | * semantics of time: they can drive it either through a real-time clock, or 38 | * for example through a deterministic sequence of fixed steps. The latter is 39 | * particularly useful for unit testing. 40 | * 41 | * Note that methods of this class aren't thread-safe unless stated othewise. 42 | */ 43 | class Scheduler : public Pimpl { 44 | public: 45 | Scheduler(); 46 | ~Scheduler(); 47 | 48 | /** 49 | * Schedules a new timer to a specific point of time in the future. 50 | * 51 | * This method is thread-safe and may be called from any thread. The 52 | * callback will always be executed on the main thread. 53 | * 54 | * @param t time to schedule the timer for 55 | * @param cb callback to execute when `t` has been reached 56 | * @returns a unique ID for the scheduled timer, which will remain valid 57 | * until the timer fires or gets canceled 58 | */ 59 | timer::ID schedule(Time t, timer::Callback cb); 60 | 61 | /** 62 | * Enqueues a callback to execute at the next possible opportunity fro the 63 | * scheduler's thread. 64 | * 65 | * This method is thread-safe and may be called from any thread. The 66 | * callback will always be executed on the main thread. 67 | * 68 | * @param cb callback to execute as soon as possible 69 | */ 70 | void schedule(task::Callback cb); 71 | 72 | /** 73 | * Cancels a previously installed timer. The timer will be deleted without 74 | * its callback executing. 75 | * 76 | * @param id timer's ID as previously returned by one of the `schedule()` 77 | * methods; it's ok if the timer already doesn't exist anymore 78 | */ 79 | void cancel(timer::ID id); 80 | 81 | /** 82 | * Advances to scheduler's notion of the current time. This will let all 83 | * timers fire that are currently scheduled for a time <= `now`. Their 84 | * callbacks will have fully executed before the method returns, and they 85 | * will run from the same thread as the caller of this method. Advancing 86 | * will also execute any callbacks scheduled with 87 | * `registerAdvanceCallback()`. 88 | * 89 | * @param t new current time for the scheduler; if `t` is <= `now()`, the 90 | * method will not do anything 91 | */ 92 | void advance(Time t); 93 | 94 | /** 95 | * Returns the scheduler's current time. This is the most recent time 96 | * passed to `advance(). 97 | */ 98 | Time currentTime() const; 99 | 100 | /** 101 | * Returns the number of timers/tasks currently scheduled for execution. 102 | */ 103 | size_t pendingTimers() const; 104 | 105 | /** 106 | * Request processing to terminate. This will stop 107 | * `processUntilTerminated()`, but can also act more globally as signal to 108 | * users of the scheduler to cease operations. They can query the 109 | * termination state through `terminating()`. 110 | */ 111 | void terminate(); 112 | 113 | /** Returns true if `terminate()` has been called previously. */ 114 | bool terminating() const; 115 | 116 | /** 117 | * Executes pending activity up to current wall clock. If nothing is 118 | * pending, blocks for a little while (with new activity interrupting the 119 | * block). 120 | * 121 | * @return true if processing is to continue; false if the scheduler has 122 | * been asked to terminate 123 | */ 124 | bool loop(); 125 | }; 126 | 127 | } // namespace zeek::agent 128 | -------------------------------------------------------------------------------- /src/core/signal.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "util/pimpl.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | namespace zeek::agent { 12 | // Signal number, specified through the standard `SIGxxx` macros 13 | using Signal = int; 14 | 15 | class SignalManager; 16 | 17 | namespace signal { 18 | 19 | class Handler { 20 | public: 21 | using Callback = std::function; 22 | 23 | /** 24 | * Constructor. Instantiates a handler and registers it with a manager for 25 | * handling a specified signal. The handler will remain active during its 26 | * entire life-time and unregster itself at destruction time. 27 | * 28 | * @param mgr manager to register the handler with 29 | * @param signal signal number, which must be part of the set the manager was asked to handle 30 | * @param cb callback to execute when manager receives signal 31 | */ 32 | Handler(SignalManager* mgr, Signal sig, Callback cb); 33 | ~Handler(); 34 | 35 | private: 36 | SignalManager* _manager; 37 | Signal _signal; 38 | std::list::iterator _handler; // position of handler in manager's list 39 | }; 40 | } // namespace signal 41 | 42 | /** 43 | * Manages Unix signal handling by keeping a registry of signal handlers to 44 | * dispatch to when receiving a signal. To register a handler, instantiate it 45 | * with the manager as an argument. If multiple handlers are registered for the 46 | * same signal, the most recent one will receive control when the signal fires. 47 | * 48 | * A manager assumes that it's the only one manipulating signal behaviour for 49 | * the current process. That implies that no two managers should exist at the 50 | * same time. 51 | **/ 52 | class SignalManager : public Pimpl { 53 | public: 54 | /** 55 | * Constructor. 56 | * 57 | * @param set of signals that this manager is to (exclusively) handle 58 | */ 59 | SignalManager(const std::vector& signals_to_handle); 60 | ~SignalManager(); 61 | }; 62 | 63 | extern SignalManager* signal_mgr; 64 | 65 | } // namespace zeek::agent 66 | -------------------------------------------------------------------------------- /src/core/signal.windows.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "./signal.h" 4 | 5 | #include "logger.h" 6 | #include "util/fmt.h" 7 | #include "util/testing.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | using namespace zeek::agent; 18 | 19 | template<> 20 | struct Pimpl::Implementation { 21 | ~Implementation(); 22 | 23 | // Globally blocks the given signals for the current process. 24 | void blockSignals(const std::vector& signals) const; 25 | 26 | // Restores default behaviour for all signals blocked by `blockSignals`. 27 | void restoreSignals() const; 28 | 29 | // Starts signal handling. 30 | void start() const; 31 | 32 | // Stops signal handling. 33 | void stop() const; 34 | 35 | std::map> _handlers; // map of handler LIFO lists indexed by signal 36 | std::mutex _handlers_mutex; // guards access to _handlers map 37 | }; 38 | 39 | BOOL WINAPI CtrlHandler(DWORD ctrl) { 40 | switch ( ctrl ) { 41 | case CTRL_C_EVENT: { 42 | std::lock_guard lock(signal_mgr->pimpl()->_handlers_mutex); 43 | if ( const auto& x = signal_mgr->pimpl()->_handlers[SIGINT]; x.size() ) 44 | x.back()(); // keep lock during callback that handler can't go away 45 | return TRUE; 46 | } 47 | default: return FALSE; 48 | } 49 | } 50 | 51 | signal::Handler::Handler(SignalManager* mgr, Signal sig, Callback cb) : _manager(mgr), _signal(sig) { 52 | ZEEK_AGENT_DEBUG("signal manager", "installing handler for signal {}", _signal); 53 | std::lock_guard lock(_manager->pimpl()->_handlers_mutex); 54 | 55 | auto& handlers = _manager->pimpl()->_handlers[_signal]; 56 | handlers.push_back(std::move(cb)); 57 | _handler = --handlers.end(); 58 | } 59 | 60 | signal::Handler::~Handler() { 61 | ZEEK_AGENT_DEBUG("signal manager", "uninstalling handler for signal {}", _signal); 62 | std::lock_guard lock(_manager->pimpl()->_handlers_mutex); 63 | 64 | _manager->pimpl()->_handlers[_signal].erase(_handler); 65 | } 66 | 67 | SignalManager::Implementation::~Implementation() { 68 | std::lock_guard lock(_handlers_mutex); 69 | _handlers.clear(); 70 | } 71 | 72 | void SignalManager::Implementation::blockSignals(const std::vector& signals) const { 73 | // We define the signal values in signal.h for WIN32 but we don't actually want to handle any of them 74 | // here except for SIGINT. If we get a value that isn't that, return an error. 75 | for ( auto sig : signals ) 76 | if ( sig != SIGINT ) 77 | throw FatalError("Signals other than SIGINT are unhandled on Windows"); 78 | } 79 | 80 | void SignalManager::Implementation::restoreSignals() const {} 81 | 82 | void SignalManager::Implementation::start() const { 83 | if ( ! SetConsoleCtrlHandler(CtrlHandler, TRUE) ) 84 | throw FatalError("Failed to register console control handler"); 85 | } 86 | 87 | void SignalManager::Implementation::stop() const { 88 | if ( ! SetConsoleCtrlHandler(CtrlHandler, FALSE) ) 89 | throw FatalError("Failed to unregister console control handler"); 90 | } 91 | 92 | SignalManager::SignalManager(const std::vector& signals_to_handle) { 93 | ZEEK_AGENT_DEBUG("signal manager", "creating instance, handling signals: {}", 94 | join(transform(signals_to_handle, [](auto i) { return std::to_string(i); }), ", ")); 95 | 96 | pimpl()->blockSignals(signals_to_handle); 97 | pimpl()->start(); 98 | } 99 | 100 | SignalManager::~SignalManager() { 101 | ZEEK_AGENT_DEBUG("signal manager", "destroying instance"); 102 | pimpl()->stop(); 103 | } 104 | -------------------------------------------------------------------------------- /src/io/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent 4 | PRIVATE 5 | console.cc 6 | zeek.cc 7 | ) 8 | -------------------------------------------------------------------------------- /src/io/console.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "util/filesystem.h" 6 | #include "util/pimpl.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | namespace zeek::agent { 14 | 15 | class Database; 16 | class Scheduler; 17 | class SignalManager; 18 | 19 | /** 20 | * Provides an interactive console to execute queries and commands. The console 21 | * will execute in a separate thread. 22 | * 23 | * There should be only one console instance at a time. 24 | * 25 | * All public methods are thread-safe. 26 | */ 27 | class ConsoleServer : public Pimpl { 28 | public: 29 | /** 30 | * Constructor. TODO: Update 31 | * 32 | * @param database database to use for queries; observer only, doesn't take ownership 33 | * @param scheduler scheduler to use for any timers; observer only, doesn't take ownership 34 | */ 35 | ConsoleServer(const filesystem::path& socket, Database* db, Scheduler* scheduler); 36 | ~ConsoleServer(); 37 | 38 | /** Starts a console server thread. */ 39 | void start(); 40 | 41 | /** Stops the console server thread. */ 42 | void stop(); 43 | }; 44 | 45 | class ConsoleClient : public Pimpl { 46 | public: 47 | /** 48 | * Constructor. TODO: Update 49 | * 50 | * @param database database to use for queries; observer only, doesn't take ownership 51 | * @param scheduler scheduler to use for any timers; observer only, doesn't take ownership 52 | * @param signal_mgr signal manager to install handlers with; observer only, doesn't take ownership; can be left 53 | * null for testing purposes (will prevent aborting with SIGINT) 54 | */ 55 | ConsoleClient(const filesystem::path& socket, Scheduler* scheduler, SignalManager* signal_mgr); 56 | ~ConsoleClient(); 57 | 58 | /** 59 | * Schedule a single statement for execution, to then exit once it has 60 | * finished. Must be called before `start(). 61 | * 62 | * @param stmt statement to execute 63 | * @param echo whether to echo the result to tty 64 | */ 65 | void scheduleStatementWithTermination(std::string stmt, bool echo = true); 66 | 67 | /** 68 | * Starts a console server thread. 69 | * 70 | * @param run_repl whether to run the REPL loop in the client; only turn 71 | * off for testing purposes 72 | */ 73 | void start(bool run_repl = true); 74 | 75 | /** Stops the console server thread. */ 76 | void stop(); 77 | }; 78 | 79 | } // namespace zeek::agent 80 | -------------------------------------------------------------------------------- /src/io/zeek.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "util/pimpl.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | namespace zeek::agent { 13 | 14 | class Database; 15 | class Scheduler; 16 | 17 | /** 18 | * Provides the connector to external Zeek instances. 19 | * 20 | * There should be only one Zeek connector instance at a time. 21 | * 22 | * All public methods are thread-safe. 23 | */ 24 | class Zeek : public Pimpl { 25 | public: 26 | /** 27 | * Constructor. 28 | * 29 | * @param database database to use for queries; observer only, doesn't take ownership 30 | * @param scheduler scheduler to use for any timeers; observer only, doesn't take ownership 31 | */ 32 | Zeek(Database* db, Scheduler* scheduler); 33 | ~Zeek(); 34 | 35 | /** 36 | * Starts communication with external Zeek instances. 37 | * 38 | * @param zeeks Zeek destinations to connect to, in the form of `
:[port]` 39 | */ 40 | void start(const std::vector& zeeks); 41 | 42 | /** 43 | * Terminates all Zeek communication. 44 | */ 45 | void stop(); 46 | 47 | /** Performs maintaince tasks and must be called regularly from the main loop. */ 48 | void poll(); 49 | }; 50 | 51 | } // namespace zeek::agent 52 | -------------------------------------------------------------------------------- /src/platform/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent 4 | PRIVATE 5 | testing.cc 6 | ) 7 | 8 | if ( HAVE_DARWIN ) 9 | add_subdirectory(darwin) 10 | endif () 11 | 12 | if ( HAVE_LINUX ) 13 | add_subdirectory(linux) 14 | endif () 15 | 16 | if ( HAVE_WINDOWS ) 17 | add_subdirectory(windows) 18 | endif () 19 | -------------------------------------------------------------------------------- /src/platform/darwin/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent PRIVATE platform.mm endpoint-security.mm network-extension.mm xpc.mm os-log-sink.mm) 4 | target_link_libraries(zeek-agent PRIVATE "EndpointSecurity") 5 | target_link_libraries(zeek-agent PRIVATE "-framework NetworkExtension") 6 | target_link_libraries(zeek-agent PRIVATE "-framework Cocoa") 7 | target_link_libraries(zeek-agent PRIVATE "bsm") 8 | 9 | add_subdirectory(ZeekAgent.app) 10 | -------------------------------------------------------------------------------- /src/platform/darwin/CPPLINT.cfg: -------------------------------------------------------------------------------- 1 | # cpplint gets confused with the Objective-C syntax in this header. 2 | exclude_files=xpc.h 3 | -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # Always build universal version of the app (CMake doesn't support this out of 3 | # the box). 4 | add_executable(ZeekAgent-x86 main.swift ExtensionManager.swift XPC.swift) 5 | add_executable(ZeekAgent-arm64 main.swift ExtensionManager.swift XPC.swift) 6 | target_compile_options(ZeekAgent-x86 PRIVATE -target "x86_64-apple-macos${CMAKE_OSX_DEPLOYMENT_TARGET}") 7 | target_compile_options(ZeekAgent-arm64 PRIVATE -target "arm64-apple-macos${CMAKE_OSX_DEPLOYMENT_TARGET}") 8 | 9 | add_custom_target(ZeekAgent ALL 10 | COMMAND lipo -create ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ZeekAgent-x86 ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ZeekAgent-arm64 -output ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ZeekAgent 11 | DEPENDS ZeekAgent-x86 ZeekAgent-arm64) 12 | 13 | configure_file("Info.plist.app.in" "Info.plist.app") 14 | configure_file("Info.plist.agent.in" "Info.plist.agent") 15 | 16 | set(packaging "${CMAKE_SOURCE_DIR}/packaging") 17 | set(contents "..") # target paths are releative to "Contents/contents", so "Contents" is one level up. 18 | set(resources ".") 19 | 20 | # Additional pieces/config for the outer app. 21 | install(PROGRAMS ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}/ZeekAgent DESTINATION ${contents}/MacOS) 22 | install(FILES "embedded.provisionprofile.app" DESTINATION "${contents}" RENAME "embedded.provisionprofile") 23 | install(FILES "zeek.png" DESTINATION "${resources}") 24 | install(FILES "${packaging}/zeek-agent.cfg.template" DESTINATION "${resources}") 25 | 26 | # System extension (the main zeek-agent binary) 27 | set(sext_base "${contents}/Library/SystemExtensions/org.zeek.zeek-agent.agent.systemextension") 28 | install(DIRECTORY DESTINATION "${sext_base}/Contents/MacOS") 29 | install(DIRECTORY DESTINATION "${sext_base}/Contents/Resources") 30 | install(TARGETS zeek-agent DESTINATION "${sext_base}/Contents/MacOS") 31 | install(FILES "${CMAKE_CURRENT_BINARY_DIR}/Info.plist.agent" DESTINATION "${sext_base}/Contents" RENAME "Info.plist") 32 | install(FILES "embedded.provisionprofile.agent" DESTINATION "${sext_base}/Contents" RENAME "embedded.provisionprofile") 33 | install(FILES "ZeekAgent.icns" DESTINATION "${sext_base}/Contents/Resources") 34 | -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/ExtensionManager.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | import NetworkExtension 4 | import SystemExtensions 5 | 6 | // Handles activation and configuration of our macOS system extension. 7 | class ExtensionManager: NSObject, OSSystemExtensionRequestDelegate { 8 | private var version_old: String? 9 | private var version_new: String? 10 | 11 | // Trigger installation of our system extension. 12 | public func install() { 13 | let r = OSSystemExtensionRequest.activationRequest( 14 | forExtensionWithIdentifier: "org.zeek.zeek-agent.agent", queue: .main) 15 | r.delegate = self 16 | OSSystemExtensionManager.shared.submitRequest(r) 17 | } 18 | 19 | // Enable the network extension. 20 | func startFilter() { 21 | let mgr = NEFilterManager.shared() 22 | guard !mgr.isEnabled else { return } 23 | 24 | loadFilterConfiguration { success in 25 | guard success else { return } 26 | mgr.isEnabled = true 27 | 28 | if mgr.providerConfiguration == nil { 29 | let providerConfiguration = NEFilterProviderConfiguration() 30 | providerConfiguration.filterSockets = true 31 | providerConfiguration.filterPackets = false 32 | 33 | mgr.providerConfiguration = providerConfiguration 34 | mgr.localizedDescription = "Zeek Agent" 35 | } 36 | 37 | self.saveFilterConfiguration(mgr) 38 | } 39 | } 40 | 41 | // Disable the network extension. 42 | func stopFilter() { 43 | let mgr = NEFilterManager.shared() 44 | guard mgr.isEnabled else { return } 45 | 46 | loadFilterConfiguration { success in 47 | guard success else { return } 48 | mgr.isEnabled = false 49 | self.saveFilterConfiguration(mgr) 50 | } 51 | } 52 | 53 | // Callback 54 | internal func request( 55 | _ request: OSSystemExtensionRequest, 56 | didFinishWithResult result: OSSystemExtensionRequest.Result 57 | ) { 58 | var version_msg = "" 59 | 60 | if version_old != nil && version_new != nil { 61 | version_msg = 62 | (version_old == version_new 63 | ? "Reinstalled version \(version_new!)." 64 | : "Upgraded from version \(version_old!) to \(version_new!).") 65 | version_msg += "\n\n" 66 | } 67 | 68 | self.startFilter() 69 | Controller.shared.showMessage(msg: "Installed Zeek Agent", sub: version_msg) 70 | Controller.shared.xpc.resetConnection() 71 | Controller.shared.openMain() 72 | } 73 | 74 | // Callback 75 | internal func request(_ request: OSSystemExtensionRequest, didFailWithError error: Error) { 76 | Controller.shared.showMessage( 77 | msg: "System extension failed to install", sub: error.localizedDescription, style: .critical) 78 | Controller.shared.application.terminate(self) 79 | } 80 | 81 | // Callback 82 | internal func requestNeedsUserApproval(_ request: OSSystemExtensionRequest) { 83 | // This comes after the system's dialog sending people to preferences, which seems confusing. 84 | // Controller.shared.showMessage( 85 | // msg: "Zeek Agent needs permission to monitor the system", sub: "Please allow access in System Preferences.") 86 | Controller.shared.openMain() 87 | } 88 | 89 | // Callback 90 | internal func request( 91 | _ request: OSSystemExtensionRequest, actionForReplacingExtension existing: OSSystemExtensionProperties, 92 | withExtension extension_: OSSystemExtensionProperties 93 | ) -> OSSystemExtensionRequest.ReplacementAction { 94 | version_old = existing.bundleVersion 95 | version_new = extension_.bundleVersion 96 | return .replace 97 | } 98 | 99 | // Helper to load the current filter configuration. 100 | internal func loadFilterConfiguration(completionHandler: @escaping (Bool) -> Void) { 101 | NEFilterManager.shared().loadFromPreferences { error in 102 | DispatchQueue.main.async { 103 | var success = true 104 | if let error = error { 105 | Controller.shared.log("Failed to load the filter configuration: \(error.localizedDescription)") 106 | success = false 107 | } 108 | completionHandler(success) 109 | } 110 | } 111 | } 112 | 113 | // Helpers to save a modified filter configuration. 114 | internal func saveFilterConfiguration(_ mgr: NEFilterManager) { 115 | mgr.saveToPreferences { error in 116 | DispatchQueue.main.async { 117 | if let error = error { 118 | Controller.shared.log("Failed to save the filter configuration: \(error.localizedDescription)") 119 | return 120 | } 121 | } 122 | } 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/Info.plist.agent.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | CFBundlePackageType 4 | SYSX 5 | CFBundleName 6 | ZeekAgentSystemExtension 7 | CFBundleDisplayName 8 | Zeek Agent – System Extension 9 | CFBundleIdentifier 10 | org.zeek.zeek-agent.agent 11 | CFBundleVersion 12 | ${ZEEK_AGENT_VERSION} 13 | CFBundleExecutable 14 | zeek-agent 15 | CFBundleShortVersionString 16 | ${ZEEK_AGENT_VERSION}> 17 | CFBundleIconFile 18 | ZeekAgent 19 | LSMinimumSystemVersion 20 | ${CMAKE_OSX_DEPLOYMENT_TARGET} 21 | CFBundleDevelopmentRegion 22 | English 23 | 24 | NSSystemExtensionUsageDescription 25 | Endpoint agent forwarding host activity to Zeek 26 | 27 | NSEndpointSecurityMachServiceName 28 | org.zeek.zeek-agent.agent 29 | 30 | NSSystemExtensionUsageDescription 31 | Endpoint agent forwarding host activity to Zeek 32 | 33 | NetworkExtension 34 | 35 | NEMachServiceName 36 | org.zeek.zeek-agent.agent 37 | 38 | NEProviderClasses 39 | 40 | com.apple.networkextension.filter-data 41 | FilterDataProvider 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/Info.plist.app.in: -------------------------------------------------------------------------------- 1 | 2 | 3 | CFBundlePackageType 4 | APPL 5 | CFBundleName 6 | ZeekAgent 7 | CFBundleDisplayName 8 | Zeek Agent 9 | CFBundleIdentifier 10 | org.zeek.zeek-agent 11 | CFBundleVersion 12 | ${ZEEK_AGENT_VERSION} 13 | CFBundleExecutable 14 | ZeekAgent 15 | CFBundleShortVersionString 16 | ${ZEEK_AGENT_VERSION}> 17 | CFBundleIconFile 18 | ZeekAgent 19 | LSMinimumSystemVersion 20 | ${CMAKE_OSX_DEPLOYMENT_TARGET} 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/XPC.swift: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | import Foundation 4 | import os.log 5 | 6 | // XPC protocol between container app to communicate with the extension. The 7 | // app the caller, the extension the receiver. Any changes here need to be 8 | // reflected on the Objective-C side inside the extension. 9 | @objc(IPCProtocol) protocol IPCProtocol { 10 | func getStatus(reply: @escaping (String, String, String) -> Void) 11 | func getOptions(reply: @escaping ([String: String]) -> Void) 12 | func setOptions(_ options: [String: String]) 13 | func exit() 14 | } 15 | 16 | // API for the container app to communicate with the extension. 17 | class XPC { 18 | private var service: IPCProtocol? 19 | private var connection: NSXPCConnection? 20 | 21 | private let logger = Logger(subsystem: "org.zeek.zeek-agent", category: "installer") 22 | 23 | func log(_ msg: String) { 24 | logger.info("\(msg, privacy: .public)") 25 | } 26 | 27 | // Returns true if the extension is currently running (i.e., we can 28 | // communicate with it). If yes, returns the version string of the running instance. 29 | public func isExtensionRunning() -> String? { 30 | let semaphore = DispatchSemaphore(value: 0) 31 | 32 | var version: String? 33 | _ = getStatus { version_, capabilities, agent_id in 34 | version = version_ 35 | semaphore.signal() 36 | } 37 | 38 | _ = semaphore.wait(timeout: .now() + 1) 39 | return version 40 | } 41 | 42 | // XPC: Retrieve status from extension. 43 | public func getStatus(_ callback: @escaping ((String, String, String) -> Void)) -> Bool { 44 | guard let service = connectXPC() else { return false } 45 | service.getStatus(reply: callback) 46 | return true 47 | } 48 | 49 | // XPC: Retrieve configuration options from extension. 50 | public func getOptions(_ callback: @escaping (([String: String]) -> Void)) -> Bool { 51 | guard let service = connectXPC() else { return false } 52 | service.getOptions(reply: callback) 53 | return true 54 | } 55 | 56 | // XPC: Send options to extension. 57 | public func setOptions(_ options: [String: String]) -> Bool { 58 | guard let service = connectXPC() else { return false } 59 | service.setOptions(options) 60 | return true 61 | } 62 | 63 | // XPC: Restart the extension's process. 64 | public func restart() -> Bool { 65 | guard let service = connectXPC() else { return false } 66 | service.exit() 67 | return true 68 | } 69 | 70 | // Terminates the current XPC connection to the extension (if any), and 71 | // resets any internal state. 72 | public func resetConnection() { 73 | service = nil 74 | connection = nil 75 | } 76 | 77 | // Initiates an XPC connection the extension. If the connection is already 78 | // up, returns directly without doing anything. 79 | private func connectXPC() -> IPCProtocol? { 80 | if connection == nil { 81 | connection = NSXPCConnection(machServiceName: "org.zeek.zeek-agent.agent") 82 | if connection == nil { 83 | logger.error("Failed to create XPC connection") 84 | return nil 85 | } 86 | 87 | connection!.remoteObjectInterface = NSXPCInterface(with: IPCProtocol.self) 88 | connection!.interruptionHandler = { self.resetConnection() } 89 | connection!.invalidationHandler = { self.resetConnection() } 90 | connection!.resume() 91 | service = nil 92 | } 93 | 94 | if service == nil { 95 | service = 96 | connection!.remoteObjectProxyWithErrorHandler { error in 97 | self.resetConnection() 98 | } as? IPCProtocol 99 | } 100 | 101 | return service 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/ZeekAgent.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/src/platform/darwin/ZeekAgent.app/ZeekAgent.icns -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/ZeekAgent.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/src/platform/darwin/ZeekAgent.app/ZeekAgent.iconset/icon_128x128.png -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/ZeekAgent.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/src/platform/darwin/ZeekAgent.app/ZeekAgent.iconset/icon_256x256.png -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/ZeekAgent.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/src/platform/darwin/ZeekAgent.app/ZeekAgent.iconset/icon_32x32.png -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/ZeekAgent.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/src/platform/darwin/ZeekAgent.app/ZeekAgent.iconset/icon_512x512.png -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/ZeekAgent.iconset/icon_64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/src/platform/darwin/ZeekAgent.app/ZeekAgent.iconset/icon_64x64.png -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/embedded.provisionprofile.agent: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/src/platform/darwin/ZeekAgent.app/embedded.provisionprofile.agent -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/embedded.provisionprofile.app: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/src/platform/darwin/ZeekAgent.app/embedded.provisionprofile.app -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/entitlements.plist.agent: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.application-identifier 6 | org.zeek.zeek-agent.agent 7 | 8 | com.apple.security.application-groups 9 | 10 | org.zeek.zeek-agent 11 | 12 | 13 | com.apple.developer.endpoint-security.client 14 | 15 | 16 | com.apple.developer.networking.networkextension 17 | 18 | content-filter-provider-systemextension 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/entitlements.plist.app: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.application-identifier 6 | org.zeek.zeek-agent.agent 7 | 8 | com.apple.security.application-groups 9 | 10 | org.zeek.zeek-agent 11 | 12 | 13 | com.apple.developer.system-extension.install 14 | 15 | 16 | com.apple.developer.endpoint-security.client 17 | 18 | 19 | com.apple.developer.networking.networkextension 20 | 21 | content-filter-provider-systemextension 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/platform/darwin/ZeekAgent.app/zeek.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zeek/zeek-agent-v2/fb1fe0f8017db6c30356e3ea77595ae432950efa/src/platform/darwin/ZeekAgent.app/zeek.png -------------------------------------------------------------------------------- /src/platform/darwin/endpoint-security.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "util/result.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | namespace zeek::agent::platform::darwin { 14 | 15 | class EndpointSecurity; 16 | 17 | namespace es { 18 | 19 | /** 20 | * State for an active subscription to EndpointSecurity events. An instance of 21 | * this class is creating when subscribing to ES events, and the subscription 22 | * is kept active as long as the instance exists. 23 | */ 24 | class Subscriber { 25 | public: 26 | ~Subscriber(); 27 | 28 | private: 29 | friend class darwin::EndpointSecurity; 30 | Subscriber(std::string tag, es_client_t* client); 31 | 32 | std::string _tag; 33 | es_client_t* _client = nullptr; 34 | }; 35 | 36 | } // namespace es 37 | 38 | /** 39 | * Wrapper around macOS's Endpoint Security API. This encapsulates API state 40 | * across multiple clients, maintaining just a single internal copy. 41 | */ 42 | class EndpointSecurity { 43 | public: 44 | using Events = std::vector; 45 | using Callback = std::function; 46 | 47 | ~EndpointSecurity(); 48 | 49 | /** 50 | * Returns success if EndpointSecurity is available for use, or a error 51 | * otherwise describibing why it's not. 52 | * 53 | * @returns success or an appropiate error message if ES isn't available; 54 | * then no functionlity must be used 55 | */ 56 | Result isAvailable() { return _init_result; } 57 | 58 | /** 59 | * Creates a subscription to EndpointSecurity events. 60 | * 61 | * @param tag descriptive tag identifying the subscription in log messages 62 | * 63 | * @param events set of events to subscribe to; see 64 | * https://developer.apple.com/documentation/endpointsecurity/es_event_type_t for the list 65 | * 66 | * @param callback callback to invoke when an event is received 67 | * 68 | * @returns a subscription objects that keeps the subscription active as long as it exists, or an error if the 69 | * subscription could not be created 70 | */ 71 | Result> subscribe(std::string tag, const Events& events, Callback callback); 72 | 73 | private: 74 | friend EndpointSecurity* endpointSecurity(); 75 | 76 | EndpointSecurity(); 77 | 78 | Result _init_result; // caches result of `isAvailable()` 79 | }; 80 | 81 | /** Returns the global `EndpointSecurity` singleton. */ 82 | EndpointSecurity* endpointSecurity(); 83 | 84 | } // namespace zeek::agent::platform::darwin 85 | -------------------------------------------------------------------------------- /src/platform/darwin/endpoint-security.mm: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | // 3 | // The EndpointSecurity code borrows from 4 | // https://gist.github.com/Omar-Ikram/8e6721d8e83a3da69b31d4c2612a68ba. 5 | 6 | #include "endpoint-security.h" 7 | 8 | #include "autogen/config.h" 9 | #include "core/logger.h" 10 | #include "util/helpers.h" 11 | 12 | #include 13 | 14 | using namespace zeek::agent; 15 | using namespace zeek::agent::platform::darwin; 16 | 17 | es::Subscriber::Subscriber(std::string tag, es_client_t* client) : _tag(std::move(tag)), _client(client) { 18 | if ( ! _client ) 19 | return; 20 | 21 | ZEEK_AGENT_DEBUG("darwin", "[EndpointSecurity] [{}] subscription created", _tag); 22 | } 23 | 24 | es::Subscriber::~Subscriber() { 25 | if ( ! _client ) 26 | return; 27 | 28 | es_delete_client(_client); 29 | ZEEK_AGENT_DEBUG("darwin", "[EndpointSecurity] [{}] subscription deleted", _tag); 30 | } 31 | 32 | EndpointSecurity::EndpointSecurity() { 33 | if ( auto rc = subscribe("CheckAvailability", {}, [](const es_message_t*) {}) ) { 34 | ZEEK_AGENT_DEBUG("darwin", "[EndpointSecurity] available"); 35 | _init_result = Nothing(); 36 | } 37 | else { 38 | ZEEK_AGENT_DEBUG("darwin", "[EndpointSecurity] not available: {}", rc.error()); 39 | _init_result = rc.error(); 40 | } 41 | } 42 | 43 | EndpointSecurity::~EndpointSecurity() {} 44 | 45 | Result> EndpointSecurity::subscribe(std::string tag, const Events& events, 46 | Callback callback) { // NOLINT 47 | es_client_t* client; 48 | es_new_client_result_t res = es_new_client(&client, ^(es_client_t* c, const es_message_t* msg) { 49 | callback(msg); 50 | }); 51 | 52 | switch ( res ) { 53 | case ES_NEW_CLIENT_RESULT_SUCCESS: { 54 | if ( events.size() ) { 55 | if ( es_subscribe(client, events.data(), events.size()) != ES_RETURN_SUCCESS ) 56 | return result::Error("failed to subscribe to EndpointSecurity events"); 57 | } 58 | 59 | return std::unique_ptr( 60 | new es::Subscriber(std::move(tag), events.size() ? client : nullptr)); 61 | } 62 | 63 | case ES_NEW_CLIENT_RESULT_ERR_NOT_ENTITLED: 64 | return result::Error("macOS entitlement not available (com.apple.developer.endpoint-security.client)"); 65 | 66 | case ES_NEW_CLIENT_RESULT_ERR_NOT_PERMITTED: 67 | return result::Error( 68 | "Application lacks Transparency, Consent, and Control (TCC) approval " 69 | "from the user. This can be resolved by granting 'Full Disk Access' from " 70 | "the 'Security & Privacy' tab of System Preferences."); 71 | 72 | case ES_NEW_CLIENT_RESULT_ERR_NOT_PRIVILEGED: return result::Error("not running as root"); 73 | case ES_NEW_CLIENT_RESULT_ERR_TOO_MANY_CLIENTS: return result::Error("too many clients"); 74 | case ES_NEW_CLIENT_RESULT_ERR_INVALID_ARGUMENT: return result::Error("invalid argument"); 75 | case ES_NEW_CLIENT_RESULT_ERR_INTERNAL: return result::Error("internal error"); 76 | } 77 | 78 | cannot_be_reached(); 79 | } 80 | 81 | EndpointSecurity* platform::darwin::endpointSecurity() { 82 | static auto es = std::unique_ptr{}; 83 | 84 | if ( ! es ) 85 | es = std::unique_ptr(new EndpointSecurity); 86 | 87 | return es.get(); 88 | } 89 | -------------------------------------------------------------------------------- /src/platform/darwin/network-extension.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "core/table.h" 6 | #include "platform/darwin/platform.h" 7 | #include "util/pimpl.h" 8 | #include "util/result.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace zeek::agent::platform::darwin { 16 | 17 | class NetworkExtension; 18 | 19 | namespace ne { 20 | 21 | struct Flow { 22 | pid_t pid; 23 | ProcessInfo process; 24 | 25 | Value local_addr; 26 | Value local_port; 27 | Value remote_addr; 28 | Value remote_port; 29 | Value protocol; 30 | Value family; 31 | Value state; 32 | }; 33 | 34 | /** 35 | * State for an active subscription to NetworkExtension events. An instance of 36 | * this class is creating when subscribing to NE events, and the subscription 37 | * is kept active as long as the instance exists. 38 | */ 39 | class Subscriber { 40 | public: 41 | using Callback = std::function; 42 | 43 | ~Subscriber(); 44 | 45 | private: 46 | friend class darwin::NetworkExtension; 47 | Subscriber(NetworkExtension* ne, std::string tag, Callback cb) 48 | : _ne(ne), _tag(std::move(tag)), _callback(std::move(cb)) {} 49 | 50 | NetworkExtension* _ne; 51 | std::string _tag; 52 | Callback _callback; 53 | }; 54 | 55 | } // namespace ne 56 | 57 | /** 58 | * Wrapper around macOS's Network Extension API. This encapsulates API state 59 | * across multiple clients, maintaining just a single internal copy. 60 | */ 61 | class NetworkExtension : public Pimpl { 62 | public: 63 | using Callback = ne::Subscriber::Callback; 64 | 65 | ~NetworkExtension(); 66 | 67 | /** 68 | * Returns success if the Network Extension has been initialized 69 | * successfully, or an error otherwise. 70 | * 71 | * @returns success or an appropriate error message if NE isn't 72 | * available; then no functionality must be used 73 | */ 74 | Result isAvailable(); 75 | 76 | /** 77 | * Creates a subscription to EndpointSecurity events. 78 | * 79 | * @param tag descriptive tag identifying the subscription in log messages 80 | * 81 | * @param callback callback to invoke when an event is received 82 | * 83 | * @returns a subscription object that keeps the subscription active as long as it exists 84 | */ 85 | std::unique_ptr subscribe(std::string tag, Callback callback); 86 | 87 | void newFlow(const ne::Flow& flow); 88 | 89 | private: 90 | friend ne::Subscriber; 91 | friend NetworkExtension* networkExtension(); 92 | 93 | NetworkExtension(); 94 | 95 | std::list _subscribers; 96 | }; 97 | 98 | /** Returns the global `NetworkExtension` singleton. */ 99 | NetworkExtension* networkExtension(); 100 | 101 | /** 102 | * Start operating as a macOS network extension. Must be called from the main 103 | * thread as early as possible, and will not return. 104 | */ 105 | [[noreturn]] extern void enterNetworkExtensionMode(); 106 | 107 | } // namespace zeek::agent::platform::darwin 108 | -------------------------------------------------------------------------------- /src/platform/darwin/os-log-sink.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | 10 | namespace zeek::agent::platform::darwin { 11 | 12 | /** Custom spdlog sink writing to OSLog. */ 13 | class OSLogSink final : public spdlog::sinks::base_sink { 14 | public: 15 | OSLogSink(); 16 | ~OSLogSink() override; 17 | 18 | protected: 19 | void sink_it_(const spdlog::details::log_msg& msg) override; 20 | void flush_() override; 21 | 22 | private: 23 | os_log_t _oslog = nullptr; 24 | }; 25 | 26 | } // namespace zeek::agent::platform::darwin 27 | -------------------------------------------------------------------------------- /src/platform/darwin/os-log-sink.mm: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "os-log-sink.h" 4 | 5 | #include "autogen/config.h" 6 | #include "core/logger.h" 7 | 8 | #include 9 | 10 | using namespace zeek::agent; 11 | using namespace zeek::agent::platform::darwin; 12 | 13 | OSLogSink::OSLogSink() { _oslog = os_log_create("org.zeek.zeek-agent", "agent"); } 14 | 15 | OSLogSink::~OSLogSink() { 16 | if ( _oslog ) 17 | CFRelease(_oslog); 18 | } 19 | 20 | void OSLogSink::sink_it_(const spdlog::details::log_msg& msg) { 21 | std::string formatted = std::string(msg.payload.data(), msg.payload.size()); 22 | os_log_type_t level; 23 | 24 | switch ( msg.level ) { 25 | case spdlog::level::critical: level = OS_LOG_TYPE_ERROR; break; 26 | case spdlog::level::debug: level = OS_LOG_TYPE_DEBUG; break; 27 | case spdlog::level::err: level = OS_LOG_TYPE_ERROR; break; 28 | case spdlog::level::info: level = OS_LOG_TYPE_INFO; break; 29 | case spdlog::level::n_levels: cannot_be_reached(); 30 | case spdlog::level::off: return; 31 | case spdlog::level::trace: level = OS_LOG_TYPE_DEBUG; break; 32 | case spdlog::level::warn: level = OS_LOG_TYPE_INFO; break; 33 | } 34 | 35 | auto log_msg = std::string(msg.payload.data(), msg.payload.size()); 36 | auto log_level = std::string(to_string_view(msg.level).data(), to_string_view(msg.level).size()); 37 | os_log_with_type(_oslog, level, "[%{public}s] %{public}s", log_level.c_str(), log_msg.c_str()); 38 | } 39 | 40 | void OSLogSink::flush_() {} 41 | -------------------------------------------------------------------------------- /src/platform/darwin/platform.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "core/table.h" 6 | 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | namespace zeek::agent::platform::darwin { 13 | 14 | /** 15 | * Returns the path to the `App[lication Support` directory appropiate for the 16 | * user running the agent (which might be the system-wide one for root). 17 | */ 18 | extern std::optional getApplicationSupport(); 19 | 20 | struct ProcessInfo { 21 | Value name; 22 | Value pid; 23 | Value ppid; 24 | Value uid; 25 | Value gid; 26 | Value ruid; 27 | Value rgid; 28 | Value priority; 29 | Value startup; 30 | Value vsize; 31 | Value rsize; 32 | Value utime; 33 | Value stime; 34 | }; 35 | 36 | /** 37 | * Retrieves a list of all currently running processes 38 | * 39 | * @return a list of the PIDs of all processes, or an error if the list cannot 40 | * be obtained 41 | */ 42 | Result> getProcesses(); 43 | 44 | /** 45 | * Given a process ID, returns information about the process. 46 | * 47 | * @param pid the process ID to retrieve information for @return information 48 | * about the process, or an error if the information cannot be obtained; even 49 | * if successful, the struct may have been filled only partially 50 | */ 51 | Result getProcessInfo(pid_t pid); 52 | 53 | } // namespace zeek::agent::platform::darwin 54 | -------------------------------------------------------------------------------- /src/platform/darwin/xpc.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | // 3 | // Defines the XPC protocol and API for communication between the installer app 4 | // and the system extension. 5 | 6 | #pragma once 7 | 8 | #include "core/configuration.h" 9 | 10 | #include 11 | 12 | @protocol IPCProtocol 13 | - (void)getStatusWithReply:(void (^)(NSString*, NSString*, NSString*))reply; 14 | - (void)getOptionsWithReply:(void (^)(NSDictionary*))reply; 15 | - (void)setOptions:(NSDictionary*)options; 16 | - (void)exit; 17 | @end 18 | 19 | @interface IPC : NSObject 20 | + (IPC*)sharedObject; 21 | - (id)init; 22 | - (const zeek::agent::Options&)options; 23 | - (void)updateOptions; 24 | @property(strong) NSUserDefaults* defaults; 25 | @property(strong) NSXPCListener* listener; 26 | @property zeek::agent::Configuration* configuration; 27 | @end 28 | -------------------------------------------------------------------------------- /src/platform/darwin/xpc.mm: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "xpc.h" 4 | 5 | #include "core/logger.h" 6 | #include "endpoint-security.h" 7 | #include "network-extension.h" 8 | 9 | using namespace zeek::agent; 10 | 11 | @implementation IPC 12 | 13 | + (IPC*)sharedObject { 14 | static dispatch_once_t once; 15 | static IPC* sharedObject; 16 | dispatch_once(&once, ^{ 17 | sharedObject = [[self alloc] init]; 18 | }); 19 | 20 | return sharedObject; 21 | } 22 | 23 | - (instancetype)init { 24 | [super init]; 25 | _defaults = [[NSUserDefaults alloc] initWithSuiteName:@"group.org.zeek.zeek-agent"]; 26 | _listener = [[NSXPCListener alloc] initWithMachServiceName:@"org.zeek.zeek-agent.agent"]; 27 | _listener.delegate = self; 28 | [_listener resume]; 29 | 30 | return self; 31 | } 32 | 33 | - (void)dealloc { 34 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 35 | [super dealloc]; 36 | } 37 | 38 | - (void)updateOptions { 39 | logger()->debug("updating configuration"); 40 | 41 | auto options = _configuration->options(); 42 | 43 | auto log_level = [_defaults stringForKey:@"log.level"]; 44 | if ( log_level ) { 45 | if ( [log_level isEqual:@""] ) 46 | options.log_level = options::default_log_level; 47 | else if ( auto rc = options::log_level::from_str([log_level UTF8String]) ) 48 | options.log_level = *rc; 49 | else 50 | logger()->warn("invalid log level: {}", [log_level UTF8String]); 51 | } 52 | 53 | auto zeek_destination = [_defaults stringForKey:@"zeek.destination"]; 54 | if ( zeek_destination ) 55 | options.zeek_destinations = {[zeek_destination UTF8String]}; 56 | 57 | if ( auto rc = _configuration->setOptions(options); ! rc ) 58 | logger()->warn("error applying new options: {}", rc.error()); 59 | } 60 | 61 | - (const Options&)options { 62 | return _configuration->options(); 63 | } 64 | 65 | - (BOOL)listener:(NSXPCListener*)listener shouldAcceptNewConnection:(NSXPCConnection*)connection { 66 | connection.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(IPCProtocol)]; 67 | connection.exportedObject = self; 68 | [connection resume]; 69 | return YES; 70 | } 71 | 72 | - (void)getStatusWithReply:(void (^)(NSString*, NSString*, NSString*))reply { 73 | logger()->debug("[IPC] remote call: getStatus"); 74 | auto es = (platform::darwin::endpointSecurity()->isAvailable() ? "+ES" : "-ES"); 75 | auto ne = (platform::darwin::networkExtension()->isAvailable() ? "+NE" : "-NE"); 76 | 77 | auto version = [NSString stringWithUTF8String:Version]; 78 | auto capabilities = join(std::vector{es, ne}, " "); 79 | auto capabilities_ = [NSString stringWithUTF8String:capabilities.c_str()]; 80 | auto agent_id = [NSString stringWithUTF8String:[[IPC sharedObject] options].agent_id.c_str()]; 81 | reply(version, capabilities_, agent_id); 82 | } 83 | 84 | - (void)getOptionsWithReply:(void (^)(NSDictionary*))reply { 85 | logger()->debug("[IPC] remote call: getOptions"); 86 | 87 | auto options = [NSMutableDictionary dictionary]; 88 | 89 | auto log_level = [_defaults stringForKey:@"log.level"]; 90 | if ( log_level ) 91 | options[@"log.level"] = log_level; 92 | else 93 | options[@"log.level"] = @"default"; 94 | 95 | auto zeek_destination = [_defaults stringForKey:@"zeek.destination"]; 96 | if ( zeek_destination ) 97 | options[@"zeek.destination"] = zeek_destination; 98 | else 99 | options[@"zeek.destination"] = @""; 100 | 101 | reply(options); 102 | 103 | CFRelease(options); 104 | } 105 | 106 | - (void)setOptions:(NSDictionary*)options { 107 | logger()->debug("[IPC] remote call: setOptions"); 108 | 109 | for ( id key in options ) { 110 | auto value = [options objectForKey:key]; 111 | [_defaults setObject:value forKey:key]; 112 | } 113 | 114 | [self updateOptions]; 115 | } 116 | 117 | - (void)exit { 118 | logger()->debug("[IPC] remote call: exit"); 119 | exit(0); 120 | } 121 | @end 122 | -------------------------------------------------------------------------------- /src/platform/linux/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent PRIVATE platform.cc bpf.cc) 4 | set_property(SOURCE bpf.cc APPEND PROPERTY OBJECT_DEPENDS bpftool) 5 | target_link_libraries(zeek-agent PRIVATE bpf) 6 | -------------------------------------------------------------------------------- /src/platform/linux/bpf.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "util/pimpl.h" 4 | #include "util/result.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | struct ring_buffer; 15 | 16 | namespace zeek::agent::platform::linux { 17 | 18 | /** 19 | * Wrapper around Linux' BPF functionality. This centralizes BPF state across 20 | * all agent components using BPF. All public methods are thread-safe. 21 | */ 22 | class BPF { 23 | public: 24 | using EventCallback = int (*)(void* ctx, void* data, size_t data_sz); 25 | 26 | /** Captures a BPF program skeleton. */ 27 | struct Skeleton { 28 | std::string name; /**< Name of the BPF program. */ 29 | void* open = nullptr; /**< Function pointer to the BPF program's `open` function. */ 30 | void* load = nullptr; /**< Function pointer to the BPF program's `load` function. */ 31 | void* attach = nullptr; /**< Function pointer to the BPF program's `attach` function. */ 32 | void* detach = nullptr; /**< Function pointer to the BPF program's `detach` function. */ 33 | void* destroy = nullptr; /**< Function pointer to the BPF program's `destroy` function. */ 34 | 35 | EventCallback event_callback = nullptr; /**< Callback function to be invoked when an event is received. */ 36 | void* event_context = nullptr; /**< Context to be passed to the event callback function. */ 37 | 38 | void* _bpf = nullptr; /**< Pointer to the BPF program. */ 39 | }; 40 | 41 | ~BPF(); 42 | 43 | /** 44 | * Returns true if BPF has been initialized successfully and is available 45 | * for use. 46 | */ 47 | bool isAvailable() const; 48 | 49 | template 50 | Result load(Skeleton skel) { 51 | if ( auto rc = load(std::move(skel)) ) 52 | return reinterpret_cast(*rc); 53 | else 54 | return rc.error(); 55 | } 56 | 57 | Result init(const std::string& name, void* ring_buffer); 58 | Result attach(const std::string& name) const; 59 | Result detach(const std::string& name) const; 60 | Result destroy(const std::string& name); 61 | 62 | private: 63 | friend BPF* bpf(); 64 | 65 | BPF(); 66 | Result load(Skeleton skel); 67 | void poll(); 68 | 69 | std::atomic _stopping = false; 70 | std::unique_ptr _thread; 71 | mutable std::mutex _skeletons_mutex; 72 | std::map _skeletons; 73 | struct ::ring_buffer* _ring_buffers = nullptr; 74 | }; 75 | 76 | /** Returns the global `BPF` singleton. */ 77 | BPF* bpf(); 78 | 79 | } // namespace zeek::agent::platform::linux 80 | -------------------------------------------------------------------------------- /src/platform/linux/platform.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "platform/platform.h" 4 | 5 | #include "autogen/config.h" 6 | #include "util/fmt.h" 7 | #include "util/helpers.h" 8 | #include "util/testing.h" 9 | 10 | #include 11 | 12 | #include 13 | 14 | using namespace zeek::agent; 15 | 16 | std::string platform::name() { return "Linux"; } 17 | 18 | void platform::init(Configuration* cfg) {} 19 | 20 | void platform::done() {} 21 | 22 | bool platform::isTTY() { return ::isatty(1); } 23 | 24 | bool platform::runningAsAdmin() { return geteuid() == 0; } 25 | 26 | std::optional platform::getenv(const std::string& name) { 27 | if ( auto x = ::getenv(name.c_str()) ) 28 | return {x}; 29 | else 30 | return {}; 31 | } 32 | 33 | Result platform::setenv(const char* name, const char* value, int overwrite) { 34 | if ( ::setenv(name, value, overwrite) == 0 ) 35 | return Nothing(); 36 | else 37 | return result::Error(strerror(errno)); 38 | } 39 | 40 | std::optional platform::configurationFile() { 41 | // TODO: These paths aren't necessarily right yet. 42 | filesystem::path exec = PathFind::FindExecutable(); 43 | return filesystem::weakly_canonical(exec.parent_path() / "../etc" / "zeek-agent.conf"); 44 | } 45 | 46 | std::optional platform::dataDirectory() { 47 | // TODO: These paths aren't necessarily right yet. 48 | filesystem::path dir; 49 | 50 | if ( auto home = platform::getenv("HOME") ) 51 | dir = filesystem::path(*home) / ".cache" / "zeek-agent"; 52 | else 53 | dir = "/var/run/zeek-agent"; 54 | 55 | std::error_code ec; 56 | filesystem::create_directories(dir, ec); 57 | if ( ec ) 58 | throw FatalError(frmt("cannot create path '{}'", dir.native())); 59 | 60 | return dir; 61 | } 62 | 63 | void platform::initializeOptions(Options* options) { 64 | // Nothing to do. 65 | } 66 | 67 | unsigned int platform::linux::kernelVersion() { 68 | struct utsname uts; 69 | if ( uname(&uts) < 0 ) 70 | throw FatalError("cannot retrieve Linux kernel version"); 71 | 72 | char* p = uts.release; 73 | 74 | while ( *p && ! isdigit(*p) ) 75 | p++; 76 | 77 | auto major = strtol(p, &p, 10); 78 | 79 | while ( *p && ! isdigit(*p) ) 80 | p++; 81 | 82 | auto minor = strtol(p, &p, 10); 83 | 84 | if ( ! (major && minor) ) 85 | throw FatalError(frmt("cannot parse Linux kernel version: {}", uts.release)); 86 | 87 | return major * 100 + minor; 88 | } 89 | -------------------------------------------------------------------------------- /src/platform/linux/platform.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #include 8 | 9 | namespace zeek::agent::platform::linux { 10 | 11 | /** Returns the kernel version as "major * 100 + minor". */ 12 | extern unsigned int kernelVersion(); 13 | 14 | } // namespace zeek::agent::platform::linux 15 | -------------------------------------------------------------------------------- /src/platform/platform.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "core/configuration.h" 6 | #include "util/filesystem.h" 7 | 8 | #ifdef HAVE_DARWIN 9 | #include "darwin/platform.h" 10 | #endif 11 | 12 | #ifdef HAVE_LINUX 13 | #include "linux/platform.h" 14 | #endif 15 | 16 | #ifdef HAVE_WINDOWS 17 | #include "windows/platform.h" 18 | #endif 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | namespace zeek::agent::platform { 25 | 26 | /** Performs one-time initialization at startup. */ 27 | extern void init(Configuration* cfg); 28 | 29 | /** Performs one-time cleanup at shutdown. */ 30 | extern void done(); 31 | 32 | /** Returns a name for the current platform. */ 33 | extern std::string name(); 34 | 35 | /** Returns the path to the default configuration file. */ 36 | extern std::optional configurationFile(); 37 | 38 | /** Returns the directory path where to store dynamic, persistent state. */ 39 | extern std::optional dataDirectory(); 40 | 41 | /** Returns true if stdin is a terminal. */ 42 | extern bool isTTY(); 43 | 44 | /** 45 | * Platform specific-implementation of setenv(). Follows the same semantics as 46 | * POSIX's setenv() on all platforms. 47 | */ 48 | extern Result setenv(const char* name, const char* value, int overwrite); 49 | 50 | /** 51 | * Gets a variable from the environment, returning an unset optional if the 52 | * variable isn't set. 53 | */ 54 | extern std::optional getenv(const std::string& name); 55 | 56 | /** 57 | * Checks for whether the process is running with administrator rights. 58 | */ 59 | extern bool runningAsAdmin(); 60 | 61 | /** 62 | * Prepopulates an option object with defaults derived from platform-specific 63 | * mechanisms. 64 | */ 65 | extern void initializeOptions(Options* options); 66 | 67 | } // namespace zeek::agent::platform 68 | -------------------------------------------------------------------------------- /src/platform/testing.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "util/testing.h" 4 | 5 | #include "platform.h" 6 | 7 | using namespace zeek::agent; 8 | 9 | TEST_SUITE("Platform") { 10 | TEST_CASE("getenv") { 11 | CHECK_EQ(platform::getenv(""), std::nullopt); 12 | 13 | #ifndef HAVE_WINDOWS 14 | const auto home = platform::getenv("HOME"); 15 | #else 16 | const auto home = platform::getenv("HOMEPATH"); 17 | #endif 18 | REQUIRE(home); 19 | CHECK_FALSE(home->empty()); 20 | 21 | CHECK_EQ(platform::getenv("TEST_ENV_DOES_NOT_EXIST"), std::nullopt); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/platform/windows/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent PRIVATE platform.cc) 4 | -------------------------------------------------------------------------------- /src/tables/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | if ( HAVE_LINUX ) 4 | include(BPF) 5 | endif () 6 | 7 | add_subdirectory(files) 8 | add_subdirectory(processes) 9 | add_subdirectory(sockets) 10 | add_subdirectory(system_logs) 11 | add_subdirectory(users) 12 | add_subdirectory(zeek_agent) 13 | -------------------------------------------------------------------------------- /src/tables/files/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent PRIVATE files.cc) 4 | 5 | if ( HAVE_POSIX ) 6 | target_sources(zeek-agent PRIVATE files.posix.cc) 7 | endif () 8 | 9 | if ( HAVE_WINDOWS ) 10 | target_sources(zeek-agent PRIVATE files.windows.cc) 11 | endif () 12 | -------------------------------------------------------------------------------- /src/tables/processes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent PRIVATE processes.test.cc) 4 | 5 | if ( HAVE_DARWIN ) 6 | target_sources(zeek-agent PRIVATE processes.darwin.cc) 7 | endif () 8 | 9 | if ( HAVE_LINUX ) 10 | generate_bpf_code(zeek-agent processes processes.linux.bpf.c) 11 | target_sources(zeek-agent PRIVATE processes.linux.cc) 12 | target_link_libraries(zeek-agent PRIVATE pfs) 13 | endif () 14 | 15 | if ( HAVE_WINDOWS ) 16 | target_sources(zeek-agent PRIVATE processes.windows.cc) 17 | endif () 18 | -------------------------------------------------------------------------------- /src/tables/processes/processes.darwin.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "processes.h" 4 | 5 | #include "autogen/config.h" 6 | #include "core/configuration.h" 7 | #include "core/database.h" 8 | #include "core/logger.h" 9 | #include "core/table.h" 10 | #include "platform/darwin/endpoint-security.h" 11 | #include "platform/darwin/platform.h" 12 | #include "util/fmt.h" 13 | 14 | #include 15 | 16 | #include 17 | 18 | namespace zeek::agent::table { 19 | 20 | class ProcessesDarwin : public ProcessesCommon { 21 | public: 22 | std::vector> snapshot(const std::vector& args) override; 23 | void addProcess(std::vector>* rows, const struct proc_bsdinfo* p, 24 | const struct proc_taskinfo* ti); 25 | 26 | Init init() override; 27 | }; 28 | 29 | namespace { 30 | database::RegisterTable _1; 31 | } 32 | 33 | Table::Init ProcessesDarwin::init() { return Init::Available; } 34 | 35 | std::vector> ProcessesDarwin::snapshot(const std::vector& args) { 36 | auto pids = platform::darwin::getProcesses(); 37 | if ( ! pids ) { 38 | logger()->debug("could not get process list: {}", pids.error()); 39 | return {}; 40 | } 41 | 42 | std::vector> rows; 43 | 44 | for ( auto pid : *pids ) { 45 | if ( pid <= 0 ) 46 | continue; 47 | 48 | if ( auto p = platform::darwin::getProcessInfo(pid) ) 49 | rows.push_back({p->name, p->pid, p->ppid, p->uid, p->gid, p->ruid, p->rgid, p->priority, p->startup, 50 | p->vsize, p->rsize, p->utime, p->stime}); 51 | else 52 | logger()->debug("could not get process info for PID {}: {}", pid, p.error()); 53 | } 54 | 55 | return rows; 56 | } 57 | 58 | void ProcessesDarwin::addProcess(std::vector>* rows, const struct proc_bsdinfo* pi, 59 | const struct proc_taskinfo* ti) {} 60 | 61 | class ProcessesEventsDarwin : public ProcessesEventsCommon { 62 | public: 63 | Init init() override; 64 | void activate() override; 65 | void deactivate() override; 66 | 67 | private: 68 | std::unique_ptr _subscriber; 69 | }; 70 | 71 | namespace { 72 | database::RegisterTable _2; 73 | } 74 | 75 | static void handle_event(ProcessesEventsDarwin* table, const es_message_t* msg) { 76 | es_process_t* process = nullptr; 77 | 78 | Value state; 79 | switch ( msg->event_type ) { 80 | case ES_EVENT_TYPE_NOTIFY_EXEC: 81 | process = msg->event.exec.target; 82 | state = "started"; 83 | break; 84 | 85 | case ES_EVENT_TYPE_NOTIFY_EXIT: 86 | process = msg->process; 87 | state = "stopped"; 88 | break; 89 | 90 | default: break; // shouldn't happen, but just ignore 91 | }; 92 | 93 | if ( ! process ) 94 | return; 95 | 96 | auto pid_ = audit_token_to_pid(process->audit_token); 97 | 98 | const Value t = to_time(msg->time); 99 | const Value name = process->executable->path.data; 100 | const Value pid = static_cast(pid_); 101 | const Value ppid = static_cast(process->ppid); 102 | const Value uid = static_cast(audit_token_to_euid(process->audit_token)); 103 | const Value gid = static_cast(audit_token_to_egid(process->audit_token)); 104 | const Value ruid = static_cast(audit_token_to_ruid(process->audit_token)); 105 | const Value rgid = static_cast(audit_token_to_rgid(process->audit_token)); 106 | const Value startup = std::chrono::system_clock::now() - to_time(process->start_time); 107 | 108 | Value priority; 109 | Value vsize; 110 | Value rsize; 111 | Value utime; 112 | Value stime; 113 | 114 | if ( auto p = platform::darwin::getProcessInfo(pid_) ) { 115 | // Looks like we can't get this anymore at EXIT events, so just fill in 116 | // what we can. 117 | priority = p->priority; 118 | rsize = p->rsize; 119 | vsize = p->vsize; 120 | utime = p->utime; 121 | stime = p->stime; 122 | } 123 | 124 | table->newEvent({t, name, pid, ppid, uid, gid, ruid, rgid, priority, startup, vsize, rsize, utime, stime, state}); 125 | } 126 | 127 | Table::Init ProcessesEventsDarwin::init() { 128 | auto es = platform::darwin::endpointSecurity(); 129 | return es->isAvailable() ? Init::Available : Init::PermanentlyUnavailable; 130 | } 131 | 132 | void ProcessesEventsDarwin::activate() { 133 | auto es = platform::darwin::endpointSecurity(); 134 | 135 | if ( auto subscriber = es->subscribe("processes-events", {ES_EVENT_TYPE_NOTIFY_EXEC, ES_EVENT_TYPE_NOTIFY_EXIT}, 136 | [this](const auto& event) { handle_event(this, event); }) ) 137 | _subscriber = std::move(*subscriber); 138 | else 139 | logger()->warn(frmt("could not initialize EndpointSecurity subscriber: {}", subscriber.error())); 140 | } 141 | 142 | void ProcessesEventsDarwin::deactivate() { _subscriber.reset(); } 143 | 144 | } // namespace zeek::agent::table 145 | -------------------------------------------------------------------------------- /src/tables/processes/processes.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "core/table.h" 6 | 7 | namespace zeek::agent::table { 8 | 9 | class ProcessesCommon : public SnapshotTable { 10 | public: 11 | Schema schema() const override { 12 | return { 13 | // clang-format off 14 | .name = "processes", 15 | .summary = "current processes", 16 | .description = R"( 17 | The table provides a list of all processes that are running on 18 | the endpoint at the time of the query. 19 | )", 20 | .platforms = { Platform::Darwin, Platform::Linux, Platform::Windows }, 21 | .columns = { 22 | {.name = "name", .type = value::Type::Text, .summary = "name of process"}, 23 | {.name = "pid", .type = value::Type::Count, .summary = "process ID"}, 24 | {.name = "ppid", .type = value::Type::Count, .summary = "parent's process ID"}, 25 | {.name = "uid", .type = value::Type::Count, .summary = "effective user ID"}, 26 | {.name = "gid", .type = value::Type::Count, .summary = "effective group ID"}, 27 | {.name = "ruid", .type = value::Type::Count, .summary = "real user ID"}, 28 | {.name = "rgid", .type = value::Type::Count, .summary = "real group ID"}, 29 | {.name = "priority", .type = value::Type::Text, .summary = "process priority (representation is platform-specific)"}, 30 | {.name = "startup", .type = value::Type::Interval, .summary = "time process started"}, 31 | {.name = "vsize", .type = value::Type::Count, .summary = "virtual memory size"}, 32 | {.name = "rsize", .type = value::Type::Count, .summary = "resident memory size"}, 33 | {.name = "utime", .type = value::Type::Interval, .summary = "user CPU time"}, 34 | {.name = "stime", .type = value::Type::Interval, .summary = "system CPU time"}, 35 | } 36 | // clang-format on 37 | }; 38 | } 39 | }; 40 | 41 | class ProcessesEventsCommon : public EventTable { 42 | public: 43 | Schema schema() const override { 44 | return { 45 | // clang-format off 46 | .name = "processes_events", 47 | .summary = "process activity", 48 | .description = R"( 49 | The table reports processes starting and stopping on the endpoint. 50 | )", 51 | .platforms = { Platform::Darwin, Platform::Linux }, 52 | .columns = { 53 | {.name = "time", .type = value::Type::Time, .summary = "timestamp"}, 54 | {.name = "name", .type = value::Type::Text, .summary = "name of process"}, 55 | {.name = "pid", .type = value::Type::Count, .summary = "process ID"}, 56 | {.name = "ppid", .type = value::Type::Count, .summary = "parent's process ID"}, 57 | {.name = "uid", .type = value::Type::Count, .summary = "effective user ID"}, 58 | {.name = "gid", .type = value::Type::Count, .summary = "effective group ID"}, 59 | {.name = "ruid", .type = value::Type::Count, .summary = "real user ID"}, 60 | {.name = "rgid", .type = value::Type::Count, .summary = "real group ID"}, 61 | {.name = "priority", .type = value::Type::Text, .summary = "process priority (representation is platform-specific)"}, 62 | {.name = "duration", .type = value::Type::Interval, .summary = "interval since started"}, 63 | {.name = "vsize", .type = value::Type::Count, .summary = "virtual memory size"}, 64 | {.name = "rsize", .type = value::Type::Count, .summary = "resident memory size"}, 65 | {.name = "utime", .type = value::Type::Interval, .summary = "user CPU time"}, 66 | {.name = "stime", .type = value::Type::Interval, .summary = "system CPU time"}, 67 | {.name = "state", .type = value::Type::Text, .summary = "state of process"}, 68 | } 69 | // clang-format on 70 | }; 71 | } 72 | }; 73 | 74 | } // namespace zeek::agent::table 75 | -------------------------------------------------------------------------------- /src/tables/processes/processes.linux.event.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #define BPF_PROCESS_NAME_MAX 128 8 | #define BPF_PROCESS_PRIORITY_MAX 16 9 | 10 | enum bpfProcessState { BPF_PROCESS_STATE_UNKNOWN = 0, BPF_PROCESS_STATE_STARTED, BPF_PROCESS_STATE_STOPPED }; 11 | 12 | struct bpfProcessEvent { 13 | char name[BPF_PROCESS_NAME_MAX]; 14 | __u64 pid; 15 | __u64 ppid; 16 | __u64 uid; 17 | __u64 gid; 18 | __u64 ruid; 19 | __u64 rgid; 20 | __s64 life_time; // -1 for unknown 21 | int priority; // + MAX_RT_PRIO 22 | __u64 vsize; // bytes 23 | __u64 rsize; // pages 24 | __u64 utime; // nsecs 25 | __u64 stime; // nsecs 26 | enum bpfProcessState state; 27 | }; 28 | -------------------------------------------------------------------------------- /src/tables/processes/processes.test.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "processes.h" 4 | 5 | #include "autogen/config.h" 6 | #include "util/testing.h" 7 | 8 | using namespace zeek::agent; 9 | 10 | TEST_CASE_FIXTURE(test::TableFixture, "processes" * doctest::test_suite("Tables")) { 11 | useTable("processes"); 12 | 13 | std::string name = "zeek-agent"; 14 | 15 | // We should be able to see ourselves. 16 | #ifdef HAVE_WINDOWS 17 | int ret = 0; 18 | TCHAR filename[MAX_PATH]; 19 | 20 | if ( GetModuleFileNameA(NULL, filename, MAX_PATH) == 0 ) { 21 | std::error_condition cond = std::system_category().default_error_condition(static_cast(GetLastError())); 22 | FAIL("Failed to get path to executable: ", cond.message()); 23 | } 24 | 25 | name = filename; 26 | #endif 27 | 28 | auto result = query(frmt("SELECT pid from processes WHERE name = \"{}\" AND pid = {}", name, getpid())); 29 | REQUIRE_EQ(result.rows.size(), 1); 30 | } 31 | -------------------------------------------------------------------------------- /src/tables/processes/processes.windows.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "processes.h" 4 | 5 | #include "core/configuration.h" 6 | #include "core/database.h" 7 | #include "core/table.h" 8 | #include "platform/platform.h" 9 | #include "util/fmt.h" 10 | 11 | #include 12 | #include 13 | 14 | using namespace zeek::agent::platform::windows; 15 | 16 | namespace zeek::agent::table { 17 | 18 | class ProcessesWindows : public ProcessesCommon { 19 | public: 20 | std::vector> snapshot(const std::vector& args) override; 21 | }; 22 | 23 | namespace { 24 | database::RegisterTable _; 25 | } 26 | 27 | static Interval filetime_to_interval(FILETIME t) { 28 | LARGE_INTEGER lt{.LowPart = t.dwLowDateTime, .HighPart = static_cast(t.dwHighDateTime)}; 29 | 30 | // Times like these are in 100ns intervals. Multiply by 100 to get nanosecond intervals, which we can 31 | // then convert to an interval object. 32 | auto tmp = lt.QuadPart * 100; 33 | 34 | return to_interval_from_ns(static_cast(tmp)); 35 | } 36 | 37 | static std::string priority_string(DWORD value) { 38 | switch ( value ) { 39 | case ABOVE_NORMAL_PRIORITY_CLASS: return "above_normal"; 40 | case BELOW_NORMAL_PRIORITY_CLASS: return "below_normal"; 41 | case HIGH_PRIORITY_CLASS: return "high"; 42 | case IDLE_PRIORITY_CLASS: return "idle"; 43 | case NORMAL_PRIORITY_CLASS: return "normal"; 44 | case REALTIME_PRIORITY_CLASS: return "realtime"; 45 | default: return "unknown"; 46 | } 47 | } 48 | 49 | std::vector> ProcessesWindows::snapshot(const std::vector& args) { 50 | HandlePtr snapshot(CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0)); 51 | if ( snapshot.get() == INVALID_HANDLE_VALUE ) 52 | return {}; 53 | 54 | PROCESSENTRY32 entry{}; 55 | entry.dwSize = sizeof(PROCESSENTRY32); 56 | 57 | if ( ! Process32First(snapshot.get(), &entry) ) 58 | return {}; 59 | 60 | std::vector> rows; 61 | do { 62 | HandlePtr proc(OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | PROCESS_VM_READ, FALSE, entry.th32ProcessID)); 63 | 64 | CHAR proc_name[MAX_PATH]; 65 | HRESULT res = GetModuleFileNameExA(proc.get(), NULL, proc_name, MAX_PATH); 66 | if ( FAILED(res) ) 67 | continue; 68 | 69 | PROCESS_MEMORY_COUNTERS_EX memory{}; 70 | if ( ! GetProcessMemoryInfo(proc.get(), reinterpret_cast(&memory), sizeof(memory)) ) 71 | continue; 72 | 73 | DWORD prio = GetPriorityClass(proc.get()); 74 | if ( prio == 0 ) 75 | continue; 76 | 77 | FILETIME creation_time, exit_time, kernel_time, user_time; 78 | if ( ! GetProcessTimes(proc.get(), &creation_time, &exit_time, &kernel_time, &user_time) ) 79 | continue; 80 | 81 | Value name = proc_name; 82 | Value pid = static_cast(entry.th32ProcessID); 83 | Value priority = priority_string(prio); 84 | Value utime = filetime_to_interval(user_time); 85 | Value stime = filetime_to_interval(kernel_time); 86 | Value vsize = static_cast(memory.PrivateUsage); // Virtual size 87 | Value rsize = static_cast(memory.WorkingSetSize); 88 | 89 | // On Windows, it's possible for a parent process to spawn some children and then exit while 90 | // leaving the children running. In these cases, th32ParentProcessID will point at a PID that 91 | // that doesn't actually have a process running for it. Since that's possible, we just leave 92 | // that field empty. 93 | Value ppid{}; 94 | 95 | // These fields are unknown for Windows. It's possible to get an SID matching a user 96 | // for a process ID but it's a bit complicated and might not be extremely useful here. 97 | Value uid{}; 98 | Value gid{}; 99 | Value ruid{}; 100 | Value rgid{}; 101 | Value startup{}; 102 | 103 | rows.push_back({name, pid, ppid, uid, gid, ruid, rgid, priority, startup, vsize, rsize, utime, stime}); 104 | 105 | } while ( Process32Next(snapshot.get(), &entry) ); 106 | 107 | return rows; 108 | } 109 | 110 | } // namespace zeek::agent::table 111 | -------------------------------------------------------------------------------- /src/tables/sockets/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent PRIVATE sockets.test.cc) 4 | 5 | if ( HAVE_DARWIN ) 6 | target_sources(zeek-agent PRIVATE sockets.darwin.cc) 7 | endif () 8 | 9 | if ( HAVE_LINUX ) 10 | generate_bpf_code(zeek-agent sockets sockets.linux.bpf.c) 11 | target_sources(zeek-agent PRIVATE sockets.linux.cc) 12 | endif () 13 | 14 | if ( HAVE_WINDOWS ) 15 | target_sources(zeek-agent PRIVATE sockets.windows.cc) 16 | endif () 17 | -------------------------------------------------------------------------------- /src/tables/sockets/sockets.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "core/table.h" 6 | 7 | namespace zeek::agent::table { 8 | 9 | class SocketsCommon : public SnapshotTable { 10 | public: 11 | Schema schema() const override { 12 | return { 13 | // clang-format off 14 | .name = "sockets", 15 | .summary = "open network sockets", 16 | .description = R"( 17 | The table provides a list of all IP sockets that are open on 18 | the endpoint at the time of the query. 19 | )", 20 | .platforms = { Platform::Darwin, Platform::Linux, Platform::Windows }, 21 | .columns = { 22 | {.name = "pid", .type = value::Type::Count, .summary = "ID of process holding socket"}, 23 | {.name = "process", .type = value::Type::Text, .summary = "name of process holding socket"}, 24 | {.name = "family", .type = value::Type::Text, .summary = "`IPv4` or `IPv6`"}, 25 | {.name = "protocol", .type = value::Type::Count, .summary = "transport protocol"}, 26 | {.name = "local_addr", .type = value::Type::Address, .summary = "local IP address"}, 27 | {.name = "local_port", .type = value::Type::Count, .summary = "local port number"}, 28 | {.name = "remote_addr", .type = value::Type::Address, .summary = "remote IP address"}, 29 | {.name = "remote_port", .type = value::Type::Count, .summary = "remote port number"}, 30 | {.name = "state", .type = value::Type::Text, .summary = "state of socket"}, 31 | } 32 | // clang-format on 33 | }; 34 | } 35 | }; 36 | 37 | class SocketsEventsCommon : public EventTable { 38 | public: 39 | Schema schema() const override { 40 | return { 41 | // clang-format off 42 | .name = "sockets_events", 43 | .summary = "open network sockets", 44 | .description = R"( 45 | The table reports IP sockets opening and closing on the endpoint. 46 | )", 47 | .platforms = { Platform::Darwin, Platform::Linux }, 48 | .columns = { 49 | {.name = "time", .type = value::Type::Time, .summary = "timestamp"}, 50 | {.name = "pid", .type = value::Type::Count, .summary = "ID of process holding socket"}, 51 | {.name = "process", .type = value::Type::Text, .summary = "name of process holding socket"}, 52 | {.name = "uid", .type = value::Type::Count, .summary = "user ID of process"}, 53 | {.name = "gid", .type = value::Type::Count, .summary = "group ID of process"}, 54 | {.name = "family", .type = value::Type::Text, .summary = "`IPv4` or `IPv6`"}, 55 | {.name = "protocol", .type = value::Type::Count, .summary = "transport protocol"}, 56 | {.name = "local_addr", .type = value::Type::Address, .summary = "local IP address"}, 57 | {.name = "local_port", .type = value::Type::Count, .summary = "local port number"}, 58 | {.name = "remote_addr", .type = value::Type::Address, .summary = "remote IP address"}, 59 | {.name = "remote_port", .type = value::Type::Count, .summary = "remote port number"}, 60 | {.name = "state", .type = value::Type::Text, .summary = "state of socket"}, 61 | } 62 | // clang-format on 63 | }; 64 | } 65 | }; 66 | 67 | } // namespace zeek::agent::table 68 | -------------------------------------------------------------------------------- /src/tables/sockets/sockets.linux.event.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | #define BPF_PROCESS_NAME_MAX 128 8 | 9 | struct bpfProcess { 10 | __u64 pid; 11 | char name[BPF_PROCESS_NAME_MAX]; 12 | __u64 uid; 13 | __u64 gid; 14 | }; 15 | 16 | enum bpfSocketState { 17 | BPF_SOCKET_STATE_UNKNOWN = 0, 18 | BPF_SOCKET_STATE_CLOSED, 19 | BPF_SOCKET_STATE_ESTABLISHED, 20 | BPF_SOCKET_STATE_EXPIRED, 21 | BPF_SOCKET_STATE_FAILED, 22 | BPF_SOCKET_STATE_LISTEN, 23 | }; 24 | 25 | struct bpfSocketEvent { 26 | struct bpfProcess process; // valid only of process.pid > 0 27 | __u64 family; 28 | __u64 protocol; 29 | __u8 local_addr[16]; 30 | __u64 local_port; 31 | __u8 remote_addr[16]; 32 | __u64 remote_port; 33 | enum bpfSocketState state; 34 | }; 35 | -------------------------------------------------------------------------------- /src/tables/sockets/sockets.test.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "sockets.h" 4 | 5 | #include "autogen/config.h" 6 | #include "platform/platform.h" 7 | #include "util/fmt.h" 8 | #include "util/testing.h" 9 | 10 | #include 11 | 12 | #ifndef HAVE_WINDOWS 13 | #include 14 | #endif 15 | 16 | using namespace zeek::agent; 17 | 18 | TEST_CASE_FIXTURE(test::TableFixture, "sockets" * doctest::test_suite("Tables")) { 19 | useTable("sockets"); 20 | 21 | int port = -1; 22 | int fd = -1; 23 | std::random_device rd; 24 | std::uniform_int_distribution dist(1024, 65536); 25 | 26 | #ifdef HAVE_WINDOWS 27 | WSADATA wsa{}; 28 | int res = WSAStartup(MAKEWORD(2, 2), &wsa); 29 | if ( res ) { 30 | std::error_condition cond = std::system_category().default_error_condition(static_cast(GetLastError())); 31 | printf("%s", frmt("Failed to initialize WSA: {}", cond.message()).c_str()); 32 | REQUIRE(res == 0); 33 | } 34 | #endif 35 | 36 | while ( true ) { 37 | // Listen on a random port, then check if we can see it. 38 | fd = socket(AF_INET, SOCK_STREAM, 0); 39 | REQUIRE(fd >= 0); 40 | 41 | #ifndef HAVE_WINDOWS 42 | fchmod(fd, 0777); 43 | #endif 44 | 45 | struct sockaddr_in addr; 46 | memset(&addr, 0, sizeof(addr)); 47 | 48 | port = dist(rd); 49 | 50 | addr.sin_family = AF_INET; 51 | addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); 52 | addr.sin_port = htons(port); 53 | if ( bind(fd, (const struct sockaddr*)&addr, sizeof(addr)) != 0 ) 54 | // port presumably already in use, try another one 55 | continue; 56 | 57 | #ifndef HAVE_WINDOWS 58 | fchmod(fd, 0777); 59 | #endif 60 | REQUIRE(listen(fd, SOMAXCONN) >= 0); 61 | break; 62 | } 63 | 64 | // We should be able to see our port. 65 | auto result = query(frmt("SELECT pid, state from sockets WHERE local_port = {}", port)); 66 | REQUIRE_EQ(result.rows.size(), 1); 67 | CHECK_EQ(result.get(0, "pid"), getpid()); 68 | CHECK_EQ(result.get(0, "state"), std::string("LISTEN")); 69 | 70 | // Clean up 71 | #ifdef HAVE_WINDOWS 72 | closesocket(fd); 73 | WSACleanup(); 74 | #else 75 | close(fd); 76 | #endif 77 | } 78 | -------------------------------------------------------------------------------- /src/tables/system_logs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent PRIVATE system_logs.test.cc) 4 | 5 | if ( HAVE_DARWIN ) 6 | target_sources(zeek-agent PRIVATE system_logs.darwin.cc) 7 | target_link_libraries(zeek-agent PRIVATE "-framework LoggingSupport") 8 | set_target_properties(zeek-agent PROPERTIES LINK_FLAGS "-Wl,-F/System/Library/PrivateFrameworks/") 9 | endif () 10 | 11 | if ( HAVE_LINUX ) 12 | target_sources(zeek-agent PRIVATE system_logs.linux.cc) 13 | endif () 14 | 15 | if ( HAVE_WINDOWS ) 16 | target_sources(zeek-agent PRIVATE system_logs.windows.cc) 17 | endif () 18 | -------------------------------------------------------------------------------- /src/tables/system_logs/system_logs.darwin.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | // 3 | // Inspired by https://github.com/steipete/OSLogTest/blob/master/LoggingTest/OSLogStream.swift 4 | // and https://github.com/llvm-mirror/lldb/blob/master/tools/debugserver/source/MacOSX/DarwinLog/DarwinLogCollector.cpp 5 | 6 | #include "system_logs.h" 7 | 8 | #include "ActivityStreamSPI.h" 9 | #include "core/database.h" 10 | #include "core/logger.h" 11 | #include "util/fmt.h" 12 | #include "util/helpers.h" 13 | 14 | #include 15 | 16 | #include 17 | 18 | using namespace zeek::agent; 19 | using namespace zeek::agent::table; 20 | 21 | extern "C" os_activity_stream_t os_activity_stream_for_pid(pid_t pid, os_activity_stream_flag_t flags, 22 | os_activity_stream_block_t stream_block); 23 | extern "C" void os_activity_stream_resume(os_activity_stream_t stream); 24 | extern "C" void os_activity_stream_cancel(os_activity_stream_t stream); 25 | extern "C" char* os_log_copy_formatted_message(os_log_message_t log_message); 26 | // extern "C" void os_activity_stream_set_event_handler(os_activity_stream_t stream, 27 | // os_activity_stream_event_block_t block); 28 | 29 | namespace { 30 | 31 | class SystemLogsDarwin : public SystemLogs { 32 | public: 33 | Init init() override; 34 | void activate() override; 35 | void deactivate() override; 36 | 37 | void recordEntry(os_activity_stream_entry_t entry, int error); 38 | 39 | os_activity_stream_t _activity_stream = nullptr; 40 | }; 41 | 42 | database::RegisterTable _; 43 | 44 | void SystemLogsDarwin::recordEntry(os_activity_stream_entry_t entry, int error) { 45 | if ( entry->type != OS_ACTIVITY_STREAM_TYPE_LOG_MESSAGE && 46 | entry->type != OS_ACTIVITY_STREAM_TYPE_LEGACY_LOG_MESSAGE ) 47 | return; 48 | 49 | // Notes: 50 | // - Can't reliably read 'category', accesses cause crashes 51 | // - Format seems to be always empty 52 | // - Subsystem seems to be always empty 53 | 54 | Value process; 55 | if ( entry->proc_imagepath ) { 56 | if ( auto x = split(entry->proc_imagepath, "/"); x.size() ) 57 | process = x.back(); 58 | } 59 | 60 | auto t = to_time(entry->log_message.tv_gmt.tv_sec); 61 | auto msg = os_log_copy_formatted_message(&entry->log_message); 62 | newEvent({t, process, "default", msg, {}}); 63 | free(msg); 64 | } 65 | 66 | Table::Init SystemLogsDarwin::init() { return Init::Available; } 67 | 68 | void SystemLogsDarwin::activate() { 69 | // Notes: 70 | // 71 | // OS_ACTIVITY_STREAM_BUFFERED - not sure what it does exactly, but reduces CPU load 72 | // OS_ACTIVITY_STREAM_INFO - include INFO level 73 | // OS_ACTIVITY_STREAM_DEBUG - include DEBUG level 74 | // 75 | // - We don't set an event handler, doesn't seem provide anything we need. 76 | os_activity_stream_block_t callback = ^bool(os_activity_stream_entry_t entry, int error) { // NOLINT 77 | if ( ! entry ) 78 | return true; 79 | 80 | recordEntry(entry, error); 81 | return true; 82 | }; 83 | 84 | _activity_stream = os_activity_stream_for_pid(-1, OS_ACTIVITY_STREAM_BUFFERED, callback); 85 | if ( ! _activity_stream ) { 86 | ZEEK_AGENT_DEBUG("system_logs", "could not get OS log handle"); 87 | return; 88 | } 89 | 90 | os_activity_stream_resume(_activity_stream); 91 | } 92 | 93 | void SystemLogsDarwin::deactivate() { 94 | if ( ! _activity_stream ) 95 | return; 96 | 97 | os_activity_stream_cancel(_activity_stream); 98 | _activity_stream = nullptr; 99 | } 100 | 101 | } // namespace 102 | -------------------------------------------------------------------------------- /src/tables/system_logs/system_logs.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "core/table.h" 6 | 7 | namespace zeek::agent::table { 8 | 9 | class SystemLogs : public EventTable { 10 | public: 11 | Schema schema() const override { 12 | return { 13 | // clang-format off 14 | .name = "system_logs_events", 15 | .summary = "log messages recorded by the operating systems", 16 | .description = R"( 17 | The table provides access to log messages recorded by the 18 | operating system. 19 | 20 | On Linux, the table requires `systemd` and hooks into its journal. 21 | 22 | On macOS, the tables hooks into the unified logging system 23 | (`OSLog`). 24 | 25 | On Windows, the tables hook into the event logging system. 26 | 27 | This is an evented table that captures log messages as they 28 | appear. New messages will be returned with the next query. 29 | )", 30 | .platforms = { Platform::Darwin, Platform::Linux, Platform::Windows }, 31 | .columns = { 32 | {.name = "time", .type = value::Type::Time, .summary = "timestamp"}, 33 | {.name = "process", .type = value::Type::Text, .summary = "process name"}, 34 | {.name = "level", .type = value::Type::Text, .summary = "severity level"}, 35 | {.name = "message", .type = value::Type::Text, .summary = "log message"}, 36 | {.name = "eventid", .type = value::Type::Text, .summary = "platform-specific identifier for the log event"} 37 | } 38 | // clang-format on 39 | }; 40 | } 41 | }; 42 | 43 | } // namespace zeek::agent::table 44 | -------------------------------------------------------------------------------- /src/tables/system_logs/system_logs.test.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "system_logs.h" 4 | 5 | #include "autogen/config.h" 6 | #include "util/testing.h" 7 | 8 | using namespace zeek::agent; 9 | 10 | TEST_CASE_FIXTURE(test::TableFixture, "system_logs_events" * doctest::test_suite("Tables")) { 11 | enableMockDataForTable("system_logs_events"); 12 | useTable("system_logs_events"); 13 | 14 | // Can use mock data only here. 15 | auto result = query("SELECT * from system_logs_events"); 16 | REQUIRE_EQ(result.rows.size(), 3); 17 | CHECK_EQ(*result.get(0, "message"), "text_a_d"); 18 | } 19 | -------------------------------------------------------------------------------- /src/tables/users/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent PRIVATE users.test.cc) 4 | 5 | if ( HAVE_DARWIN ) 6 | target_sources(zeek-agent PRIVATE users.darwin.mm) 7 | target_link_libraries(zeek-agent PRIVATE "-framework Collaboration") 8 | endif () 9 | 10 | if ( HAVE_LINUX ) 11 | target_sources(zeek-agent PRIVATE users.linux.cc) 12 | target_link_libraries(zeek-agent PRIVATE pfs) 13 | endif () 14 | 15 | if ( HAVE_WINDOWS ) 16 | target_sources(zeek-agent PRIVATE users.windows.cc) 17 | endif () 18 | -------------------------------------------------------------------------------- /src/tables/users/users.darwin.mm: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | // 3 | // This takes inspiration from https://stackoverflow.com/questions/3681895/get-all-users-on-os-x. 4 | 5 | #include "users.h" 6 | 7 | #include "autogen/config.h" 8 | #include "core/configuration.h" 9 | #include "core/database.h" 10 | #include "core/logger.h" 11 | #include "core/table.h" 12 | #include "util/fmt.h" 13 | 14 | #include 15 | #include 16 | 17 | #import 18 | #import 19 | 20 | namespace zeek::agent::table { 21 | 22 | class UsersDarwin : public UsersCommon { 23 | public: 24 | UsersDarwin(); 25 | std::vector> snapshot(const std::vector& args) override; 26 | void addUser(std::vector>* rows, const CBIdentity* identity); 27 | 28 | std::vector _buffer; 29 | }; 30 | 31 | namespace { 32 | database::RegisterTable _; 33 | } 34 | 35 | UsersDarwin::UsersDarwin() { 36 | auto bufsize = sysconf(_SC_GETPW_R_SIZE_MAX); 37 | 38 | if ( bufsize < 0 ) 39 | bufsize = 32768; // giant 40 | 41 | _buffer.resize(bufsize); 42 | } 43 | 44 | std::vector> UsersDarwin::snapshot(const std::vector& args) { 45 | std::vector> rows; 46 | 47 | auto defaultAuthority = CSGetLocalIdentityAuthority(); 48 | auto identityClass = kCSIdentityClassUser; // kCSIdentityClassGroup would be for groups 49 | auto query = CSIdentityQueryCreate(nullptr, identityClass, defaultAuthority); 50 | 51 | CFErrorRef error = nullptr; 52 | CSIdentityQueryExecute(query, kCSIdentityQueryIncludeHiddenIdentities, &error); 53 | 54 | auto results = CSIdentityQueryCopyResults(query); 55 | auto num_results = CFArrayGetCount(results); 56 | 57 | for ( int i = 0; i < num_results; ++i ) { 58 | auto cs_identity = (CSIdentityRef)CFArrayGetValueAtIndex(results, i); 59 | auto cb_identity = [CBIdentity identityWithCSIdentity:cs_identity]; 60 | addUser(&rows, cb_identity); 61 | } 62 | 63 | CFRelease(results); 64 | CFRelease(query); 65 | return rows; 66 | } 67 | 68 | void UsersDarwin::addUser(std::vector>* rows, const CBIdentity* identity) { 69 | Value short_name, full_name, is_admin, is_system, uid, gid, home, shell, email; 70 | 71 | if ( auto posix_name = [[identity posixName] UTF8String] ) { 72 | // 80 is the "admin" group. 73 | // TODO: Is it ok to assume that's a static value that's the same everywhere? 74 | CBGroupIdentity* admin = 75 | [CBGroupIdentity groupIdentityWithPosixGID:80 authority:[CBIdentityAuthority defaultIdentityAuthority]]; 76 | 77 | short_name = posix_name; 78 | full_name = value::fromOptionalString([[identity fullName] UTF8String]); 79 | email = value::fromOptionalString([[identity emailAddress] UTF8String]); 80 | is_admin = 81 | static_cast([identity isMemberOfGroup:admin]); // Looks like this returns int, not bool, on macOS 11. 82 | is_system = static_cast([identity isHidden]); 83 | 84 | struct passwd pwd; 85 | struct passwd* result = nullptr; 86 | if ( getpwnam_r(posix_name, &pwd, _buffer.data(), _buffer.size(), &result) == 0 && result ) { 87 | uid = std::to_string(pwd.pw_uid); 88 | gid = pwd.pw_gid; 89 | home = value::fromOptionalString(pwd.pw_dir); 90 | shell = value::fromOptionalString(pwd.pw_shell); 91 | } 92 | else 93 | logger()->warn("users: getpwname_r() failed for user {}", posix_name); 94 | } 95 | else 96 | logger()->warn("users: user without posix name"); 97 | 98 | rows->push_back({short_name, full_name, is_admin, is_system, uid, gid, home, shell, email}); 99 | } 100 | 101 | } // namespace zeek::agent::table 102 | -------------------------------------------------------------------------------- /src/tables/users/users.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "core/table.h" 6 | 7 | namespace zeek::agent::table { 8 | 9 | class UsersCommon : public SnapshotTable { 10 | public: 11 | Schema schema() const override { 12 | return { 13 | // clang-format off 14 | .name = "users", 15 | .summary = "user accounts", 16 | .description = R"( 17 | The table provides a list of all user accounts that exist on 18 | the endpoint, retrieved at the time of the query from the 19 | operating system. 20 | )", 21 | .platforms = { Platform::Darwin, Platform::Linux, Platform::Windows }, 22 | .columns = { 23 | {.name = "name", .type = value::Type::Text, .summary = "short name"}, 24 | {.name = "full_name", .type = value::Type::Text, .summary = "full name"}, 25 | {.name = "is_admin", .type = value::Type::Bool, .summary = "1 if user has adminstrative privileges"}, 26 | {.name = "is_system", .type = value::Type::Bool, .summary = "1 if user correponds to OS service"}, 27 | {.name = "uid", .type = value::Type::Text, .summary = "user ID (can be alpha-numeric on some platforms)"}, 28 | {.name = "gid", .type = value::Type::Count, .summary = "group ID"}, 29 | {.name = "home", .type = value::Type::Text, .summary = "path to home directory"}, 30 | {.name = "shell", .type = value::Type::Text, .summary = "path to default shell"}, 31 | {.name = "email", .type = value::Type::Text, .summary = "email address"}, 32 | } 33 | // clang-format on 34 | }; 35 | } 36 | }; 37 | 38 | } // namespace zeek::agent::table 39 | -------------------------------------------------------------------------------- /src/tables/users/users.linux.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | // 3 | // This takes inspiration from https://stackoverflow.com/questions/3681895/get-all-users-on-os-x. 4 | 5 | #include "users.h" 6 | 7 | #include "autogen/config.h" 8 | #include "core/configuration.h" 9 | #include "core/database.h" 10 | #include "core/logger.h" 11 | #include "core/table.h" 12 | #include "util/fmt.h" 13 | 14 | #include 15 | 16 | namespace zeek::agent::table { 17 | 18 | class UsersLinux : public UsersCommon { 19 | public: 20 | std::vector> snapshot(const std::vector& args) override; 21 | }; 22 | 23 | namespace { 24 | database::RegisterTable _; 25 | } 26 | 27 | std::vector> UsersLinux::snapshot(const std::vector& args) { 28 | std::vector> rows; 29 | 30 | setpwent(); 31 | 32 | while ( true ) { 33 | auto pw = getpwent(); 34 | if ( ! pw ) 35 | break; 36 | 37 | Value short_name = pw->pw_name; 38 | Value full_name = pw->pw_gecos; 39 | Value is_admin = (pw->pw_uid == 0); 40 | Value is_system = {}; 41 | Value uid = std::to_string(static_cast(pw->pw_uid)); 42 | Value gid = static_cast(pw->pw_gid); 43 | Value home = pw->pw_dir; 44 | Value shell = pw->pw_shell; 45 | Value email = {}; 46 | 47 | rows.push_back({short_name, full_name, is_admin, is_system, uid, gid, home, shell, email}); 48 | } 49 | 50 | endpwent(); 51 | return rows; 52 | } 53 | 54 | } // namespace zeek::agent::table 55 | -------------------------------------------------------------------------------- /src/tables/users/users.test.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "users.h" 4 | 5 | #include "autogen/config.h" 6 | #include "util/testing.h" 7 | 8 | using namespace zeek::agent; 9 | 10 | TEST_CASE_FIXTURE(test::TableFixture, "users" * doctest::test_suite("Tables")) { 11 | useTable("users"); 12 | 13 | // We should always have root. 14 | #ifdef HAVE_WINDOWS 15 | auto result = query(R"(SELECT is_admin from users WHERE uid like "S-1-5%-500")"); 16 | #else 17 | auto result = query("SELECT is_admin from users WHERE name = \"root\""); 18 | #endif 19 | REQUIRE_EQ(result.rows.size(), 1); 20 | CHECK_EQ(result.get(0, "is_admin"), true); 21 | } 22 | -------------------------------------------------------------------------------- /src/tables/users/users.windows.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | // 3 | // This takes inspiration from https://stackoverflow.com/questions/3681895/get-all-users-on-os-x. 4 | 5 | #include "users.h" 6 | 7 | #include "core/database.h" 8 | #include "core/table.h" 9 | #include "platform/windows/platform.h" 10 | 11 | using namespace zeek::agent::platform::windows; 12 | 13 | namespace zeek::agent::table { 14 | 15 | class UsersWindows : public UsersCommon { 16 | public: 17 | std::vector> snapshot(const std::vector& args) override; 18 | }; 19 | 20 | namespace { 21 | database::RegisterTable _; 22 | } 23 | 24 | std::vector> UsersWindows::snapshot(const std::vector& args) { 25 | std::vector> rows; 26 | 27 | auto user_data = WMIManager::Get().GetUserData(); 28 | 29 | for ( const auto& user : user_data ) { 30 | Value short_name = user.name; 31 | Value full_name = user.full_name; 32 | Value is_admin = user.is_admin; 33 | Value is_system = user.is_system_acct; 34 | Value uid = user.sid; 35 | Value gid = {}; 36 | Value home = user.home_directory; 37 | Value shell = {}; 38 | Value email = {}; 39 | 40 | rows.push_back({short_name, full_name, is_admin, is_system, uid, gid, home, shell, email}); 41 | } 42 | 43 | return rows; 44 | } 45 | 46 | } // namespace zeek::agent::table 47 | -------------------------------------------------------------------------------- /src/tables/zeek_agent/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent PRIVATE zeek_agent.test.cc) 4 | 5 | if ( HAVE_DARWIN ) 6 | target_sources(zeek-agent PRIVATE zeek_agent.darwin.mm) 7 | target_link_libraries(zeek-agent PRIVATE "-framework Cocoa") 8 | target_link_libraries(zeek-agent PRIVATE "-framework SystemConfiguration") 9 | endif () 10 | 11 | if ( HAVE_LINUX ) 12 | target_sources(zeek-agent PRIVATE zeek_agent.linux.cc) 13 | endif () 14 | 15 | if ( HAVE_WINDOWS ) 16 | target_sources(zeek-agent PRIVATE zeek_agent.windows.cc) 17 | endif () 18 | -------------------------------------------------------------------------------- /src/tables/zeek_agent/zeek_agent.darwin.mm: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "zeek_agent.h" 4 | 5 | #include "autogen/config.h" 6 | #include "core/database.h" 7 | #include "platform/platform.h" 8 | 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #ifdef HAVE_BROKER 17 | #include 18 | #endif 19 | 20 | using namespace zeek::agent; 21 | using namespace zeek::agent::table; 22 | 23 | namespace { 24 | 25 | class ZeekAgentDarwin : public ZeekAgent { 26 | public: 27 | std::vector> snapshot(const std::vector& args) override; 28 | }; 29 | 30 | database::RegisterTable _; 31 | 32 | Value addresses() { 33 | // Adapted from: SCDynamicStoreRef storeRef = SCDynamicStoreCreate(NULL, (CFStringRef)@"FindCurrentInterfaceIpMac", 34 | // NULL, NULL); 35 | SCDynamicStoreRef storeRef = SCDynamicStoreCreate(nullptr, (CFStringRef) @"FindCurrentInterfaceIpMac", NULL, NULL); 36 | if ( ! storeRef ) 37 | return {}; 38 | 39 | CFPropertyListRef global = SCDynamicStoreCopyValue(storeRef, CFSTR("State:/Network/Global/IPv4")); 40 | if ( ! global ) 41 | return {}; 42 | 43 | NSString* primaryInterface = [(__bridge NSDictionary*)global valueForKey:@"PrimaryInterface"]; 44 | if ( ! primaryInterface ) 45 | return {}; 46 | 47 | auto addrs = Set(value::Type::Address); 48 | 49 | if ( auto interfaceState = [NSString stringWithFormat:@"State:/Network/Interface/%@/IPv4", primaryInterface] ) { 50 | if ( CFPropertyListRef state = SCDynamicStoreCopyValue(storeRef, (CFStringRef)interfaceState) ) { 51 | if ( NSString* ip = [(__bridge NSDictionary*)state valueForKey:@"Addresses"][0] ) 52 | addrs.insert([ip UTF8String]); 53 | 54 | CFRelease(state); 55 | } 56 | } 57 | 58 | if ( auto interfaceState = [NSString stringWithFormat:@"State:/Network/Interface/%@/IPv6", primaryInterface] ) { 59 | if ( CFPropertyListRef state = SCDynamicStoreCopyValue(storeRef, (CFStringRef)interfaceState) ) { 60 | if ( NSString* ip = [(__bridge NSDictionary*)state valueForKey:@"Addresses"][0] ) 61 | addrs.insert([ip UTF8String]); 62 | 63 | CFRelease(state); 64 | } 65 | } 66 | 67 | CFRelease(storeRef); 68 | return addrs; 69 | } 70 | 71 | std::vector> ZeekAgentDarwin::snapshot(const std::vector& args) { 72 | std::vector hostname_buffer(1024); 73 | gethostname(hostname_buffer.data(), static_cast(hostname_buffer.size())); 74 | hostname_buffer.push_back(0); 75 | 76 | auto version = [[NSProcessInfo processInfo] operatingSystemVersionString]; 77 | 78 | Value id = options().agent_id; 79 | Value instance = options().instance_id; 80 | Value hostname = hostname_buffer.data(); 81 | Value addrs = addresses(); 82 | Value platform = platform::name(); 83 | Value os_name = std::string("macOS ") + std::string([version UTF8String]); 84 | Value agent = options().version_number; 85 | #ifdef HAVE_BROKER 86 | Value broker = broker::version::string(); 87 | #else 88 | Value broker = "n/a"; 89 | #endif 90 | Value uptime = std::chrono::system_clock::now() - startupTime(); 91 | Value tables = 92 | Set(value::Type::Text, transform(database()->tables(), [](const auto* t) { return Value(t->name()); })); 93 | 94 | Value kernel_name, kernel_release, kernel_arch; 95 | struct utsname uname_info {}; 96 | if ( uname(&uname_info) >= 0 ) { 97 | kernel_name = uname_info.sysname; 98 | kernel_release = uname_info.release; 99 | kernel_arch = uname_info.machine; 100 | } 101 | 102 | return {{id, instance, hostname, addrs, platform, os_name, kernel_name, kernel_release, kernel_arch, agent, broker, 103 | uptime, tables}}; 104 | } 105 | } // namespace 106 | -------------------------------------------------------------------------------- /src/tables/zeek_agent/zeek_agent.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "core/table.h" 6 | 7 | namespace zeek::agent::table { 8 | 9 | class ZeekAgent : public SnapshotTable { 10 | public: 11 | ZeekAgent() { _startup = std::chrono::system_clock::now(); } 12 | auto startupTime() { return _startup; } 13 | 14 | Schema schema() const override { 15 | return { 16 | .name = "zeek_agent", 17 | .summary = "Zeek Agent information", 18 | .description = R"( 19 | An internal table providing information about the Zeek 20 | Agent process and the endpoint it's running on. 21 | )", 22 | .platforms = {Platform::Darwin, Platform::Linux, Platform::Windows}, 23 | .columns = {{.name = "id", 24 | .type = value::Type::Text, 25 | .summary = "unique agent ID (stable across restarts)"}, 26 | {.name = "instance", 27 | .type = value::Type::Text, 28 | .summary = "unique ID for agent process (reset on restart)"}, 29 | {.name = "hostname", .type = value::Type::Text, .summary = "name of endpoint"}, 30 | {.name = "addresses", 31 | .type = value::Type::Set, 32 | .summary = "IP addresses of endpoint's primary network connection"}, 33 | {.name = "platform", .type = value::Type::Text, .summary = "`Darwin` or `Linux` or `Windows`"}, 34 | {.name = "os_name", .type = value::Type::Text, .summary = "name of operating system"}, 35 | {.name = "kernel_name", .type = value::Type::Text, .summary = "name of OS kernel"}, 36 | {.name = "kernel_version", .type = value::Type::Text, .summary = "version of OS kernel"}, 37 | {.name = "kernel_arch", .type = value::Type::Text, .summary = "build architecture"}, 38 | {.name = "agent_version", .type = value::Type::Count, .summary = "agent version"}, 39 | {.name = "broker", .type = value::Type::Text, .summary = "Broker version"}, 40 | {.name = "uptime", .type = value::Type::Interval, .summary = "agent uptime"}, 41 | {.name = "tables", .type = value::Type::Set, .summary = "tables available to queries"}}, 42 | }; 43 | } 44 | 45 | private: 46 | Time _startup; 47 | }; 48 | 49 | } // namespace zeek::agent::table 50 | -------------------------------------------------------------------------------- /src/tables/zeek_agent/zeek_agent.test.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "zeek_agent.h" 4 | 5 | #include "autogen/config.h" 6 | #include "util/testing.h" 7 | 8 | using namespace zeek::agent; 9 | 10 | TEST_CASE_FIXTURE(test::TableFixture, "zeek_agent" * doctest::test_suite("Tables")) { 11 | useTable("zeek_agent"); 12 | 13 | auto result = query("SELECT * from zeek_agent"); 14 | CHECK_EQ(result.get(0, "agent_version"), cfg.options().version_number); 15 | } 16 | -------------------------------------------------------------------------------- /src/tables/zeek_agent/zeek_agent.windows.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "zeek_agent.h" 4 | 5 | #include "autogen/config.h" 6 | #include "core/database.h" 7 | #include "platform/platform.h" 8 | #include "util/helpers.h" 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #ifdef HAVE_BROKER 18 | #include 19 | #endif 20 | 21 | using namespace zeek::agent; 22 | using namespace zeek::agent::platform::windows; 23 | using namespace zeek::agent::table; 24 | 25 | namespace { 26 | 27 | class ZeekAgentWindows : public ZeekAgent { 28 | public: 29 | std::vector> snapshot(const std::vector& args) override; 30 | }; 31 | 32 | database::RegisterTable _; 33 | 34 | std::string get_address_string(const SOCKET_ADDRESS& addr, char* buff, int bufflen) { 35 | memset(buff, 0, bufflen); 36 | 37 | if ( addr.lpSockaddr->sa_family == AF_INET ) { 38 | auto* sa_in = reinterpret_cast(addr.lpSockaddr); 39 | inet_ntop(AF_INET, &(sa_in->sin_addr), buff, bufflen); 40 | } 41 | else if ( addr.lpSockaddr->sa_family == AF_INET6 ) { 42 | auto* sa_in6 = reinterpret_cast(addr.lpSockaddr); 43 | inet_ntop(AF_INET6, &(sa_in6->sin6_addr), buff, bufflen); 44 | } 45 | 46 | return buff; 47 | } 48 | 49 | Value addresses() { 50 | ULONG buffer_size = 15360; 51 | PIP_ADAPTER_ADDRESSES ipaa = NULL; 52 | 53 | int tries = 0; 54 | ULONG retval = 0; 55 | do { 56 | ipaa = static_cast(malloc(buffer_size)); 57 | retval = GetAdaptersAddresses(AF_UNSPEC, 0, NULL, ipaa, &buffer_size); 58 | 59 | if ( retval == ERROR_BUFFER_OVERFLOW ) { 60 | free(ipaa); 61 | ipaa = NULL; 62 | } 63 | else 64 | break; 65 | tries++; 66 | } while ( retval == ERROR_BUFFER_OVERFLOW && tries < 3 ); 67 | 68 | if ( ! ipaa ) 69 | return {}; 70 | 71 | char addr_str[128]{}; 72 | 73 | Set addrs(value::Type::Address); 74 | PIP_ADAPTER_ADDRESSES curr = ipaa; 75 | while ( curr ) { 76 | PIP_ADAPTER_UNICAST_ADDRESS unicast = curr->FirstUnicastAddress; 77 | while ( unicast ) { 78 | addrs.insert(get_address_string(unicast->Address, addr_str, sizeof(addr_str))); 79 | unicast = unicast->Next; 80 | } 81 | 82 | PIP_ADAPTER_ANYCAST_ADDRESS anycast = curr->FirstAnycastAddress; 83 | while ( anycast ) { 84 | addrs.insert(get_address_string(anycast->Address, addr_str, sizeof(addr_str))); 85 | anycast = anycast->Next; 86 | } 87 | 88 | PIP_ADAPTER_MULTICAST_ADDRESS multicast = curr->FirstMulticastAddress; 89 | while ( multicast ) { 90 | addrs.insert(get_address_string(multicast->Address, addr_str, sizeof(addr_str))); 91 | multicast = multicast->Next; 92 | } 93 | curr = curr->Next; 94 | } 95 | 96 | free(ipaa); 97 | return addrs; 98 | } 99 | 100 | Value distribution() { return {WMIManager::Get().GetOSVersion()}; } 101 | 102 | std::vector> ZeekAgentWindows::snapshot(const std::vector& args) { 103 | std::vector hostname_buffer(1024); 104 | gethostname(hostname_buffer.data(), static_cast(hostname_buffer.size())); 105 | hostname_buffer.push_back(0); 106 | 107 | Value id = options().agent_id; 108 | Value instance = options().instance_id; 109 | Value hostname = hostname_buffer.data(); 110 | Value address = std::move(addresses()); 111 | Value platform = platform::name(); 112 | Value os_name = distribution(); 113 | Value agent = options().version_number; 114 | #ifdef HAVE_BROKER 115 | Value broker = broker::version::string(); 116 | #else 117 | Value broker = "n/a"; 118 | #endif 119 | Value uptime = std::chrono::system_clock::now() - startupTime(); 120 | Value tables = 121 | Set(value::Type::Text, transform(database()->tables(), [](const auto* t) { return Value(t->name()); })); 122 | 123 | // Kernel information doesn't really exist for windows so those columns are returned as nulls. 124 | return {{id, instance, hostname, address, platform, os_name, {}, {}, {}, agent, broker, uptime, tables}}; 125 | } 126 | } // namespace 127 | -------------------------------------------------------------------------------- /src/util/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | target_sources(zeek-agent 4 | PRIVATE 5 | ascii-table.cc 6 | helpers.cc 7 | result.cc 8 | socket.cc 9 | ) 10 | 11 | if ( HAVE_POSIX ) 12 | target_sources(zeek-agent PRIVATE socket.posix.cc) 13 | else () 14 | target_sources(zeek-agent PRIVATE socket.no-ipc.cc) 15 | endif () 16 | -------------------------------------------------------------------------------- /src/util/ascii-table.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "ascii-table.h" 4 | 5 | #include "util/color.h" 6 | #include "util/helpers.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | using namespace zeek::agent; 13 | 14 | void AsciiTable::addHeader(std::vector row) { 15 | addRow(std::move(row)); 16 | _rows.back().first = true; 17 | } 18 | 19 | void AsciiTable::addRow(std::vector row) { 20 | if ( _column_widths.size() < row.size() ) 21 | _column_widths.resize(row.size()); 22 | 23 | for ( auto i = 0U; i < _column_widths.size(); i++ ) 24 | _column_widths[i] = std::max(row[i].size(), _column_widths[i]); 25 | 26 | _rows.emplace_back(false, std::move(row)); 27 | } 28 | 29 | void AsciiTable::clear(bool reset_colum_widths) { 30 | _rows.clear(); 31 | 32 | if ( reset_colum_widths ) 33 | _column_widths.clear(); 34 | } 35 | 36 | void AsciiTable::printRow(std::ostream& out, const std::vector& row, bool is_header, bool is_border, 37 | const char* sep) { 38 | for ( auto i = 0U; i < _column_widths.size(); i++ ) { 39 | auto width = _column_widths[i]; 40 | auto value = (i < row.size() ? row[i] : std::string()); 41 | auto fill_left = std::string((width - value.size()) / 2, ' '); 42 | auto fill_right = std::string(width - fill_left.size() - value.size(), ' '); 43 | 44 | if ( is_header ) 45 | value = color::yellow(value); 46 | else if ( is_border ) 47 | value = color::normal(value); 48 | 49 | if ( i > 0 ) 50 | out << sep; 51 | 52 | out << fill_left << value << fill_right; 53 | } 54 | 55 | out << '\n' << std::flush; 56 | } 57 | 58 | void AsciiTable::print(std::ostream& out, bool include_header) { 59 | auto border = transform(_column_widths, [](auto i) { return std::string(i, '-'); }); 60 | 61 | for ( const auto& r : _rows ) { 62 | if ( r.first && ! include_header ) 63 | continue; 64 | 65 | printRow(out, r.second, r.first, false, " "); 66 | 67 | if ( r.first ) 68 | printRow(out, border, false, true, " "); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/util/ascii-table.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace zeek::agent { 11 | 12 | /** Helper class to pretty-print a table of values. */ 13 | class AsciiTable { 14 | public: 15 | /** Adds a header row to the table. */ 16 | void addHeader(std::vector row); 17 | 18 | /** Adds a data row to the table. */ 19 | void addRow(std::vector row); 20 | 21 | /** Render the rows added so far to an output stream. */ 22 | void print(std::ostream& out, bool include_header = true); 23 | 24 | /** 25 | * Clears out any rows added so far. 26 | * 27 | * @param reset_column_withds if falls, keep state on the maximum column 28 | * width so far, meaning subsequently added rows will print as if a row was 29 | * still having that width 30 | */ 31 | void clear(bool reset_colum_widths = true); 32 | 33 | private: 34 | void printRow(std::ostream& out, const std::vector& row, bool is_header, bool is_border, 35 | const char* sep); 36 | 37 | std::vector>> _rows; 38 | std::vector _column_widths; 39 | }; 40 | 41 | } // namespace zeek::agent 42 | -------------------------------------------------------------------------------- /src/util/color.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "platform/platform.h" 6 | 7 | #include 8 | #include 9 | 10 | namespace zeek::agent { 11 | 12 | namespace detail { 13 | // Helper to insert escape sequence only if connection to terminal. 14 | inline std::string ansi(const char* escape, std::string x) { 15 | if ( platform::isTTY() ) 16 | return std::string(escape) + std::move(x) + std::string("\033[0m"); 17 | else 18 | return x; 19 | } 20 | } // namespace detail 21 | 22 | namespace color { 23 | /**< Returns ANSI escape sequence to print text in gray, if connected to terminal. */ 24 | inline std::string gray(std::string txt) { return detail::ansi("\033[30m", std::move(txt)); } 25 | 26 | /**< Returns ANSI escape sequence to print text in green, if connected to terminal. */ 27 | inline std::string green(std::string txt) { return detail::ansi("\033[32m", std::move(txt)); } 28 | 29 | /**< Returns ANSI escape sequence to print text in standard color, if connected to terminal. */ 30 | inline std::string normal(std::string txt) { return txt; } 31 | 32 | /**< Returns ANSI escape sequence to print text in red, if connected to terminal. */ 33 | inline std::string red(std::string txt) { return detail::ansi("\033[31m", std::move(txt)); } 34 | 35 | /**< Returns ANSI escape sequence to print text in yellow, if connected to terminal. */ 36 | inline std::string yellow(std::string txt) { return detail::ansi("\033[33m", std::move(txt)); } 37 | 38 | }; // namespace color 39 | 40 | } // namespace zeek::agent 41 | -------------------------------------------------------------------------------- /src/util/filesystem.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "autogen/config.h" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | /** Type alias. */ 13 | namespace filesystem = ghc::filesystem; 14 | 15 | namespace zeek::agent { 16 | 17 | #ifdef HAVE_WINDOWS 18 | namespace platform::windows { 19 | std::string narrowWstring(const std::wstring& wstr); // provided by platform.cc 20 | } // namespace platform::windows 21 | inline std::string path_to_string(const filesystem::path& p) { return platform::windows::narrowWstring(p.native()); } 22 | inline spdlog::filename_t path_to_spdlog_filename(const filesystem::path& p) { return p.wstring(); } 23 | #else 24 | inline std::string path_to_string(const filesystem::path& p) { return p.native(); } 25 | inline spdlog::filename_t path_to_spdlog_filename(const filesystem::path& p) { return p.string(); } 26 | #endif 27 | 28 | } // namespace zeek::agent 29 | -------------------------------------------------------------------------------- /src/util/fmt.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include "util/filesystem.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | namespace zeek::agent { 16 | 17 | /** Forwards to `fmt::format()`. */ 18 | template 19 | auto frmt(fmt::format_string format, Args&&... args) { 20 | return ::fmt::format(format, std::forward(args)...); 21 | } 22 | 23 | /** Forwards to `fmt::format()`. */ 24 | template 25 | auto frmt(const wchar_t* format, Args&&... args) { 26 | return ::fmt::format(format, std::forward(args)...); 27 | } 28 | 29 | /** Renders class instances through their `str()` method. */ 30 | template 31 | std::string to_string(const T& t) { 32 | return t.str(); 33 | } 34 | 35 | /** Renders class instances through their `str()` method. */ 36 | template<> 37 | inline std::string to_string(const filesystem::path& t) { 38 | return zeek::agent::path_to_string(t); 39 | } 40 | 41 | /** Fallback for strings. */ 42 | inline std::string to_string(const std::string& s) { return s; } 43 | 44 | } // namespace zeek::agent 45 | 46 | template<> 47 | struct fmt::formatter : fmt::formatter { 48 | auto format(const nlohmann::json& json, format_context& ctx) const -> decltype(ctx.out()) { 49 | return fmt::format_to(ctx.out(), "{}", json.dump()); 50 | } 51 | }; 52 | 53 | template<> 54 | struct fmt::formatter : fmt::formatter { 55 | auto format(const filesystem::path& p, format_context& ctx) const -> decltype(ctx.out()) { 56 | return fmt::format_to(ctx.out(), "{}", zeek::agent::path_to_string(p)); 57 | } 58 | }; 59 | 60 | template<> 61 | struct fmt::formatter : fmt::formatter { 62 | auto format(const wchar_t& c, wformat_context& ctx) const -> decltype(ctx.out()) { 63 | return fmt::format_to(ctx.out(), L"{}", c); 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /src/util/pimpl.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include 6 | 7 | namespace zeek::agent { 8 | 9 | /** Helper base class to implement the PIMPL idiom. */ 10 | template 11 | class Pimpl { 12 | public: 13 | Pimpl() : _pimpl(std::make_unique()) {} 14 | ~Pimpl() {} 15 | 16 | inline const auto* pimpl() const { return _pimpl.get(); } 17 | inline auto* pimpl() { return _pimpl.get(); } 18 | 19 | Pimpl(const Pimpl& other) = delete; 20 | Pimpl(Pimpl&& other) = delete; 21 | Pimpl& operator=(const Pimpl& other) = delete; 22 | Pimpl& operator=(Pimpl&& other) = delete; 23 | 24 | struct Implementation; 25 | 26 | private: 27 | std::unique_ptr _pimpl; 28 | }; 29 | 30 | } // namespace zeek::agent 31 | -------------------------------------------------------------------------------- /src/util/result.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #include "result.h" 4 | 5 | #include "testing.h" 6 | 7 | #include 8 | #include 9 | 10 | using namespace zeek::agent; 11 | 12 | TEST_SUITE("Result") { 13 | TEST_CASE_TEMPLATE("default constructed is error", T, Nothing, bool, std::string) { 14 | Result r; 15 | CHECK(! r); 16 | CHECK(r.errorOrThrow() == result::Error("")); 17 | } 18 | 19 | TEST_CASE_TEMPLATE("conversion to bool", T, Nothing, bool, std::string) { 20 | Result r; 21 | CHECK(! r); 22 | 23 | if constexpr ( std::is_same_v ) 24 | r = Nothing(); 25 | else 26 | r = T{}; 27 | 28 | CHECK(r); 29 | } 30 | 31 | TEST_CASE("errorOrThrow") { 32 | CHECK_THROWS_WITH_AS(Result(42).errorOrThrow(), "", const result::NoError&); 33 | CHECK_EQ(Result().errorOrThrow(), result::Error("")); 34 | CHECK_EQ(Result(result::Error("foo")).errorOrThrow(), result::Error("foo")); 35 | } 36 | 37 | TEST_CASE("equal") { 38 | CHECK_EQ(Result(42), Result(42)); 39 | CHECK_EQ(Result(0), Result(0)); 40 | CHECK_EQ(Result(result::Error("foo")), Result(result::Error("foo"))); 41 | } 42 | 43 | TEST_CASE("not equal") { 44 | CHECK_NE(Result(42), Result(0)); 45 | CHECK_NE(Result(42), Result(result::Error("foo"))); 46 | } 47 | 48 | TEST_CASE("valueOrThrow") { 49 | SUBCASE("const") { 50 | const auto r1 = Result(0); 51 | const auto r2 = Result(); 52 | const auto r3 = Result(result::Error("foo")); 53 | 54 | CHECK_EQ(r1.valueOrThrow(), 0); 55 | CHECK_THROWS_WITH_AS(r2.valueOrThrow(), "", const result::NoResult&); 56 | CHECK_THROWS_WITH_AS(r3.valueOrThrow(), "foo", const result::NoResult&); 57 | } 58 | 59 | SUBCASE("non const") { 60 | auto r1 = Result(0); 61 | auto r2 = Result(); 62 | auto r3 = Result(result::Error("foo")); 63 | 64 | CHECK_EQ(r1.valueOrThrow(), 0); 65 | CHECK_THROWS_WITH_AS(r2.valueOrThrow(), "", const result::NoResult&); 66 | CHECK_THROWS_WITH_AS(r3.valueOrThrow(), "foo", const result::NoResult&); 67 | 68 | r1.valueOrThrow() += 42; 69 | CHECK_EQ(r1, Result(42)); 70 | } 71 | } 72 | 73 | TEST_SUITE("Error") { 74 | TEST_CASE("string") { CHECK_EQ(result::Error("foo").operator std::string(), "foo"); } 75 | TEST_CASE("string_view") { CHECK_EQ(result::Error("foo").operator std::string_view(), "foo"); } 76 | 77 | TEST_CASE("comparison") { 78 | auto e1 = result::Error(); 79 | auto e2 = result::Error("bar"); 80 | 81 | CHECK_EQ(e1, e1); 82 | CHECK_EQ(e2, e2); 83 | CHECK_NE(e1, e2); 84 | CHECK_NE(e2, e1); 85 | } 86 | 87 | TEST_CASE("NoError") { CHECK_EQ(std::string(result::NoError().what()), ""); } 88 | } 89 | 90 | TEST_SUITE("Nothing") { 91 | TEST_CASE("comparison") { 92 | CHECK_EQ(Nothing(), Nothing()); 93 | CHECK_FALSE(Nothing() != Nothing()); 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /src/util/socket.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | // 3 | // Socket implementation for POSIX systems. 4 | 5 | #include "socket.h" 6 | 7 | #include "util/fmt.h" 8 | #include "util/testing.h" 9 | 10 | using namespace zeek::agent; 11 | 12 | socket::SocketBuffer& socket::SocketBuffer::operator=(const SocketBuffer& other) { 13 | if ( this == &other ) 14 | return *this; 15 | 16 | _socket = other._socket; 17 | _remote = other._remote; 18 | return *this; 19 | } 20 | 21 | int socket::SocketBuffer::sync() { 22 | if ( auto rc = _socket->write(str(), *_remote); ! rc ) { 23 | logger()->debug("failed to send message to socket: {}", rc.error()); 24 | _remote->setError(rc.error()); 25 | } 26 | 27 | str(""); 28 | return 0; 29 | } 30 | 31 | socket::Remote& socket::Remote::operator=(const Remote& other) noexcept { 32 | if ( this == &other ) 33 | return *this; 34 | 35 | _dst = other._dst; 36 | _sbuf = SocketBuffer(other._sbuf._socket, this); 37 | _sout = std::make_unique(&_sbuf); 38 | return *this; 39 | } 40 | 41 | TEST_SUITE("socket") { 42 | TEST_CASE("read-and-write") { 43 | auto path1 = filesystem::path(frmt("/tmp/zeek-agent-test-socket.{}.1", getpid())); 44 | auto path2 = filesystem::path(frmt("/tmp/zeek-agent-test-socket.{}.2", getpid())); 45 | 46 | Socket socket1; 47 | REQUIRE(! socket1); 48 | REQUIRE(socket1.bind(path1)); 49 | REQUIRE(socket1); 50 | 51 | Socket socket2; 52 | REQUIRE(! socket2); 53 | REQUIRE(socket2.bind(path2)); 54 | REQUIRE(socket2); 55 | 56 | socket1.write("Hello, Socket 2!", {&socket1, path2}); 57 | 58 | auto result = socket2.read(); 59 | REQUIRE(result); 60 | REQUIRE(*result); 61 | auto [data_1, remote_sender_1] = **result; 62 | CHECK_EQ(data_1, "Hello, Socket 2!"); 63 | 64 | remote_sender_1 << "Hello, Socket 1!" << std::flush; 65 | 66 | result = socket1.read(); 67 | REQUIRE(result); 68 | REQUIRE(*result); 69 | auto [data_2, remote_sender_2] = **result; 70 | CHECK_EQ(data_2, "Hello, Socket 1!"); 71 | 72 | CHECK(remote_sender_1); 73 | CHECK(remote_sender_2); 74 | CHECK(socket1); 75 | CHECK(socket2); 76 | } 77 | 78 | TEST_CASE("unknown-remote") { 79 | auto path = filesystem::path(frmt("/tmp/zeek-agent-test-socket.{}", getpid())); 80 | 81 | Socket socket; 82 | REQUIRE(socket.bind(path)); 83 | 84 | socket::Remote remote(&socket, filesystem::path("/DOES-NOT-EXIST")); 85 | remote << "xyz" << std::flush; 86 | 87 | CHECK(! remote); 88 | CHECK(remote.error()); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/util/socket.no-ipc.cc: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | // 3 | // Limited Socket implementation supporting only communication within the same 4 | // process. This is for platforms where we have not implemented IPC support 5 | // yet. 6 | 7 | #include "socket.h" 8 | 9 | #include "core/logger.h" 10 | #include "platform/platform.h" 11 | #include "util/fmt.h" 12 | #include "util/helpers.h" 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | using namespace zeek::agent; 19 | 20 | socket::Address socket::Remote::pathToDestination(const filesystem::path& path) { return to_string(path); } 21 | 22 | // Global map of messages queued for each path/address. 23 | using Message = std::pair; 24 | std::map> messages; // messages queued for each path 25 | 26 | // Lock protecting access to messages. 27 | std::mutex mutex; 28 | 29 | template<> 30 | struct Pimpl::Implementation { 31 | // Binds the socket to a local path, setting it up for communication. 32 | Result bind(const filesystem::path& path); 33 | 34 | // Reads one message from the socket. If no input is currently available, 35 | Result read(); 36 | 37 | // Sends one message to the currently active destination. This will fail 38 | Result write(const std::string& data, const socket::Remote& dst); 39 | 40 | Socket* _socket = nullptr; // socket that this implementation belongs to 41 | filesystem::path _path; // path the socket is bound to 42 | socket::Address _idx; // map into messages 43 | }; 44 | 45 | Result Socket::Implementation::bind(const filesystem::path& path) { 46 | const std::scoped_lock lock(mutex); 47 | 48 | _path = path; 49 | _idx = to_string(_path); 50 | messages[_idx] = {}; 51 | return Nothing(); 52 | } 53 | 54 | Result Socket::Implementation::read() { 55 | const std::scoped_lock lock(mutex); 56 | 57 | auto i = messages.find(_idx); 58 | if ( i == messages.end() ) 59 | return result::Error("socket not bound", to_string(_path)); 60 | 61 | if ( i->second.empty() ) 62 | return {std::nullopt}; 63 | 64 | auto msg = i->second.front(); 65 | i->second.pop_front(); 66 | 67 | return std::make_optional(std::make_pair(msg.first, socket::Remote(_socket, msg.second))); 68 | } 69 | 70 | Result Socket::Implementation::write(const std::string& data, const socket::Remote& dst) { 71 | const std::scoped_lock lock(mutex); 72 | 73 | auto i = messages.find(dst.destination()); 74 | if ( i == messages.end() ) 75 | return result::Error("socket not bound", to_string(dst.destination())); 76 | 77 | i->second.emplace_back(data, _idx); 78 | return Nothing(); 79 | } 80 | 81 | Socket::Socket() { pimpl()->_socket = this; } 82 | 83 | Socket::~Socket() {} 84 | 85 | bool Socket::isActive() const { return ! pimpl()->_path.empty(); }; 86 | 87 | Result Socket::bind(const filesystem::path& path) { return pimpl()->bind(path); } 88 | 89 | Result Socket::read() { return pimpl()->read(); } 90 | 91 | Result Socket::write(const std::string& data, const socket::Remote& dst) { return pimpl()->write(data, dst); } 92 | 93 | bool Socket::supportsIPC() { return false; } 94 | -------------------------------------------------------------------------------- /src/util/testing.h: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2021-2024 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | // clang-format off 6 | #include 7 | // clang-format on 8 | 9 | #include "core/configuration.h" 10 | #include "core/database.h" 11 | #include "core/logger.h" 12 | #include "core/scheduler.h" 13 | 14 | #include 15 | #include 16 | 17 | namespace zeek::agent::test { 18 | 19 | /** 20 | * Fixture class for tests of table implementations, making it easy to query 21 | * them for checking results. 22 | */ 23 | class TableFixture { 24 | public: 25 | TableFixture() : db(&cfg, &scheduler) {} 26 | 27 | /** 28 | * Make global registered table of given name available for querying. 29 | * 30 | * @param table_name name of a table registered through `database::Register()`. 31 | */ 32 | void useTable(std::string table_name) { 33 | const auto& tables = Database::registeredTables(); 34 | auto t = tables.find(table_name); 35 | REQUIRE_MESSAGE(t != tables.end(), "table ", table_name, " is not available"); 36 | db.addTable(t->second.get()); 37 | } 38 | 39 | void enableMockDataForTable(std::string table_name) { 40 | const auto& tables = Database::registeredTables(); 41 | auto t = tables.find(table_name); 42 | REQUIRE_MESSAGE(t != tables.end(), "table ", table_name, " is not available"); 43 | t->second->enableMockData(); 44 | } 45 | 46 | /** 47 | * Perform a single-shot query against all added tables. This will execute 48 | * an SQL statement and block until the result is available. If there's a 49 | * problem with the query, it will directly fail the current test and abort 50 | * it. 51 | * 52 | * @param stmt SQL statement to execute 53 | * @returns a valid query result (any prior errors are caught and lead to abortion) 54 | */ 55 | query::Result query(std::string stmt) { 56 | std::optional result; 57 | Query q = {.sql_stmt = std::move(stmt), 58 | .subscription = {}, 59 | .cookie = "", 60 | .callback_result = [&](query::ID id, query::Result result_) { result = std::move(result_); }}; 61 | 62 | auto rc = db.query(q); 63 | REQUIRE_MESSAGE(rc, rc.error()); 64 | 65 | scheduler.advance(scheduler.currentTime() + 1s); // this will execute the query 66 | 67 | REQUIRE(result); 68 | return std::move(*result); 69 | }; 70 | 71 | ~TableFixture() {} 72 | 73 | Configuration cfg; 74 | Scheduler scheduler; 75 | Database db; 76 | }; 77 | 78 | 79 | } // namespace zeek::agent::test 80 | -------------------------------------------------------------------------------- /src/util/variant.h: -------------------------------------------------------------------------------- 1 | // Copyrights (c) 2021 by the Zeek Project. See LICENSE for details. 2 | 3 | #pragma once 4 | 5 | #include 6 | #include 7 | 8 | namespace zeek::agent { 9 | 10 | // A variant that won't map `const char*` to bool. See 11 | // https://stackoverflow.com/a/31009102. Note that we need to cast instance to 12 | // the base for std::visit() becasue of this GCC bug: 13 | // https://stackoverflow.com/a/68441460. *sigh* 14 | template 15 | struct BetterVariant : public std::variant { 16 | using Base = std::variant; 17 | using Base::variant; 18 | 19 | BetterVariant(char* s) : Base(std::string(s)) {} 20 | BetterVariant(const char* s) : Base(std::string(s)) {} 21 | 22 | auto& operator=(const char* s) { 23 | Base::operator=(std::string(s)); 24 | return *this; 25 | } 26 | 27 | auto& operator=(char* s) { 28 | Base::operator=(std::string(s)); 29 | return *this; 30 | } 31 | }; 32 | 33 | } // namespace zeek::agent 34 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | .btest.failed.dat 2 | .tmp 3 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.error/reporter.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | XXXXXXXXXX.XXXXXX Reporter::ERROR error from Zeek Agent: could not compile query (failed to compile SQL statement: SELECT foo FROM bar (no such table: bar)) [] 3 | XXXXXXXXXX.XXXXXX Reporter::INFO received termination signal 4 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.if-missing-table/zeek..stdout: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.log/changes: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | join 3 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.query/zeek.output: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | Hurz, [version=] 3 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.requires-table/zeek..stdout: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.scheduled/zeek.output: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | got result:, [id=, version=] 3 | got result:, [id=, version=] 4 | terminating soon - there should not be another 'got result' after this 5 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.table.files/zeek.zeek-agent-files.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | #separator \x09 3 | #set_separator , 4 | #empty_field (empty) 5 | #unset_field - 6 | #path zeek-agent-files 7 | #open XXXX-XX-XX-XX-XX-XX 8 | #fields t hid change path type_ uid gid mode mtime size 9 | #types time string string string string count count string time count 10 | XXXXXXXXXX.XXXXXX - text_a_b text_a_c 103 104 text_a_f XXXXXXXXXX.XXXXXX 107 11 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.table.processes/zeek.zeek-agent-processes.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | #separator \x09 3 | #set_separator , 4 | #empty_field (empty) 5 | #unset_field - 6 | #path zeek-agent-processes 7 | #open XXXX-XX-XX-XX-XX-XX 8 | #fields t hid change name pid uid gid ppid priority startup 9 | #types time string string string count count count count string interval 10 | XXXXXXXXXX.XXXXXX - text_a_a 101 103 104 102 text_a_h 18.000000 11 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.table.sockets/zeek.zeek-agent-sockets.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | #separator \x09 3 | #set_separator , 4 | #empty_field (empty) 5 | #unset_field - 6 | #path zeek-agent-sockets 7 | #open XXXX-XX-XX-XX-XX-XX 8 | #fields t hid change pid process family protocol local_addr local_port remote_addr remote_port state 9 | #types time string string count string string count addr count addr count string 10 | XXXXXXXXXX.XXXXXX - 100 text_a_b text_a_c 103 192.168.1.5 105 192.168.1.7 107 text_a_i 11 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.table.ssh/zeek.zeek-agent-ssh-authorized-keys.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | #separator \x09 3 | #set_separator , 4 | #empty_field (empty) 5 | #unset_field - 6 | #path zeek-agent-ssh-authorized-keys 7 | #open XXXX-XX-XX-XX-XX-XX 8 | #fields t hid change path number content 9 | #types time string string string count string 10 | XXXXXXXXXX.XXXXXX - text_a_b 102 blob_a_d 11 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.table.system_logs/zeek.zeek-agent-system-logs.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | #separator \x09 3 | #set_separator , 4 | #empty_field (empty) 5 | #unset_field - 6 | #path zeek-agent-system-logs 7 | #open XXXX-XX-XX-XX-XX-XX 8 | #fields t hid time_ process level message 9 | #types time string time string string string 10 | XXXXXXXXXX.XXXXXX XXXXXXXXXX.XXXXXX text_e_b text_e_c text_e_d 11 | -------------------------------------------------------------------------------- /tests/Baseline/zeek.table.users/zeek.zeek-agent-users.log: -------------------------------------------------------------------------------- 1 | ### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63. 2 | #separator \x09 3 | #set_separator , 4 | #empty_field (empty) 5 | #unset_field - 6 | #path zeek-agent-users 7 | #open XXXX-XX-XX-XX-XX-XX 8 | #fields t hid change name full_name is_admin is_system uid gid home shell email 9 | #types time string string string string bool bool string count string string string 10 | XXXXXXXXXX.XXXXXX - text_a_a text_a_b F F text_a_e 105 text_a_g text_a_h text_a_i 11 | -------------------------------------------------------------------------------- /tests/Files/random.seed: -------------------------------------------------------------------------------- 1 | 2983378351 2 | 1299727368 3 | 0 4 | 310447 5 | 0 6 | 1409073626 7 | 3975311262 8 | 34130240 9 | 1450515018 10 | 1466150520 11 | 1342286698 12 | 1193956778 13 | 2188527278 14 | 3361989254 15 | 3912865238 16 | 3596260151 17 | 517973768 18 | 1462428821 19 | 0 20 | 2278350848 21 | 32767 22 | -------------------------------------------------------------------------------- /tests/Makefile: -------------------------------------------------------------------------------- 1 | 2 | test: 3 | @btest -j -d 4 | 5 | test-no-zeek: 6 | @btest -j -d -g no-zeek 7 | -------------------------------------------------------------------------------- /tests/Scripts/canonifier: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | 3 | base=$(cd $(dirname $0) && pwd) 4 | 5 | # Get us "modern" regexps with sed. 6 | if [ `uname` == "Linux" ]; then 7 | sed="sed -r" 8 | else 9 | sed="sed -E" 10 | fi 11 | 12 | ${base}/diff-remove-timestamps | 13 | sed 's/H[a-zA-Z0-9]\{20,22\}\([^a-zA-Z0-9]\)/\1/g' 14 | -------------------------------------------------------------------------------- /tests/Scripts/diff-remove-timestamps: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env bash 2 | # 3 | # Replace anything which looks like timestamps with XXXs (including the #start/end markers in logs). 4 | 5 | # Get us "modern" regexps with sed. 6 | if [ `uname` == "Linux" ]; then 7 | sed="sed -r" 8 | else 9 | sed="sed -E" 10 | fi 11 | 12 | $sed 's/(0\.000000)|([0-9]{9,10}\.[0-9]{2,8})/XXXXXXXXXX.XXXXXX/g' | \ 13 | $sed 's/^ *#(open|close).(19|20)..-..-..-..-..-..$/#\1 XXXX-XX-XX-XX-XX-XX/g' 14 | -------------------------------------------------------------------------------- /tests/Scripts/get-zeek-env: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | # 3 | # BTest helper for getting values for Zeek-related environment variables. 4 | 5 | base=$(dirname $0) 6 | zeek_dist=$(cat ${base}/../../build/CMakeCache.txt 2>/dev/null | grep ZEEK_DIST | cut -d = -f 2) 7 | 8 | if [ -n "${zeek_dist}" ]; then 9 | if [ "$1" = "zeekpath" ]; then 10 | ${zeek_dist}/build/zeek-path-dev 11 | else 12 | echo "usage: $(basename $0) " >&2 13 | exit 1 14 | fi 15 | else 16 | # Use Zeek installation for testing. In this case zeek-config must be in PATH. 17 | if ! which zeek-config >/dev/null 2>&1; then 18 | # We don't treat this as an error because we want to run tests without Zeek too. 19 | exit 0 20 | fi 21 | 22 | if [ "$1" = "zeekpath" ]; then 23 | echo "$(cd ${base}/.. && pwd):$(zeek-config --zeekpath)" 24 | else 25 | echo "usage: $(basename $0) " >&2 26 | exit 1 27 | fi 28 | fi 29 | -------------------------------------------------------------------------------- /tests/agent/ctest.sh: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Run the C++ unit tests. 2 | # @TEST-GROUP: no-zeek 3 | # 4 | # @TEST-EXEC: cd "${BUILD_DIRECTORY}" && ./bin/zeek-agent --test >${TEST_DIAGNOSTICS} 2>&1 5 | -------------------------------------------------------------------------------- /tests/btest.cfg: -------------------------------------------------------------------------------- 1 | [btest] 2 | TestDirs = agent zeek 3 | TmpDir = %(testbase)s/.tmp 4 | BaselineDir = %(testbase)s/Baseline 5 | IgnoreDirs = .tmp 6 | IgnoreFiles = *.tmp *.swp #* *.trace .DS_Store 7 | 8 | [environment] 9 | BUILD_DIRECTORY=%(testbase)s/../build 10 | CONFIG=%(testbase)s/zeek-agent.cfg 11 | FRAMEWORK=%(testbase)s/../zeek-agent/scripts/framework 12 | LC_ALL=C 13 | PACKAGE=%(testbase)s/../zeek-agent/scripts 14 | PATH=%(testbase)s/../build/bin:%(default_path)s 15 | PROJECT_ROOT=%(testbase)s/.. 16 | TEST_DIFF_CANONIFIER=%(testbase)s/Scripts/canonifier 17 | TMPDIR=%(testbase)s/.tmp 18 | TRACES=%(testbase)s/Traces 19 | TZ=UTC 20 | ZEEKPATH=`%(testbase)s/Scripts/get-zeek-env zeekpath` 21 | ZEEK_DEFAULT_CONNECT_RETRY=1 22 | ZEEK_DEFAULT_LISTEN_ADDRESS=127.0.0.1 23 | ZEEK_DEFAULT_LISTEN_RETRY=1 24 | ZEEK_DNS_FAKE=1 25 | ZEEK_SEED_FILE=%(testbase)s/Files/random.seed 26 | -------------------------------------------------------------------------------- /tests/test-setup.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-IGNORE 2 | 3 | module TestSetup; 4 | 5 | @if ( getenv("ZEEK_PORT") != "" ) 6 | 7 | @if ( Version::number >= 50000 ) 8 | redef Broker::default_port_websocket = to_port(getenv("ZEEK_PORT")); 9 | @else 10 | redef ZeekAgent::listen_port = to_port(getenv("ZEEK_PORT")); 11 | @endif 12 | 13 | @endif 14 | -------------------------------------------------------------------------------- /tests/zeek-agent.cfg: -------------------------------------------------------------------------------- 1 | [zeek] 2 | reconnect_interval=1.0 3 | -------------------------------------------------------------------------------- /tests/zeek/error.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Test a basic query to the agent. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -L debug -N -z localhost:${ZEEK_PORT} 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: cat zeek/reporter.log | zeek-cut -n location >reporter.log 9 | # @TEST-EXEC: btest-diff reporter.log 10 | 11 | @load test-setup 12 | 13 | event got_result() 14 | { } 15 | 16 | event zeek_init() 17 | { 18 | ZeekAgent::query([ $sql_stmt="SELECT foo FROM bar", $event_=got_result ]); 19 | } 20 | 21 | event ZeekAgentAPI::agent_error_v1(ctx: ZeekAgent::Context, msg: string) 22 | { 23 | terminate(); 24 | } 25 | -------------------------------------------------------------------------------- /tests/zeek/if-missing-table.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Test a query to the agent that requires a table to not exist. Should not generate any result. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -L debug -N -z localhost:${ZEEK_PORT} 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: test '!' -f reporter.log 9 | # @TEST-EXEC: btest-diff zeek/.stdout 10 | 11 | @load test-setup 12 | 13 | type Columns: record { 14 | version: int; 15 | }; 16 | 17 | event got_result(ctx: ZeekAgent::Context, data: Columns) 18 | { 19 | print "SHOULD NOT HAPPEN 1"; 20 | terminate(); 21 | } 22 | 23 | event do_terminate() 24 | { 25 | terminate(); 26 | } 27 | 28 | event zeek_init() 29 | { 30 | ZeekAgent::query([ $sql_stmt="SELECT agent_version FROM zeek_agent", 31 | $event_=got_result, $cookie="Hurz", $if_missing_tables=set( 32 | "zeek_agent") ]); 33 | schedule 5secs { do_terminate() }; 34 | } 35 | 36 | event ZeekAgentAPI::agent_error_v1(ctx: ZeekAgent::Context, msg: string) 37 | { 38 | print "SHOULD NOT HAPPEN 2", msg; 39 | } 40 | -------------------------------------------------------------------------------- /tests/zeek/log.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Check that agent connectivity gets recorded in log file. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -L info -N -z localhost:${ZEEK_PORT} 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: cat zeek/zeek-agent.log | zeek-cut type_ >changes 9 | # @TEST-EXEC: btest-diff changes 10 | 11 | @load test-setup 12 | 13 | hook ZeekAgent::log_policy(rec: ZeekAgent::Info, id: Log::ID, 14 | filter: Log::Filter) 15 | { 16 | if ( rec$type_ == "join" ) 17 | terminate(); 18 | } 19 | -------------------------------------------------------------------------------- /tests/zeek/query.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Test a basic query to the agent. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -L info -N -z localhost:${ZEEK_PORT} 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: cat zeek/.stdout | sed 's/version=[0-9]\{1,\}/version=/g' >zeek/output 9 | # @TEST-EXEC: btest-diff zeek/output 10 | 11 | @load test-setup 12 | 13 | type Columns: record { 14 | version: count; 15 | }; 16 | 17 | event got_result(ctx: ZeekAgent::Context, data: Columns) 18 | { 19 | print ctx$cookie, data; 20 | terminate(); 21 | } 22 | 23 | event zeek_init() 24 | { 25 | ZeekAgent::query([ $sql_stmt="SELECT agent_version FROM zeek_agent", 26 | $event_=got_result, $cookie="Hurz", $schedule_=20secs ]); 27 | } 28 | -------------------------------------------------------------------------------- /tests/zeek/requires-table.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Test a query to the agent that requires a non-existent table. Should not generate an error. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -L debug -N -z localhost:${ZEEK_PORT} 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: test '!' -f reporter.log 9 | # @TEST-EXEC: btest-diff zeek/.stdout 10 | 11 | @load test-setup 12 | 13 | event do_terminate() 14 | { 15 | terminate(); 16 | } 17 | 18 | event got_result() 19 | { # can't get here 20 | } 21 | 22 | event zeek_init() 23 | { 24 | ZeekAgent::query([ $sql_stmt="SELECT foo FROM bar", $event_=got_result, 25 | $requires_tables=set("bar") ]); 26 | schedule 5secs { do_terminate() }; 27 | } 28 | 29 | event ZeekAgentAPI::agent_error_v1(ctx: ZeekAgent::Context, msg: string) 30 | { 31 | print "SHOULD NOT HAPPEN", msg; 32 | } 33 | -------------------------------------------------------------------------------- /tests/zeek/scheduled.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Test a repeatedly scheduled query with cancel. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -L info -N -z localhost:${ZEEK_PORT} >output 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: cat zeek/.stdout | sed 's/version=[0-9]\{1,\}/version=/g' >zeek/output 9 | # @TEST-EXEC: btest-diff zeek/output 10 | 11 | @load test-setup 12 | 13 | type Columns: record { 14 | id: string; 15 | version: count; 16 | }; 17 | 18 | event do_terminate() 19 | { 20 | terminate(); 21 | } 22 | 23 | global query_id: string; 24 | global n = 0; 25 | 26 | event got_result(ctx: ZeekAgent::Context, data: Columns) 27 | { 28 | print "got result:", data; 29 | 30 | if ( ++n == 2 ) 31 | { 32 | ZeekAgent::cancel(query_id); 33 | print "terminating soon - there should not be another 'got result' after this"; 34 | schedule 2secs { do_terminate() }; 35 | } 36 | } 37 | 38 | event zeek_init() 39 | { 40 | query_id = ZeekAgent::query([ 41 | $sql_stmt="SELECT id, agent_version FROM zeek_agent", 42 | $event_=got_result, $cookie="Hurz", $schedule_=3secs ]); 43 | } 44 | -------------------------------------------------------------------------------- /tests/zeek/table/files.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Test the `files` table script end-to-end with mock data. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} ${PACKAGE}/table/files.zeek %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -M -N -L info -z localhost:${ZEEK_PORT} 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: cat zeek/zeek-agent-files.log | zeek-cut -cn host >tmp && mv tmp zeek/zeek-agent-files.log 9 | # @TEST-EXEC: btest-diff zeek/zeek-agent-files.log 10 | 11 | @load test-setup 12 | 13 | redef ZeekAgent_Files::subscription = ZeekAgent::SnapshotPlusDifferences; 14 | 15 | # We only accept the 1st write writer so that our output doesn't depend on 16 | # runtime duration. 17 | global already_logged = F; 18 | 19 | hook ZeekAgent_Files::log_policy(rec: any, id: Log::ID, filter: Log::Filter) 20 | { 21 | if ( already_logged ) 22 | break; 23 | else 24 | already_logged = T; 25 | } 26 | 27 | event do_terminate() 28 | { 29 | terminate(); 30 | } 31 | 32 | event ZeekAgentAPI::agent_hello_v1(ctx: ZeekAgent::Context, 33 | columns: ZeekAgentAPI::AgentHelloV1) 34 | { 35 | schedule 2secs { do_terminate() }; 36 | } 37 | -------------------------------------------------------------------------------- /tests/zeek/table/processes.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Test the `processes` table script end-to-end with mock data. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} ${PACKAGE}/table/processes.zeek %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -M -N -L info -z localhost:${ZEEK_PORT} 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: cat zeek/zeek-agent-processes.log | zeek-cut -cn host >tmp && mv tmp zeek/zeek-agent-processes.log 9 | # @TEST-EXEC: btest-diff zeek/zeek-agent-processes.log 10 | 11 | @load test-setup 12 | 13 | redef ZeekAgent_Processes::subscription = ZeekAgent::SnapshotPlusDifferences; 14 | 15 | # We only accept the 1st write writer so that our output doesn't depend on 16 | # runtime duration. 17 | global already_logged = F; 18 | 19 | hook ZeekAgent_Processes::log_policy(rec: any, id: Log::ID, filter: Log::Filter) 20 | { 21 | if ( already_logged ) 22 | break; 23 | else 24 | already_logged = T; 25 | } 26 | 27 | event do_terminate() 28 | { 29 | terminate(); 30 | } 31 | 32 | event ZeekAgentAPI::agent_hello_v1(ctx: ZeekAgent::Context, 33 | columns: ZeekAgentAPI::AgentHelloV1) 34 | { 35 | schedule 2secs { do_terminate() }; 36 | } 37 | -------------------------------------------------------------------------------- /tests/zeek/table/sockets.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Test the `sockets` table script end-to-end with mock data. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} ${PACKAGE}/table/sockets.zeek %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -M -N -L info -z localhost:${ZEEK_PORT} 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: cat zeek/zeek-agent-sockets.log | zeek-cut -cn host >tmp && mv tmp zeek/zeek-agent-sockets.log 9 | # @TEST-EXEC: btest-diff zeek/zeek-agent-sockets.log 10 | 11 | @load test-setup 12 | 13 | redef ZeekAgent_Sockets::subscription = ZeekAgent::SnapshotPlusDifferences; 14 | 15 | # We only accept the 1st write writer so that our output doesn't depend on 16 | # runtime duration. 17 | global already_logged = F; 18 | 19 | hook ZeekAgent_Sockets::log_policy(rec: any, id: Log::ID, filter: Log::Filter) 20 | { 21 | if ( already_logged ) 22 | break; 23 | else 24 | already_logged = T; 25 | } 26 | 27 | event do_terminate() 28 | { 29 | terminate(); 30 | } 31 | 32 | event ZeekAgentAPI::agent_hello_v1(ctx: ZeekAgent::Context, 33 | columns: ZeekAgentAPI::AgentHelloV1) 34 | { 35 | schedule 2secs { do_terminate() }; 36 | } 37 | -------------------------------------------------------------------------------- /tests/zeek/table/ssh.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Test the `ssh` table script end-to-end with mock data. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} ${PACKAGE}/table/ssh.zeek %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -M -N -L info -z localhost:${ZEEK_PORT} 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: cat zeek/zeek-agent-ssh-authorized-keys.log | zeek-cut -cn host >tmp && mv tmp zeek/zeek-agent-ssh-authorized-keys.log 9 | # @TEST-EXEC: btest-diff zeek/zeek-agent-ssh-authorized-keys.log 10 | # 11 | # Note: We can't test the configuration log because the agent can't mock the 12 | # dynamic record content. Don't see a good way right now how we could do 13 | # that. 14 | 15 | @load test-setup 16 | 17 | redef ZeekAgent_SSH::subscription = ZeekAgent::SnapshotPlusDifferences; 18 | 19 | # We only accept the 1st write writer so that our output doesn't depend on 20 | # runtime duration. 21 | global already_logged_keys = F; 22 | 23 | hook ZeekAgent_SSH::log_policy_keys(rec: any, id: Log::ID, filter: Log::Filter) 24 | { 25 | if ( already_logged_keys ) 26 | break; 27 | else 28 | already_logged_keys = T; 29 | } 30 | 31 | event do_terminate() 32 | { 33 | terminate(); 34 | } 35 | 36 | event ZeekAgentAPI::agent_hello_v1(ctx: ZeekAgent::Context, 37 | columns: ZeekAgentAPI::AgentHelloV1) 38 | { 39 | schedule 5secs { do_terminate() }; 40 | } 41 | -------------------------------------------------------------------------------- /tests/zeek/table/system_logs.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Test the `system-logs` table script end-to-end with mock data. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} ${PACKAGE}/table/system-logs.zeek %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -M -N -L info -z localhost:${ZEEK_PORT} 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: cat zeek/zeek-agent-system-logs.log | zeek-cut -cn host >tmp && mv tmp zeek/zeek-agent-system-logs.log 9 | # @TEST-EXEC: btest-diff zeek/zeek-agent-system-logs.log 10 | 11 | @load test-setup 12 | 13 | redef ZeekAgent_SystemLogs::query_interval = 1sec; 14 | 15 | # We only accept the 2nd write writer so that our output doesn't depend on 16 | # runtime duration (1st write is empty). 17 | global already_logged = 0; 18 | 19 | hook ZeekAgent_SystemLogs::log_policy(rec: any, id: Log::ID, 20 | filter: Log::Filter) 21 | { 22 | if ( ++already_logged != 2 ) 23 | break; 24 | } 25 | 26 | event do_terminate() 27 | { 28 | terminate(); 29 | } 30 | 31 | event ZeekAgentAPI::agent_hello_v1(ctx: ZeekAgent::Context, 32 | columns: ZeekAgentAPI::AgentHelloV1) 33 | { 34 | schedule 4secs { do_terminate() }; 35 | } 36 | -------------------------------------------------------------------------------- /tests/zeek/table/users.zeek: -------------------------------------------------------------------------------- 1 | # @TEST-DOC: Test the `users` table script end-to-end with mock data. 2 | # 3 | # @TEST-PORT: ZEEK_PORT 4 | # 5 | # @TEST-EXEC: btest-bg-run zeek zeek ${FRAMEWORK} ${PACKAGE}/table/users.zeek %INPUT 6 | # @TEST-EXEC: btest-bg-run agent zeek-agent -c ${CONFIG} -M -N -L info -z localhost:${ZEEK_PORT} 7 | # @TEST-EXEC: btest-bg-wait 30 8 | # @TEST-EXEC: cat zeek/zeek-agent-users.log | zeek-cut -cn host >tmp && mv tmp zeek/zeek-agent-users.log 9 | # @TEST-EXEC: btest-diff zeek/zeek-agent-users.log 10 | 11 | @load test-setup 12 | 13 | redef ZeekAgent_Users::subscription = ZeekAgent::SnapshotPlusDifferences; 14 | 15 | # We only accept the 1st write writer so that our output doesn't depend on 16 | # runtime duration. 17 | global already_logged = F; 18 | 19 | hook ZeekAgent_Users::log_policy(rec: any, id: Log::ID, filter: Log::Filter) 20 | { 21 | if ( already_logged ) 22 | break; 23 | else 24 | already_logged = T; 25 | } 26 | 27 | event do_terminate() 28 | { 29 | terminate(); 30 | } 31 | 32 | event ZeekAgentAPI::agent_hello_v1(ctx: ZeekAgent::Context, 33 | columns: ZeekAgentAPI::AgentHelloV1) 34 | { 35 | schedule 2secs { do_terminate() }; 36 | } 37 | -------------------------------------------------------------------------------- /vcpkg.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "main", 3 | "version-string": "latest", 4 | "dependencies": [ 5 | "pthreads", 6 | "openssl", 7 | "zlib" 8 | ] 9 | } 10 | --------------------------------------------------------------------------------