mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
78 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f41fbb124 | ||
|
|
ff0efe952b | ||
|
|
c711129ac9 | ||
|
|
a56f6c5447 | ||
|
|
fadc9b505b | ||
|
|
c7766d5993 | ||
|
|
341abf044b | ||
|
|
b1cf83a7e3 | ||
|
|
013b0b40af | ||
|
|
6093118ba1 | ||
|
|
6719d4c32a | ||
|
|
1ce4756771 | ||
|
|
9a7dcefb92 | ||
|
|
59382bc3ac | ||
|
|
0725fccc7f | ||
|
|
166c0420e5 | ||
|
|
f4ec2d51ab | ||
|
|
d54ec98bd5 | ||
|
|
bbeb653c77 | ||
|
|
52ffe5fc50 | ||
|
|
ffd77fef9d | ||
|
|
47648d2d5c | ||
|
|
208b4a6ebc | ||
|
|
7f86366672 | ||
|
|
9e7847740f | ||
|
|
348ff8c006 | ||
|
|
476cd21653 | ||
|
|
7bf11abca0 | ||
|
|
466546f548 | ||
|
|
73c18851f9 | ||
|
|
650f6fac97 | ||
|
|
9764f1bd69 | ||
|
|
688d560b62 | ||
|
|
b6af5ade60 | ||
|
|
08ce693096 | ||
|
|
85cfa641ce | ||
|
|
72ed5ee4f9 | ||
|
|
ecf7040b87 | ||
|
|
cedbc0da19 | ||
|
|
ef9348e6f5 | ||
|
|
b23b528082 | ||
|
|
587ac2ddc8 | ||
|
|
14729210d3 | ||
|
|
c3d29e3c4a | ||
|
|
4b0ad39413 | ||
|
|
e8b7fdff64 | ||
|
|
35d42d0134 | ||
|
|
a42dd6e120 | ||
|
|
53a2bbdd1e | ||
|
|
e417d8847f | ||
|
|
a23b67d5de | ||
|
|
7502bc247f | ||
|
|
cf4dab55e0 | ||
|
|
e43ad30d4e | ||
|
|
d8928ac320 | ||
|
|
ac1c9d8b05 | ||
|
|
9b184ed4fb | ||
|
|
67883c5200 | ||
|
|
8e1e155c23 | ||
|
|
fb6aa850b3 | ||
|
|
7f06b8c11a | ||
|
|
978b33e450 | ||
|
|
f00ad32edd | ||
|
|
7b0d2fdbb8 | ||
|
|
1672e52b7b | ||
|
|
6cca5ab27d | ||
|
|
7e4af5e337 | ||
|
|
5ea4431901 | ||
|
|
b53818f556 | ||
|
|
0f5e551345 | ||
|
|
51b0f7146d | ||
|
|
f5882b3146 | ||
|
|
59c146b4af | ||
|
|
aaa2b0e259 | ||
|
|
9c6fd0677f | ||
|
|
344a35aaf6 | ||
|
|
45e36fa501 | ||
|
|
d5a7c5f1fa |
@@ -1 +1 @@
|
||||
6.3.2
|
||||
7.0.0
|
||||
|
||||
7
.github/workflows/check-markdown.yml
vendored
7
.github/workflows/check-markdown.yml
vendored
@@ -10,8 +10,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: "Checkout Santa"
|
||||
uses: actions/checkout@61b9e3751b92087fd0b06925ba6dd6314e06f089 # ratchet:actions/checkout@master
|
||||
uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # ratchet:actions/checkout@v4
|
||||
- name: "Check for deadlinks"
|
||||
uses: gaurav-nelson/github-action-markdown-link-check@5c5dfc0ac2e225883c0e5f03a85311ec2830d368 # ratchet:gaurav-nelson/github-action-markdown-link-check@v1
|
||||
uses: lycheeverse/lychee-action@2b973e86fc7b1f6b36a93795fe2c9c6ae1118621 # ratchet:lycheeverse/lychee-action@v1
|
||||
with:
|
||||
fail: true
|
||||
- name: "Check for trailing whitespace and newlines"
|
||||
if: '!cancelled()'
|
||||
run: "! git grep -EIn $'[ \t]+$' -- ':(exclude)*.patch'"
|
||||
|
||||
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-11, macos-12, macos-13, macos-14]
|
||||
os: [macos-12, macos-13, macos-14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
|
||||
@@ -31,7 +31,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-11, macos-12, macos-13, macos-14]
|
||||
os: [macos-12, macos-13, macos-14]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
|
||||
|
||||
18
.github/workflows/e2e.yml
vendored
18
.github/workflows/e2e.yml
vendored
@@ -6,11 +6,29 @@ on:
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
update_vm:
|
||||
runs-on: e2e-host
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
|
||||
- name: Update VM
|
||||
env:
|
||||
GCS_KEY: ${{ secrets.GCS_SERVICE_ACCOUNT_KEY }}
|
||||
run: |
|
||||
export GOOGLE_APPLICATION_CREDENTIALS=/tmp/gcp.json
|
||||
echo "${GCS_KEY}" > ${GOOGLE_APPLICATION_CREDENTIALS}
|
||||
function cleanup {
|
||||
rm /tmp/gcp.json
|
||||
}
|
||||
trap cleanup EXIT
|
||||
python3 Testing/integration/actions/update_vm.py macOS_14.bundle.tar.gz
|
||||
|
||||
start_vm:
|
||||
runs-on: e2e-host
|
||||
steps:
|
||||
- uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # ratchet:actions/checkout@v3
|
||||
- name: Start VM
|
||||
env:
|
||||
RUNNER_REG_TOKEN: ${{ secrets.RUNNER_REG_TOKEN }}
|
||||
run: python3 Testing/integration/actions/start_vm.py macOS_14.bundle.tar.gz
|
||||
|
||||
integration:
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -2,7 +2,7 @@
|
||||
*.profraw
|
||||
*.provisionprofile
|
||||
bazel-*
|
||||
Pods
|
||||
MODULE.bazel.lock
|
||||
Santa.xcodeproj/*
|
||||
Santa.xcworkspace/*
|
||||
CoverageData/*
|
||||
|
||||
5
.pyink-config
Normal file
5
.pyink-config
Normal file
@@ -0,0 +1,5 @@
|
||||
[tool.pyink]
|
||||
pyink = true
|
||||
line-length = 80
|
||||
pyink-indentation = 2
|
||||
pyink-use-majority-quotes = true
|
||||
429
.pylintrc
429
.pylintrc
@@ -1,429 +0,0 @@
|
||||
# This Pylint rcfile contains a best-effort configuration to uphold the
|
||||
# best-practices and style described in the Google Python style guide:
|
||||
# https://google.github.io/styleguide/pyguide.html
|
||||
#
|
||||
# Its canonical open-source location is:
|
||||
# https://google.github.io/styleguide/pylintrc
|
||||
|
||||
[MASTER]
|
||||
|
||||
# Files or directories to be skipped. They should be base names, not paths.
|
||||
ignore=third_party
|
||||
|
||||
# Files or directories matching the regex patterns are skipped. The regex
|
||||
# matches against base names, not paths.
|
||||
ignore-patterns=
|
||||
|
||||
# Pickle collected data for later comparisons.
|
||||
persistent=no
|
||||
|
||||
# List of plugins (as comma separated values of python modules names) to load,
|
||||
# usually to register additional checkers.
|
||||
load-plugins=
|
||||
|
||||
# Use multiple processes to speed up Pylint.
|
||||
jobs=4
|
||||
|
||||
# Allow loading of arbitrary C extensions. Extensions are imported into the
|
||||
# active Python interpreter and may run arbitrary code.
|
||||
unsafe-load-any-extension=no
|
||||
|
||||
|
||||
[MESSAGES CONTROL]
|
||||
|
||||
# Only show warnings with the listed confidence levels. Leave empty to show
|
||||
# all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED
|
||||
confidence=
|
||||
|
||||
# Enable the message, report, category or checker with the given id(s). You can
|
||||
# either give multiple identifier separated by comma (,) or put this option
|
||||
# multiple time (only on the command line, not in the configuration file where
|
||||
# it should appear only once). See also the "--disable" option for examples.
|
||||
#enable=
|
||||
|
||||
# Disable the message, report, category or checker with the given id(s). You
|
||||
# can either give multiple identifiers separated by comma (,) or put this
|
||||
# option multiple times (only on the command line, not in the configuration
|
||||
# file where it should appear only once).You can also use "--disable=all" to
|
||||
# disable everything first and then reenable specific checks. For example, if
|
||||
# you want to run only the similarities checker, you can use "--disable=all
|
||||
# --enable=similarities". If you want to run only the classes checker, but have
|
||||
# no Warning level messages displayed, use"--disable=all --enable=classes
|
||||
# --disable=W"
|
||||
disable=abstract-method,
|
||||
apply-builtin,
|
||||
arguments-differ,
|
||||
attribute-defined-outside-init,
|
||||
backtick,
|
||||
bad-option-value,
|
||||
basestring-builtin,
|
||||
buffer-builtin,
|
||||
c-extension-no-member,
|
||||
consider-using-enumerate,
|
||||
cmp-builtin,
|
||||
cmp-method,
|
||||
coerce-builtin,
|
||||
coerce-method,
|
||||
delslice-method,
|
||||
div-method,
|
||||
duplicate-code,
|
||||
eq-without-hash,
|
||||
execfile-builtin,
|
||||
file-builtin,
|
||||
filter-builtin-not-iterating,
|
||||
fixme,
|
||||
getslice-method,
|
||||
global-statement,
|
||||
hex-method,
|
||||
idiv-method,
|
||||
implicit-str-concat,
|
||||
import-error,
|
||||
import-self,
|
||||
import-star-module-level,
|
||||
inconsistent-return-statements,
|
||||
input-builtin,
|
||||
intern-builtin,
|
||||
invalid-str-codec,
|
||||
locally-disabled,
|
||||
long-builtin,
|
||||
long-suffix,
|
||||
map-builtin-not-iterating,
|
||||
misplaced-comparison-constant,
|
||||
missing-function-docstring,
|
||||
metaclass-assignment,
|
||||
next-method-called,
|
||||
next-method-defined,
|
||||
no-absolute-import,
|
||||
no-else-break,
|
||||
no-else-continue,
|
||||
no-else-raise,
|
||||
no-else-return,
|
||||
no-init, # added
|
||||
no-member,
|
||||
no-name-in-module,
|
||||
no-self-use,
|
||||
nonzero-method,
|
||||
oct-method,
|
||||
old-division,
|
||||
old-ne-operator,
|
||||
old-octal-literal,
|
||||
old-raise-syntax,
|
||||
parameter-unpacking,
|
||||
print-statement,
|
||||
raising-string,
|
||||
range-builtin-not-iterating,
|
||||
raw_input-builtin,
|
||||
rdiv-method,
|
||||
reduce-builtin,
|
||||
relative-import,
|
||||
reload-builtin,
|
||||
round-builtin,
|
||||
setslice-method,
|
||||
signature-differs,
|
||||
standarderror-builtin,
|
||||
suppressed-message,
|
||||
sys-max-int,
|
||||
too-few-public-methods,
|
||||
too-many-ancestors,
|
||||
too-many-arguments,
|
||||
too-many-boolean-expressions,
|
||||
too-many-branches,
|
||||
too-many-instance-attributes,
|
||||
too-many-locals,
|
||||
too-many-nested-blocks,
|
||||
too-many-public-methods,
|
||||
too-many-return-statements,
|
||||
too-many-statements,
|
||||
trailing-newlines,
|
||||
unichr-builtin,
|
||||
unicode-builtin,
|
||||
unnecessary-pass,
|
||||
unpacking-in-except,
|
||||
useless-else-on-loop,
|
||||
useless-object-inheritance,
|
||||
useless-suppression,
|
||||
using-cmp-argument,
|
||||
wrong-import-order,
|
||||
xrange-builtin,
|
||||
zip-builtin-not-iterating,
|
||||
|
||||
|
||||
[REPORTS]
|
||||
|
||||
# Set the output format. Available formats are text, parseable, colorized, msvs
|
||||
# (visual studio) and html. You can also give a reporter class, eg
|
||||
# mypackage.mymodule.MyReporterClass.
|
||||
output-format=text
|
||||
|
||||
# Tells whether to display a full report or only the messages
|
||||
reports=no
|
||||
|
||||
# Python expression which should return a note less than 10 (10 is the highest
|
||||
# note). You have access to the variables errors warning, statement which
|
||||
# respectively contain the number of errors / warnings messages and the total
|
||||
# number of statements analyzed. This is used by the global evaluation report
|
||||
# (RP0004).
|
||||
evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10)
|
||||
|
||||
# Template used to display messages. This is a python new-style format string
|
||||
# used to format the message information. See doc for all details
|
||||
#msg-template=
|
||||
|
||||
|
||||
[BASIC]
|
||||
|
||||
# Good variable names which should always be accepted, separated by a comma
|
||||
good-names=main,_
|
||||
|
||||
# Bad variable names which should always be refused, separated by a comma
|
||||
bad-names=
|
||||
|
||||
# Colon-delimited sets of names that determine each other's naming style when
|
||||
# the name regexes allow several styles.
|
||||
name-group=
|
||||
|
||||
# Include a hint for the correct naming format with invalid-name
|
||||
include-naming-hint=no
|
||||
|
||||
# List of decorators that produce properties, such as abc.abstractproperty. Add
|
||||
# to this list to register other decorators that produce valid properties.
|
||||
property-classes=abc.abstractproperty,cached_property.cached_property,cached_property.threaded_cached_property,cached_property.cached_property_with_ttl,cached_property.threaded_cached_property_with_ttl
|
||||
|
||||
# Regular expression matching correct function names
|
||||
function-rgx=^(?:(?P<exempt>setUp|tearDown|setUpModule|tearDownModule)|(?P<camel_case>_?[A-Z][a-zA-Z0-9]*)|(?P<snake_case>_?[a-z][a-z0-9_]*))$
|
||||
|
||||
# Regular expression matching correct variable names
|
||||
variable-rgx=^[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct constant names
|
||||
const-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
|
||||
|
||||
# Regular expression matching correct attribute names
|
||||
attr-rgx=^_{0,2}[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct argument names
|
||||
argument-rgx=^[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct class attribute names
|
||||
class-attribute-rgx=^(_?[A-Z][A-Z0-9_]*|__[a-z0-9_]+__|_?[a-z][a-z0-9_]*)$
|
||||
|
||||
# Regular expression matching correct inline iteration names
|
||||
inlinevar-rgx=^[a-z][a-z0-9_]*$
|
||||
|
||||
# Regular expression matching correct class names
|
||||
class-rgx=^_?[A-Z][a-zA-Z0-9]*$
|
||||
|
||||
# Regular expression matching correct module names
|
||||
module-rgx=^(_?[a-z][a-z0-9_]*|__init__)$
|
||||
|
||||
# Regular expression matching correct method names
|
||||
method-rgx=(?x)^(?:(?P<exempt>_[a-z0-9_]+__|runTest|setUp|tearDown|setUpTestCase|tearDownTestCase|setupSelf|tearDownClass|setUpClass|(test|assert)_*[A-Z0-9][a-zA-Z0-9_]*|next)|(?P<camel_case>_{0,2}[A-Z][a-zA-Z0-9_]*)|(?P<snake_case>_{0,2}[a-z][a-z0-9_]*))$
|
||||
|
||||
# Regular expression which should only match function or class names that do
|
||||
# not require a docstring.
|
||||
no-docstring-rgx=(__.*__|main|test.*|.*test|.*Test)$
|
||||
|
||||
# Minimum line length for functions/classes that require docstrings, shorter
|
||||
# ones are exempt.
|
||||
docstring-min-length=10
|
||||
|
||||
|
||||
[TYPECHECK]
|
||||
|
||||
# List of decorators that produce context managers, such as
|
||||
# contextlib.contextmanager. Add to this list to register other decorators that
|
||||
# produce valid context managers.
|
||||
contextmanager-decorators=contextlib.contextmanager,contextlib2.contextmanager
|
||||
|
||||
# Tells whether missing members accessed in mixin class should be ignored. A
|
||||
# mixin class is detected if its name ends with "mixin" (case insensitive).
|
||||
ignore-mixin-members=yes
|
||||
|
||||
# List of module names for which member attributes should not be checked
|
||||
# (useful for modules/projects where namespaces are manipulated during runtime
|
||||
# and thus existing member attributes cannot be deduced by static analysis. It
|
||||
# supports qualified module names, as well as Unix pattern matching.
|
||||
ignored-modules=
|
||||
|
||||
# List of class names for which member attributes should not be checked (useful
|
||||
# for classes with dynamically set attributes). This supports the use of
|
||||
# qualified names.
|
||||
ignored-classes=optparse.Values,thread._local,_thread._local
|
||||
|
||||
# List of members which are set dynamically and missed by pylint inference
|
||||
# system, and so shouldn't trigger E1101 when accessed. Python regular
|
||||
# expressions are accepted.
|
||||
generated-members=
|
||||
|
||||
|
||||
[FORMAT]
|
||||
|
||||
# Maximum number of characters on a single line.
|
||||
max-line-length=80
|
||||
|
||||
# TODO(https://github.com/PyCQA/pylint/issues/3352): Direct pylint to exempt
|
||||
# lines made too long by directives to pytype.
|
||||
|
||||
# Regexp for a line that is allowed to be longer than the limit.
|
||||
ignore-long-lines=(?x)(
|
||||
^\s*(\#\ )?<?https?://\S+>?$|
|
||||
^\s*(from\s+\S+\s+)?import\s+.+$)
|
||||
|
||||
# Allow the body of an if to be on the same line as the test if there is no
|
||||
# else.
|
||||
single-line-if-stmt=yes
|
||||
|
||||
# Maximum number of lines in a module
|
||||
max-module-lines=99999
|
||||
|
||||
# String used as indentation unit. The internal Google style guide mandates 2
|
||||
# spaces. Google's externaly-published style guide says 4, consistent with
|
||||
# PEP 8. Here, we use 2 spaces, for conformity with many open-sourced Google
|
||||
# projects (like TensorFlow).
|
||||
indent-string=' '
|
||||
|
||||
# Number of spaces of indent required inside a hanging or continued line.
|
||||
indent-after-paren=4
|
||||
|
||||
# Expected format of line ending, e.g. empty (any line ending), LF or CRLF.
|
||||
expected-line-ending-format=
|
||||
|
||||
|
||||
[MISCELLANEOUS]
|
||||
|
||||
# List of note tags to take in consideration, separated by a comma.
|
||||
notes=TODO
|
||||
|
||||
|
||||
[STRING]
|
||||
|
||||
# This flag controls whether inconsistent-quotes generates a warning when the
|
||||
# character used as a quote delimiter is used inconsistently within a module.
|
||||
check-quote-consistency=yes
|
||||
|
||||
|
||||
[VARIABLES]
|
||||
|
||||
# Tells whether we should check for unused import in __init__ files.
|
||||
init-import=no
|
||||
|
||||
# A regular expression matching the name of dummy variables (i.e. expectedly
|
||||
# not used).
|
||||
dummy-variables-rgx=^\*{0,2}(_$|unused_|dummy_)
|
||||
|
||||
# List of additional names supposed to be defined in builtins. Remember that
|
||||
# you should avoid to define new builtins when possible.
|
||||
additional-builtins=
|
||||
|
||||
# List of strings which can identify a callback function by name. A callback
|
||||
# name must start or end with one of those strings.
|
||||
callbacks=cb_,_cb
|
||||
|
||||
# List of qualified module names which can have objects that can redefine
|
||||
# builtins.
|
||||
redefining-builtins-modules=six,six.moves,past.builtins,future.builtins,functools
|
||||
|
||||
|
||||
[LOGGING]
|
||||
|
||||
# Logging modules to check that the string format arguments are in logging
|
||||
# function parameter format
|
||||
logging-modules=logging,absl.logging,tensorflow.io.logging
|
||||
|
||||
|
||||
[SIMILARITIES]
|
||||
|
||||
# Minimum lines number of a similarity.
|
||||
min-similarity-lines=4
|
||||
|
||||
# Ignore comments when computing similarities.
|
||||
ignore-comments=yes
|
||||
|
||||
# Ignore docstrings when computing similarities.
|
||||
ignore-docstrings=yes
|
||||
|
||||
# Ignore imports when computing similarities.
|
||||
ignore-imports=no
|
||||
|
||||
|
||||
[SPELLING]
|
||||
|
||||
# Spelling dictionary name. Available dictionaries: none. To make it working
|
||||
# install python-enchant package.
|
||||
spelling-dict=
|
||||
|
||||
# List of comma separated words that should not be checked.
|
||||
spelling-ignore-words=
|
||||
|
||||
# A path to a file that contains private dictionary; one word per line.
|
||||
spelling-private-dict-file=
|
||||
|
||||
# Tells whether to store unknown words to indicated private dictionary in
|
||||
# --spelling-private-dict-file option instead of raising a message.
|
||||
spelling-store-unknown-words=no
|
||||
|
||||
|
||||
[IMPORTS]
|
||||
|
||||
# Deprecated modules which should not be used, separated by a comma
|
||||
deprecated-modules=regsub,
|
||||
TERMIOS,
|
||||
Bastion,
|
||||
rexec,
|
||||
sets
|
||||
|
||||
# Create a graph of every (i.e. internal and external) dependencies in the
|
||||
# given file (report RP0402 must not be disabled)
|
||||
import-graph=
|
||||
|
||||
# Create a graph of external dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
ext-import-graph=
|
||||
|
||||
# Create a graph of internal dependencies in the given file (report RP0402 must
|
||||
# not be disabled)
|
||||
int-import-graph=
|
||||
|
||||
# Force import order to recognize a module as part of the standard
|
||||
# compatibility libraries.
|
||||
known-standard-library=
|
||||
|
||||
# Force import order to recognize a module as part of a third party library.
|
||||
known-third-party=enchant, absl
|
||||
|
||||
# Analyse import fallback blocks. This can be used to support both Python 2 and
|
||||
# 3 compatible code, which means that the block might have code that exists
|
||||
# only in one or another interpreter, leading to false positives when analysed.
|
||||
analyse-fallback-blocks=no
|
||||
|
||||
|
||||
[CLASSES]
|
||||
|
||||
# List of method names used to declare (i.e. assign) instance attributes.
|
||||
defining-attr-methods=__init__,
|
||||
__new__,
|
||||
setUp
|
||||
|
||||
# List of member names, which should be excluded from the protected access
|
||||
# warning.
|
||||
exclude-protected=_asdict,
|
||||
_fields,
|
||||
_replace,
|
||||
_source,
|
||||
_make
|
||||
|
||||
# List of valid names for the first argument in a class method.
|
||||
valid-classmethod-first-arg=cls,
|
||||
class_
|
||||
|
||||
# List of valid names for the first argument in a metaclass class method.
|
||||
valid-metaclass-classmethod-first-arg=mcs
|
||||
|
||||
|
||||
[EXCEPTIONS]
|
||||
|
||||
# Exceptions that will emit a warning when being caught. Defaults to
|
||||
# "Exception"
|
||||
overgeneral-exceptions=StandardError,
|
||||
Exception,
|
||||
BaseException
|
||||
4
BUILD
4
BUILD
@@ -1,7 +1,9 @@
|
||||
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
|
||||
load("//:helper.bzl", "run_command")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
|
||||
@@ -53,11 +53,10 @@ readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Inf
|
||||
readonly SCRATCH=$(/usr/bin/mktemp -d "${TMPDIR}/santa-"XXXXXX)
|
||||
readonly APP_PKG_ROOT="${SCRATCH}/app_pkg_root"
|
||||
readonly APP_PKG_SCRIPTS="${SCRATCH}/pkg_scripts"
|
||||
readonly ENTITLEMENTS="${SCRATCH}/entitlements"
|
||||
|
||||
readonly SCRIPT_PATH="$(/usr/bin/dirname -- ${BASH_SOURCE[0]})"
|
||||
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}" "${APP_PKG_SCRIPTS}" "${ENTITLEMENTS}"
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}" "${APP_PKG_SCRIPTS}"
|
||||
|
||||
readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
|
||||
readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
|
||||
@@ -65,19 +64,9 @@ readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
|
||||
# Sign all of binaries/bundles. Maintain inside-out ordering where necessary
|
||||
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SANTASS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
EN="${ENTITLEMENTS}/${BN}.entitlements"
|
||||
|
||||
echo "extracting ${BN} entitlements"
|
||||
/usr/bin/codesign -d --entitlements "${EN}" "${ARTIFACT}"
|
||||
if [[ -s "${EN}" ]]; then
|
||||
EN="--entitlements ${EN}"
|
||||
else
|
||||
EN=""
|
||||
fi
|
||||
|
||||
echo "codesigning ${BN}"
|
||||
/usr/bin/codesign --sign "${SIGNING_IDENTITY}" --keychain "${SIGNING_KEYCHAIN}" \
|
||||
${EN} --timestamp --force --generate-entitlement-der \
|
||||
--preserve-metadata=entitlements --timestamp --force --generate-entitlement-der \
|
||||
--options library,kill,runtime "${ARTIFACT}"
|
||||
done
|
||||
|
||||
|
||||
56
MODULE.bazel
Normal file
56
MODULE.bazel
Normal file
@@ -0,0 +1,56 @@
|
||||
module(name = "santa")
|
||||
|
||||
bazel_dep(name = "apple_support", version = "1.15.1", repo_name = "build_bazel_apple_support")
|
||||
bazel_dep(name = "abseil-cpp", version = "20240116.2", repo_name = "com_google_absl")
|
||||
bazel_dep(name = "rules_python", version = "0.33.2")
|
||||
bazel_dep(name = "rules_cc", version = "0.0.9")
|
||||
bazel_dep(name = "rules_apple", version = "3.8.0", repo_name = "build_bazel_rules_apple")
|
||||
bazel_dep(name = "rules_swift", version = "2.0.0-rc1", repo_name = "build_bazel_rules_swift")
|
||||
bazel_dep(name = "rules_fuzzing", version = "0.5.2")
|
||||
bazel_dep(name = "protobuf", version = "27.2", repo_name = "com_google_protobuf")
|
||||
bazel_dep(name = "googletest", version = "1.14.0.bcr.1", repo_name = "com_google_googletest")
|
||||
|
||||
# MOLCertificate
|
||||
bazel_dep(name = "molcertificate", version = "2.1", repo_name = "MOLCertificate")
|
||||
git_override(
|
||||
module_name = "molcertificate",
|
||||
commit = "34f0ccf68a34a07cc636ada89057c529f90bec3a",
|
||||
remote = "https://github.com/google/macops-molcertificate.git",
|
||||
)
|
||||
|
||||
# MOLAuthenticatingURLSession
|
||||
bazel_dep(name = "molauthenticatingurlsession", version = "3.0", repo_name = "MOLAuthenticatingURLSession")
|
||||
git_override(
|
||||
module_name = "molauthenticatingurlsession",
|
||||
commit = "0a50a67f29d635a4012981714c1dedef9ac25fe6",
|
||||
remote = "https://github.com/google/macops-molauthenticatingurlsession.git",
|
||||
)
|
||||
|
||||
# MOLCodesignChecker
|
||||
bazel_dep(name = "molcodesignchecker", version = "3.0", repo_name = "MOLCodesignChecker")
|
||||
git_override(
|
||||
module_name = "molcodesignchecker",
|
||||
commit = "5060bcc8baa90bae3b0ca705d14850328bbbec53",
|
||||
remote = "https://github.com/google/macops-molcodesignchecker.git",
|
||||
)
|
||||
|
||||
# MOLXPCConnection
|
||||
bazel_dep(name = "molxpcconnection", version = "2.1", repo_name = "MOLXPCConnection")
|
||||
git_override(
|
||||
module_name = "molxpcconnection",
|
||||
commit = "da816dc49becac96d941ef6a5c4153ed39d1fe7c",
|
||||
remote = "https://github.com/russellhancox/macops-molxpcconnection.git",
|
||||
)
|
||||
|
||||
# FMDB
|
||||
non_module_deps = use_extension("//:non_module_deps.bzl", "non_module_deps")
|
||||
use_repo(non_module_deps, "FMDB")
|
||||
use_repo(non_module_deps, "OCMock")
|
||||
|
||||
# Hedron's Compile Commands Extractor
|
||||
bazel_dep(name = "hedron_compile_commands", dev_dependency = True)
|
||||
git_override(
|
||||
module_name = "hedron_compile_commands",
|
||||
commit = "0e990032f3c5a866e72615cf67e5ce22186dcb97",
|
||||
remote = "https://github.com/hedronvision/bazel-compile-commands-extractor.git",
|
||||
)
|
||||
@@ -21,7 +21,7 @@ It is named Santa because it keeps track of binaries that are naughty or nice.
|
||||
# Docs
|
||||
|
||||
The Santa docs are stored in the
|
||||
[Docs](https://github.com/google/santa/blob/main/docs) directory and published
|
||||
[Docs](https://github.com/google/santa/blob/main/docs) directory and are published
|
||||
at https://santa.dev.
|
||||
|
||||
The docs include deployment options, details on how parts of Santa work and
|
||||
|
||||
@@ -150,6 +150,16 @@ objc_library(
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SigningIDHelpers",
|
||||
srcs = ["SigningIDHelpers.m"],
|
||||
hdrs = ["SigningIDHelpers.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTBlockMessage",
|
||||
srcs = ["SNTBlockMessage.m"],
|
||||
@@ -210,7 +220,7 @@ objc_library(
|
||||
],
|
||||
deps = [
|
||||
":CertificateHelpers",
|
||||
"@MOLCertificate",
|
||||
":SNTStoredEvent",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -477,8 +487,11 @@ santa_unit_test(
|
||||
santa_unit_test(
|
||||
name = "SNTBlockMessageTest",
|
||||
srcs = ["SNTBlockMessageTest.m"],
|
||||
sdk_frameworks = [
|
||||
"AppKit",
|
||||
],
|
||||
deps = [
|
||||
":SNTBlockMessage",
|
||||
":SNTBlockMessage_SantaGUI",
|
||||
":SNTConfigurator",
|
||||
":SNTFileAccessEvent",
|
||||
":SNTStoredEvent",
|
||||
@@ -524,6 +537,7 @@ objc_library(
|
||||
"bsm",
|
||||
],
|
||||
deps = [
|
||||
":Platform",
|
||||
":SystemResources",
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
|
||||
@@ -17,13 +17,6 @@
|
||||
|
||||
#include <Availability.h>
|
||||
|
||||
#if defined(MAC_OS_VERSION_12_0) && \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_12_0
|
||||
#define HAVE_MACOS_12 1
|
||||
#else
|
||||
#define HAVE_MACOS_12 0
|
||||
#endif
|
||||
|
||||
#if defined(MAC_OS_VERSION_13_0) && \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_13_0
|
||||
#define HAVE_MACOS_13 1
|
||||
@@ -31,4 +24,18 @@
|
||||
#define HAVE_MACOS_13 0
|
||||
#endif
|
||||
|
||||
#if defined(MAC_OS_VERSION_14_0) && \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_14_0
|
||||
#define HAVE_MACOS_14 1
|
||||
#else
|
||||
#define HAVE_MACOS_14 0
|
||||
#endif
|
||||
|
||||
#if defined(MAC_OS_VERSION_15_0) && \
|
||||
MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_VERSION_15_0
|
||||
#define HAVE_MACOS_15 1
|
||||
#else
|
||||
#define HAVE_MACOS_15 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#define DEBUG_LOG(format, ...) // NOP
|
||||
#endif
|
||||
|
||||
namespace santa::common {
|
||||
namespace santa {
|
||||
|
||||
template <typename ValueT>
|
||||
class PrefixTree {
|
||||
@@ -125,7 +125,10 @@ class PrefixTree {
|
||||
return false;
|
||||
}
|
||||
|
||||
cur_byte = (uint8_t) * ++p;
|
||||
// Disabling clang format due to local/remote version differences.
|
||||
// clang-format off
|
||||
cur_byte = (uint8_t)*++p;
|
||||
// clang-format on
|
||||
} while (*p);
|
||||
|
||||
node->node_type_ = node_type;
|
||||
@@ -297,6 +300,6 @@ class PrefixTree {
|
||||
absl::Mutex lock_;
|
||||
};
|
||||
|
||||
} // namespace santa::common
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
#define SANTA_PREFIX_TREE_DEBUG 1
|
||||
#include "Source/common/PrefixTree.h"
|
||||
|
||||
using santa::common::PrefixTree;
|
||||
using santa::PrefixTree;
|
||||
|
||||
@interface PrefixTreeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
static id ValueOrNull(id value) {
|
||||
return value ?: [NSNull null];
|
||||
}
|
||||
|
||||
@implementation SNTBlockMessage
|
||||
|
||||
+ (NSAttributedString *)formatMessage:(NSString *)message {
|
||||
@@ -52,7 +56,11 @@
|
||||
|
||||
#ifdef SANTAGUI
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
|
||||
NSDictionary *options = @{
|
||||
NSDocumentTypeDocumentAttribute : NSHTMLTextDocumentType,
|
||||
NSCharacterEncodingDocumentAttribute : @(NSUTF8StringEncoding),
|
||||
};
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData options:options documentAttributes:NULL];
|
||||
#else
|
||||
NSString *strippedHTML = [self stringFromHTML:fullHTML];
|
||||
if (!strippedHTML) {
|
||||
@@ -123,35 +131,79 @@
|
||||
}
|
||||
|
||||
+ (NSString *)replaceFormatString:(NSString *)str
|
||||
withDict:(NSDictionary<NSString *, NSString * (^)()> *)replacements {
|
||||
withDict:(NSDictionary<NSString *, NSString *> *)replacements {
|
||||
__block NSString *formatStr = str;
|
||||
|
||||
[replacements
|
||||
enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString * (^computeValue)(), BOOL *stop) {
|
||||
NSString *value = computeValue();
|
||||
if (value) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:key withString:value];
|
||||
}
|
||||
}];
|
||||
[replacements enumerateKeysAndObjectsUsingBlock:^(NSString *key, NSString *value, BOOL *stop) {
|
||||
if ((id)value != [NSNull null]) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:key withString:value];
|
||||
}
|
||||
}];
|
||||
|
||||
return formatStr;
|
||||
}
|
||||
|
||||
// Returns either the generated URL for the passed in event, or an NSURL from the passed in custom
|
||||
// URL string. If the custom URL string is the string "null", nil will be returned. If no custom
|
||||
// URL is passed and there is no configured EventDetailURL template, nil will be returned.
|
||||
// The following "format strings" will be replaced in the URL, if they are present:
|
||||
//
|
||||
// The following "format strings" will be replaced in the URL provided by
|
||||
// `+eventDetailURLForEvent:customURL:templateMapping:`.
|
||||
//
|
||||
// %file_identifier% - The SHA-256 of the binary being executed.
|
||||
// %bundle_or_file_identifier% - The hash of the bundle containing this file or the file itself,
|
||||
// if no bundle hash is present.
|
||||
// %file_bundle_id% - The bundle id of the binary, if any.
|
||||
// %team_id% - The Team ID if present in the signature information.
|
||||
// %signing_id% - The Signing ID if present in the signature information.
|
||||
// %cdhash% - If signed, the CDHash.
|
||||
// %username% - The executing user's name.
|
||||
// %machine_id% - The configured machine ID for this host.
|
||||
// %hostname% - The machine's FQDN.
|
||||
// %uuid% - The machine's UUID.
|
||||
// %serial% - The machine's serial number.
|
||||
//
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event customURL:(NSString *)url {
|
||||
+ (NSDictionary *)eventDetailTemplateMappingForEvent:(SNTStoredEvent *)event {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
return @{
|
||||
@"%file_sha%" : ValueOrNull(event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil),
|
||||
@"%file_identifier%" : ValueOrNull(event.fileSHA256),
|
||||
@"%bundle_or_file_identifier%" :
|
||||
ValueOrNull(event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil),
|
||||
@"%username%" : ValueOrNull(event.executingUser),
|
||||
@"%file_bundle_id%" : ValueOrNull(event.fileBundleID),
|
||||
@"%team_id%" : ValueOrNull(event.teamID),
|
||||
@"%signing_id%" : ValueOrNull(event.signingID),
|
||||
@"%cdhash%" : ValueOrNull(event.cdhash),
|
||||
@"%machine_id%" : ValueOrNull(config.machineID),
|
||||
@"%hostname%" : ValueOrNull([SNTSystemInfo longHostname]),
|
||||
@"%uuid%" : ValueOrNull([SNTSystemInfo hardwareUUID]),
|
||||
@"%serial%" : ValueOrNull([SNTSystemInfo serialNumber]),
|
||||
};
|
||||
}
|
||||
|
||||
//
|
||||
// Everything from `+eventDetailTemplateMappingForEvent:` with the following file access
|
||||
// specific templates.
|
||||
//
|
||||
// %rule_version% - The version of the rule that was violated.
|
||||
// %rule_name% - The name of the rule that was violated.
|
||||
// %accessed_path% - The path accessed by the binary.
|
||||
//
|
||||
+ (NSDictionary *)fileAccessEventDetailTemplateMappingForEvent:(SNTFileAccessEvent *)event {
|
||||
NSMutableDictionary *d = [self eventDetailTemplateMappingForEvent:event].mutableCopy;
|
||||
[d addEntriesFromDictionary:@{
|
||||
@"%rule_version%" : ValueOrNull(event.ruleVersion),
|
||||
@"%rule_name%" : ValueOrNull(event.ruleName),
|
||||
@"%accessed_path%" : ValueOrNull(event.accessedPath),
|
||||
}];
|
||||
return d;
|
||||
}
|
||||
|
||||
// Returns either the generated URL for the passed in event, or an NSURL from the passed in custom
|
||||
// URL string. If the custom URL string is the string "null", nil will be returned. If no custom
|
||||
// URL is passed and there is no configured EventDetailURL template, nil will be returned.
|
||||
// The "format strings" in `templateMapping` will be replaced in the URL, if they are present.
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event
|
||||
customURL:(NSString *)url
|
||||
templateMapping:(NSDictionary *)templateMapping {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSString *formatStr = url;
|
||||
@@ -166,26 +218,7 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Disabling clang-format. See comment in `eventDetailURLForFileAccessEvent:customURL:`
|
||||
// clang-format off
|
||||
NSDictionary<NSString *, NSString * (^)()> *kvReplacements =
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
// This key is deprecated, use %file_identifier% or %bundle_or_file_identifier%
|
||||
^{ return event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil; },
|
||||
@"%file_sha%",
|
||||
^{ return event.fileSHA256; }, @"%file_identifier%",
|
||||
^{ return event.fileSHA256 ? event.fileBundleHash ?: event.fileSHA256 : nil; },
|
||||
@"%bundle_or_file_identifier%",
|
||||
^{ return event.executingUser; }, @"%username%",
|
||||
^{ return config.machineID; }, @"%machine_id%",
|
||||
^{ return [SNTSystemInfo longHostname]; }, @"%hostname%",
|
||||
^{ return [SNTSystemInfo hardwareUUID]; }, @"%uuid%",
|
||||
^{ return [SNTSystemInfo serialNumber]; }, @"%serial%",
|
||||
nil];
|
||||
// clang-format on
|
||||
|
||||
formatStr = [SNTBlockMessage replaceFormatString:formatStr withDict:kvReplacements];
|
||||
|
||||
formatStr = [SNTBlockMessage replaceFormatString:formatStr withDict:templateMapping];
|
||||
NSURL *u = [NSURL URLWithString:formatStr];
|
||||
if (!u) {
|
||||
LOGW(@"Unable to generate event detail URL for string '%@'", formatStr);
|
||||
@@ -194,55 +227,16 @@
|
||||
return u;
|
||||
}
|
||||
|
||||
// Returns either the generated URL for the passed in event, or an NSURL from the passed in custom
|
||||
// URL string. If the custom URL string is the string "null", nil will be returned. If no custom
|
||||
// URL is passed and there is no configured EventDetailURL template, nil will be returned.
|
||||
// The following "format strings" will be replaced in the URL, if they are present:
|
||||
//
|
||||
// %rule_version% - The version of the rule that was violated.
|
||||
// %rule_name% - The name of the rule that was violated.
|
||||
// %file_identifier% - The SHA-256 of the binary being executed.
|
||||
// %accessed_path% - The path accessed by the binary.
|
||||
// %username% - The executing user's name.
|
||||
// %machine_id% - The configured machine ID for this host.
|
||||
// %hostname% - The machine's FQDN.
|
||||
// %uuid% - The machine's UUID.
|
||||
// %serial% - The machine's serial number.
|
||||
//
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event customURL:(NSString *)url {
|
||||
return [self eventDetailURLForEvent:event
|
||||
customURL:url
|
||||
templateMapping:[self eventDetailTemplateMappingForEvent:event]];
|
||||
}
|
||||
|
||||
+ (NSURL *)eventDetailURLForFileAccessEvent:(SNTFileAccessEvent *)event customURL:(NSString *)url {
|
||||
if (!url.length || [url isEqualToString:@"null"]) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
// Clang format goes wild here. If you use the container literal syntax `@{}` with a block value
|
||||
// type, it seems to break the clang format on/off functionality and breaks formatting for the
|
||||
// remainder of the file.
|
||||
// Using `dictionaryWithObjectsAndKeys` and disabling clang format as a workaround.
|
||||
// clang-format off
|
||||
NSDictionary<NSString *, NSString * (^)()> *kvReplacements =
|
||||
[NSDictionary dictionaryWithObjectsAndKeys:
|
||||
^{ return event.ruleVersion; }, @"%rule_version%",
|
||||
^{ return event.ruleName; }, @"%rule_name%",
|
||||
^{ return event.fileSHA256; }, @"%file_identifier%",
|
||||
^{ return event.accessedPath; }, @"%accessed_path%",
|
||||
^{ return event.executingUser; }, @"%username%",
|
||||
^{ return config.machineID; }, @"%machine_id%",
|
||||
^{ return [SNTSystemInfo longHostname]; }, @"%hostname%",
|
||||
^{ return [SNTSystemInfo hardwareUUID]; }, @"%uuid%",
|
||||
^{ return [SNTSystemInfo serialNumber]; }, @"%serial%",
|
||||
nil];
|
||||
// clang-format on
|
||||
|
||||
NSString *formatStr = [SNTBlockMessage replaceFormatString:url withDict:kvReplacements];
|
||||
|
||||
NSURL *u = [NSURL URLWithString:formatStr];
|
||||
if (!u) {
|
||||
LOGW(@"Unable to generate event detail URL for string '%@'", formatStr);
|
||||
}
|
||||
|
||||
return u;
|
||||
return [self eventDetailURLForEvent:event
|
||||
customURL:url
|
||||
templateMapping:[self fileAccessEventDetailTemplateMappingForEvent:event]];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -39,18 +39,30 @@
|
||||
OCMStub([self.mockSystemInfo serialNumber]).andReturn(@"my_s");
|
||||
}
|
||||
|
||||
- (void)testFormatMessage {
|
||||
NSString *input = @"Testing with somé Ünicode çharacters";
|
||||
NSAttributedString *got = [SNTBlockMessage formatMessage:input];
|
||||
XCTAssertEqualObjects([got string], input);
|
||||
}
|
||||
|
||||
- (void)testEventDetailURLForEvent {
|
||||
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
|
||||
|
||||
se.fileSHA256 = @"my_fi";
|
||||
se.executingUser = @"my_un";
|
||||
se.fileBundleID = @"s.n.t";
|
||||
se.cdhash = @"abc";
|
||||
se.teamID = @"SNT";
|
||||
se.signingID = @"SNT:s.n.t";
|
||||
|
||||
NSString *url = @"http://"
|
||||
@"localhost?fs=%file_sha%&fi=%file_identifier%&bfi=%bundle_or_file_identifier%&"
|
||||
@"fbid=%file_bundle_id%&ti=%team_id%&si=%signing_id%&ch=%cdhash%&"
|
||||
@"un=%username%&mid=%machine_id%&hn=%hostname%&u=%uuid%&s=%serial%";
|
||||
NSString *wantUrl =
|
||||
@"http://"
|
||||
@"localhost?fs=my_fi&fi=my_fi&bfi=my_fi&bfi=my_fi&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
|
||||
NSString *wantUrl = @"http://"
|
||||
@"localhost?fs=my_fi&fi=my_fi&bfi=my_fi&"
|
||||
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
|
||||
@"un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
|
||||
|
||||
NSURL *gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];
|
||||
|
||||
@@ -58,7 +70,9 @@
|
||||
se.fileBundleHash = @"my_fbh";
|
||||
|
||||
wantUrl = @"http://"
|
||||
@"localhost?fs=my_fbh&fi=my_fi&bfi=my_fbh&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
|
||||
@"localhost?fs=my_fbh&fi=my_fi&bfi=my_fbh&"
|
||||
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
|
||||
@"un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
|
||||
|
||||
gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];
|
||||
|
||||
@@ -74,15 +88,22 @@
|
||||
fae.ruleVersion = @"my_rv";
|
||||
fae.ruleName = @"my_rn";
|
||||
fae.fileSHA256 = @"my_fi";
|
||||
fae.fileBundleID = @"s.n.t";
|
||||
fae.cdhash = @"abc";
|
||||
fae.teamID = @"SNT";
|
||||
fae.signingID = @"SNT:s.n.t";
|
||||
fae.accessedPath = @"my_ap";
|
||||
fae.executingUser = @"my_un";
|
||||
|
||||
NSString *url = @"http://"
|
||||
@"localhost?rv=%rule_version%&rn=%rule_name%&fi=%file_identifier%&ap=%accessed_"
|
||||
@"path%&un=%username%&mid=%machine_id%&hn=%hostname%&u=%uuid%&s=%serial%";
|
||||
NSString *wantUrl =
|
||||
NSString *url =
|
||||
@"http://"
|
||||
@"localhost?rv=my_rv&rn=my_rn&fi=my_fi&ap=my_ap&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
|
||||
@"localhost?rv=%rule_version%&rn=%rule_name%&fi=%file_identifier%&"
|
||||
@"fbid=%file_bundle_id%&ti=%team_id%&si=%signing_id%&ch=%cdhash%&"
|
||||
@"ap=%accessed_path%&un=%username%&mid=%machine_id%&hn=%hostname%&u=%uuid%&s=%serial%";
|
||||
NSString *wantUrl = @"http://"
|
||||
@"localhost?rv=my_rv&rn=my_rn&fi=my_fi&"
|
||||
@"fbid=s.n.t&ti=SNT&si=SNT:s.n.t&ch=abc&"
|
||||
@"ap=my_ap&un=my_un&mid=my_mid&hn=my_hn&u=my_u&s=my_s";
|
||||
|
||||
NSURL *gotUrl = [SNTBlockMessage eventDetailURLForFileAccessEvent:fae customURL:url];
|
||||
|
||||
@@ -92,4 +113,17 @@
|
||||
XCTAssertNil([SNTBlockMessage eventDetailURLForFileAccessEvent:fae customURL:@"null"]);
|
||||
}
|
||||
|
||||
- (void)testEventDetailURLMissingDetails {
|
||||
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
|
||||
|
||||
se.fileSHA256 = @"my_fi";
|
||||
|
||||
NSString *url = @"http://localhost?fi=%file_identifier%";
|
||||
NSString *wantUrl = @"http://localhost?fi=my_fi";
|
||||
|
||||
NSURL *gotUrl = [SNTBlockMessage eventDetailURLForEvent:se customURL:url];
|
||||
|
||||
XCTAssertEqualObjects(gotUrl.absoluteString, wantUrl);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -182,6 +182,7 @@ typedef NS_ENUM(NSInteger, SNTRuleCleanup) {
|
||||
};
|
||||
|
||||
#ifdef __cplusplus
|
||||
|
||||
enum class FileAccessPolicyDecision {
|
||||
kNoPolicy,
|
||||
kDenied,
|
||||
@@ -190,6 +191,19 @@ enum class FileAccessPolicyDecision {
|
||||
kAllowedReadAccess,
|
||||
kAllowedAuditOnly,
|
||||
};
|
||||
|
||||
enum class StatChangeStep {
|
||||
kNoChange = 0,
|
||||
kMessageCreate,
|
||||
kCodesignValidation,
|
||||
};
|
||||
|
||||
enum class StatResult {
|
||||
kOK = 0,
|
||||
kStatError,
|
||||
kDevnoInodeMismatch,
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
static const char *kSantaDPath =
|
||||
|
||||
@@ -347,6 +347,11 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventDetailText;
|
||||
|
||||
///
|
||||
/// This string represents the text to show on the "Dismiss" button in the UI instead of "Dismiss".
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *dismissText;
|
||||
|
||||
///
|
||||
/// In lockdown mode this is the message shown to the user when an unknown binary
|
||||
/// is blocked. If this message is not configured, a reasonable default is provided.
|
||||
@@ -393,11 +398,17 @@
|
||||
///
|
||||
@property(readonly, nonatomic) NSURL *syncBaseURL;
|
||||
|
||||
///
|
||||
/// If enabled, syncing will use binary protobufs for transfer instead
|
||||
/// of JSON. Defaults to NO.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL syncEnableProtoTransfer;
|
||||
|
||||
///
|
||||
/// Proxy settings for syncing.
|
||||
/// This dictionary is passed directly to NSURLSession. The allowed keys
|
||||
/// are loosely documented at
|
||||
/// https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants.
|
||||
/// https://developer.apple.com/documentation/cfnetwork/global-proxy-settings-constants.
|
||||
///
|
||||
@property(readonly, nonatomic) NSDictionary *syncProxyConfig;
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ static NSString *const kMobileConfigDomain = @"com.google.santa";
|
||||
/// The keys managed by a mobileconfig.
|
||||
static NSString *const kStaticRules = @"StaticRules";
|
||||
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString *const kSyncEnableProtoTransfer = @"SyncEnableProtoTransfer";
|
||||
static NSString *const kSyncProxyConfigKey = @"SyncProxyConfiguration";
|
||||
static NSString *const kSyncExtraHeadersKey = @"SyncExtraHeaders";
|
||||
static NSString *const kSyncEnableCleanSyncEventUpload = @"SyncEnableCleanSyncEventUpload";
|
||||
@@ -96,6 +97,7 @@ static NSString *const kAboutTextKey = @"AboutText";
|
||||
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
|
||||
static NSString *const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString *const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kDismissTextKey = @"DismissText";
|
||||
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
|
||||
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
|
||||
static NSString *const kBannedUSBBlockMessage = @"BannedUSBBlockMessage";
|
||||
@@ -226,6 +228,7 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
|
||||
kMoreInfoURLKey : string,
|
||||
kEventDetailURLKey : string,
|
||||
kEventDetailTextKey : string,
|
||||
kDismissTextKey : string,
|
||||
kUnknownBlockMessage : string,
|
||||
kBannedBlockMessage : string,
|
||||
kBannedUSBBlockMessage : string,
|
||||
@@ -234,6 +237,7 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
|
||||
kModeNotificationLockdown : string,
|
||||
kStaticRules : array,
|
||||
kSyncBaseURLKey : string,
|
||||
kSyncEnableProtoTransfer : number,
|
||||
kSyncEnableCleanSyncEventUpload : number,
|
||||
kSyncProxyConfigKey : dictionary,
|
||||
kSyncExtraHeadersKey : dictionary,
|
||||
@@ -371,6 +375,10 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncEnableProtoTransfer {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingSyncExtraHeaders {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -403,6 +411,10 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingDismissText {
|
||||
return [self configStateSet];
|
||||
}
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingUnknownBlockMessage {
|
||||
return [self configStateSet];
|
||||
}
|
||||
@@ -719,6 +731,11 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
|
||||
return url;
|
||||
}
|
||||
|
||||
- (BOOL)syncEnableProtoTransfer {
|
||||
NSNumber *number = self.configState[kSyncEnableProtoTransfer];
|
||||
return number ? [number boolValue] : NO;
|
||||
}
|
||||
|
||||
- (NSDictionary *)syncProxyConfig {
|
||||
return self.configState[kSyncProxyConfigKey];
|
||||
}
|
||||
@@ -763,6 +780,10 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
|
||||
return self.configState[kEventDetailTextKey];
|
||||
}
|
||||
|
||||
- (NSString *)dismissText {
|
||||
return self.configState[kDismissTextKey];
|
||||
}
|
||||
|
||||
- (NSString *)unknownBlockMessage {
|
||||
return self.configState[kUnknownBlockMessage];
|
||||
}
|
||||
@@ -1048,7 +1069,8 @@ static NSString *const kSyncTypeRequired = @"SyncTypeRequired";
|
||||
}
|
||||
}
|
||||
|
||||
if ([action isEqualToString:@"auditonly"]) {
|
||||
// Note: `auditonly` without an underscore is a deprecated, but still accepted form.
|
||||
if ([action isEqualToString:@"audit_only"] || [action isEqualToString:@"auditonly"]) {
|
||||
return SNTOverrideFileAccessActionAuditOnly;
|
||||
} else if ([action isEqualToString:@"disable"]) {
|
||||
return SNTOverrideFileAccessActionDiable;
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import <MOLCertificate/MOLCertificate.h>
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
|
||||
///
|
||||
/// Represents an event stored in the database.
|
||||
///
|
||||
@interface SNTFileAccessEvent : NSObject <NSSecureCoding>
|
||||
@interface SNTFileAccessEvent : SNTStoredEvent <NSSecureCoding>
|
||||
|
||||
///
|
||||
/// The watched path that was accessed
|
||||
@@ -32,57 +32,11 @@
|
||||
@property NSString *ruleVersion;
|
||||
@property NSString *ruleName;
|
||||
|
||||
///
|
||||
/// The SHA256 of the process that accessed the path
|
||||
///
|
||||
@property NSString *fileSHA256;
|
||||
|
||||
///
|
||||
/// The path of the process that accessed the watched path
|
||||
///
|
||||
@property NSString *filePath;
|
||||
|
||||
///
|
||||
/// If the process is part of a bundle, the name of the application
|
||||
///
|
||||
@property NSString *application;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is the Team ID if present in the signature information.
|
||||
///
|
||||
@property NSString *teamID;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is the Signing ID if present in the signature information.
|
||||
///
|
||||
@property NSString *signingID;
|
||||
|
||||
///
|
||||
/// The user who executed the binary.
|
||||
///
|
||||
@property NSString *executingUser;
|
||||
|
||||
///
|
||||
/// The process ID of the binary being executed.
|
||||
///
|
||||
@property NSNumber *pid;
|
||||
|
||||
///
|
||||
/// The parent process ID of the binary being executed.
|
||||
///
|
||||
@property NSNumber *ppid;
|
||||
|
||||
///
|
||||
/// The name of the parent process.
|
||||
///
|
||||
@property NSString *parentName;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is an NSArray of MOLCertificate's
|
||||
/// representing the signing chain.
|
||||
///
|
||||
@property NSArray<MOLCertificate *> *signingChain;
|
||||
|
||||
///
|
||||
/// A string representing the publisher based on the signingChain
|
||||
///
|
||||
|
||||
@@ -48,35 +48,20 @@
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
[super encodeWithCoder:coder];
|
||||
ENCODE(accessedPath);
|
||||
ENCODE(ruleVersion);
|
||||
ENCODE(ruleName);
|
||||
ENCODE(fileSHA256);
|
||||
ENCODE(filePath);
|
||||
ENCODE(application);
|
||||
ENCODE(teamID);
|
||||
ENCODE(teamID);
|
||||
ENCODE(pid);
|
||||
ENCODE(ppid);
|
||||
ENCODE(parentName);
|
||||
ENCODE(signingChain);
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super init];
|
||||
self = [super initWithCoder:decoder];
|
||||
if (self) {
|
||||
DECODE(accessedPath, NSString);
|
||||
DECODE(ruleVersion, NSString);
|
||||
DECODE(ruleName, NSString);
|
||||
DECODE(fileSHA256, NSString);
|
||||
DECODE(filePath, NSString);
|
||||
DECODE(application, NSString);
|
||||
DECODE(teamID, NSString);
|
||||
DECODE(teamID, NSString);
|
||||
DECODE(pid, NSNumber);
|
||||
DECODE(ppid, NSNumber);
|
||||
DECODE(parentName, NSString);
|
||||
DECODEARRAY(signingChain, MOLCertificate);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -423,8 +423,14 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
return self.infoDict;
|
||||
}
|
||||
|
||||
d = self.bundle.infoDictionary;
|
||||
if (d) {
|
||||
// `-[NSBundle infoDictionary]` is heavily cached, changes to the Info.plist are not realized.
|
||||
// Use `CFBundleCopyInfoDictionaryInDirectory` instead, which does not appear to cache.
|
||||
NSString *bundlePath = [self bundlePath];
|
||||
if (bundlePath.length) {
|
||||
d = CFBridgingRelease(CFBundleCopyInfoDictionaryInDirectory(
|
||||
(__bridge CFURLRef)[NSURL fileURLWithPath:bundlePath]));
|
||||
}
|
||||
if (d.count) {
|
||||
self.infoDict = d;
|
||||
return self.infoDict;
|
||||
}
|
||||
|
||||
@@ -605,10 +605,15 @@ NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
|
||||
|
||||
/** Export current state of the SNTMetricSet as an NSDictionary. */
|
||||
- (NSDictionary *)export {
|
||||
NSDictionary *exported = nil;
|
||||
NSDictionary *exported;
|
||||
|
||||
NSArray *callbacks;
|
||||
@synchronized(self) {
|
||||
callbacks = [_callbacks mutableCopy];
|
||||
}
|
||||
|
||||
// Invoke callbacks to ensure metrics are up to date.
|
||||
for (void (^cb)(void) in _callbacks) {
|
||||
for (void (^cb)(void) in callbacks) {
|
||||
cb();
|
||||
}
|
||||
|
||||
@@ -639,20 +644,9 @@ NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format) {
|
||||
NSDictionary *SNTMetricConvertDatesToISO8601Strings(NSDictionary *metrics) {
|
||||
NSMutableDictionary *mutableMetrics = [metrics mutableCopy];
|
||||
|
||||
id formatter;
|
||||
|
||||
if (@available(macOS 10.13, *)) {
|
||||
NSISO8601DateFormatter *isoFormatter = [[NSISO8601DateFormatter alloc] init];
|
||||
|
||||
isoFormatter.formatOptions =
|
||||
NSISO8601DateFormatWithInternetDateTime | NSISO8601DateFormatWithFractionalSeconds;
|
||||
formatter = isoFormatter;
|
||||
} else {
|
||||
NSDateFormatter *localFormatter = [[NSDateFormatter alloc] init];
|
||||
[localFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
|
||||
[localFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
|
||||
formatter = localFormatter;
|
||||
}
|
||||
NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
|
||||
formatter.formatOptions =
|
||||
NSISO8601DateFormatWithInternetDateTime | NSISO8601DateFormatWithFractionalSeconds;
|
||||
|
||||
for (NSString *metricName in mutableMetrics[@"metrics"]) {
|
||||
NSMutableDictionary *metric = mutableMetrics[@"metrics"][metricName];
|
||||
|
||||
@@ -54,4 +54,19 @@
|
||||
///
|
||||
+ (NSString *)modelIdentifier;
|
||||
|
||||
///
|
||||
/// @return The Santa product version, e.g. 2024.6
|
||||
///
|
||||
+ (NSString *)santaProductVersion;
|
||||
|
||||
///
|
||||
/// @return The Santa build version, e.g. 655965194
|
||||
///
|
||||
+ (NSString *)santaBuildVersion;
|
||||
|
||||
///
|
||||
/// @return The full Santa versoin, e.g. 2024.6.655965194
|
||||
///
|
||||
+ (NSString *)santaFullVersion;
|
||||
|
||||
@end
|
||||
|
||||
@@ -74,6 +74,21 @@
|
||||
return @(model);
|
||||
}
|
||||
|
||||
+ (NSString *)santaProductVersion {
|
||||
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
|
||||
return info_dict[@"CFBundleShortVersionString"];
|
||||
}
|
||||
|
||||
+ (NSString *)santaBuildVersion {
|
||||
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
|
||||
return [[info_dict[@"CFBundleVersion"] componentsSeparatedByString:@"."] lastObject];
|
||||
}
|
||||
|
||||
+ (NSString *)santaFullVersion {
|
||||
NSDictionary *info_dict = [[NSBundle mainBundle] infoDictionary];
|
||||
return info_dict[@"CFBundleVersion"];
|
||||
}
|
||||
|
||||
#pragma mark - Internal
|
||||
|
||||
+ (NSDictionary *)_systemVersionDictionary {
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
/// A block that takes the calculated bundle hash, associated events and hashing time in ms.
|
||||
typedef void (^SNTBundleHashBlock)(NSString *, NSArray<SNTStoredEvent *> *, NSNumber *);
|
||||
|
||||
/// Protocol implemented by santabs and utilized by SantaGUI for bundle hashing
|
||||
/// Protocol implemented by santabundleservice and utilized by SantaGUI for bundle hashing
|
||||
@protocol SNTBundleServiceXPC
|
||||
|
||||
///
|
||||
|
||||
@@ -19,11 +19,11 @@
|
||||
|
||||
#include "Source/common/ScopedTypeRef.h"
|
||||
|
||||
namespace santa::common {
|
||||
namespace santa {
|
||||
|
||||
template <typename CFT>
|
||||
using ScopedCFTypeRef = ScopedTypeRef<CFT, (CFT)NULL, CFRetain, CFRelease>;
|
||||
|
||||
} // namespace santa::common
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "Source/common/ScopedCFTypeRef.h"
|
||||
|
||||
using santa::common::ScopedCFTypeRef;
|
||||
using santa::ScopedCFTypeRef;
|
||||
|
||||
@interface ScopedCFTypeRefTest : XCTestCase
|
||||
@end
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include "Source/common/ScopedTypeRef.h"
|
||||
|
||||
namespace santa::common {
|
||||
namespace santa {
|
||||
|
||||
template <typename IOT>
|
||||
using ScopedIOObjectRef =
|
||||
@@ -27,4 +27,4 @@ using ScopedIOObjectRef =
|
||||
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // namespace santa
|
||||
|
||||
@@ -20,8 +20,8 @@
|
||||
#include "Source/common/ScopedIOObjectRef.h"
|
||||
#include "Source/santad/Logs/EndpointSecurity/Serializers/Utilities.h"
|
||||
|
||||
using santa::common::ScopedIOObjectRef;
|
||||
using santa::santad::logs::endpoint_security::serializers::Utilities::GetDefaultIOKitCommsPort;
|
||||
using santa::GetDefaultIOKitCommsPort;
|
||||
using santa::ScopedIOObjectRef;
|
||||
|
||||
@interface ScopedIOObjectRefTest : XCTestCase
|
||||
@end
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include <CoreFoundation/CoreFoundation.h>
|
||||
#include <assert.h>
|
||||
|
||||
namespace santa::common {
|
||||
namespace santa {
|
||||
|
||||
template <typename ElementT, ElementT InvalidV, auto RetainFunc,
|
||||
auto ReleaseFunc>
|
||||
@@ -75,6 +75,6 @@ class ScopedTypeRef {
|
||||
ElementT object_;
|
||||
};
|
||||
|
||||
} // namespace santa::common
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
30
Source/common/SigningIDHelpers.h
Normal file
30
Source/common/SigningIDHelpers.h
Normal file
@@ -0,0 +1,30 @@
|
||||
/// Copyright 2024 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <MOLCodesignChecker/MOLCodesignChecker.h>
|
||||
|
||||
__BEGIN_DECLS
|
||||
|
||||
/**
|
||||
Return a string representing normalized SigningID (prefixed with TeamID and a
|
||||
colon).
|
||||
|
||||
@param csc A MOLCodesignChecker instance
|
||||
|
||||
@return An NSString formated as teamID:signingID or nil if there isn't a valid signing ID.
|
||||
*/
|
||||
NSString *FormatSigningID(MOLCodesignChecker *csc);
|
||||
|
||||
__END_DECLS
|
||||
33
Source/common/SigningIDHelpers.m
Normal file
33
Source/common/SigningIDHelpers.m
Normal file
@@ -0,0 +1,33 @@
|
||||
/// Copyright 2024 Google LLC
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "Source/common/SigningIDHelpers.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
NSString *FormatSigningID(MOLCodesignChecker *csc) {
|
||||
if (!csc.signingID.length) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (!csc.teamID.length) {
|
||||
if (csc.platformBinary) {
|
||||
return [NSString stringWithFormat:@"%@:%@", @"platform", csc.signingID];
|
||||
} else {
|
||||
LOGD(@"unable to format signing ID missing team ID for non-platform binary");
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"%@:%@", csc.teamID, csc.signingID];
|
||||
}
|
||||
@@ -15,13 +15,14 @@
|
||||
#ifndef SANTA__COMMON__STRING_H
|
||||
#define SANTA__COMMON__STRING_H
|
||||
|
||||
#include <EndpointSecurity/ESTypes.h>
|
||||
#include <Foundation/Foundation.h>
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace santa::common {
|
||||
namespace santa {
|
||||
|
||||
static inline std::string_view NSStringToUTF8StringView(NSString *str) {
|
||||
return std::string_view(str.UTF8String, [str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
|
||||
@@ -48,6 +49,10 @@ static inline NSString *OptionalStringToNSString(const std::optional<std::string
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace santa::common
|
||||
static inline std::string_view StringTokenToStringView(es_string_token_t es_str) {
|
||||
return std::string_view(es_str.data, es_str.length);
|
||||
}
|
||||
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -83,5 +83,6 @@ es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc,
|
||||
uint64_t future_deadline_ms = 100000);
|
||||
|
||||
uint32_t MaxSupportedESMessageVersionForCurrentOS();
|
||||
uint32_t MinSupportedESMessageVersion(es_event_type_t event_type);
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,6 +19,8 @@
|
||||
#include <mach/mach_time.h>
|
||||
#include <time.h>
|
||||
#include <uuid/uuid.h>
|
||||
|
||||
#include "Source/common/Platform.h"
|
||||
#include "Source/common/SystemResources.h"
|
||||
|
||||
NSString *RepeatedString(NSString *str, NSUInteger len) {
|
||||
@@ -64,7 +66,7 @@ struct stat MakeStat(int offset) {
|
||||
|
||||
es_string_token_t MakeESStringToken(const char *s) {
|
||||
return es_string_token_t{
|
||||
.length = strlen(s),
|
||||
.length = s ? strlen(s) : 0,
|
||||
.data = s,
|
||||
};
|
||||
}
|
||||
@@ -91,25 +93,6 @@ es_process_t MakeESProcess(es_file_t *file, audit_token_t tok, audit_token_t par
|
||||
};
|
||||
}
|
||||
|
||||
uint32_t MaxSupportedESMessageVersionForCurrentOS() {
|
||||
// Notes:
|
||||
// 1. ES message v3 was only in betas.
|
||||
// 2. Message version 7 appeared in macOS 13.3, but features from that are
|
||||
// not currently used. Leaving off support here so as to not require
|
||||
// adding v7 test JSON files.
|
||||
if (@available(macOS 13.0, *)) {
|
||||
return 6;
|
||||
} else if (@available(macOS 12.3, *)) {
|
||||
return 5;
|
||||
} else if (@available(macOS 11.0, *)) {
|
||||
return 4;
|
||||
} else if (@available(macOS 10.15.4, *)) {
|
||||
return 2;
|
||||
} else {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
es_message_t MakeESMessage(es_event_type_t et, es_process_t *proc, ActionType action_type,
|
||||
uint64_t future_deadline_ms) {
|
||||
es_message_t es_msg = {
|
||||
@@ -134,3 +117,204 @@ void SleepMS(long ms) {
|
||||
XCTAssertEqual(errno, EINTR);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t MaxSupportedESMessageVersionForCurrentOS() {
|
||||
// Note 1: This function only returns a subset of versions. This is due to the
|
||||
// minimum supported OS build version as well as features in latest versions
|
||||
// not currently being used. Capping the max means unnecessary duuplicate test
|
||||
// JSON files are not needed.
|
||||
//
|
||||
// Note 2: The following table maps ES message versions to lmin macOS version:
|
||||
// ES Version | macOS Version
|
||||
// 1 | 10.15.0
|
||||
// 2 | 10.15.4
|
||||
// 3 | Only in a beta
|
||||
// 4 | 11.0
|
||||
// 5 | 12.3
|
||||
// 6 | 13.0
|
||||
// 7 | 14.0
|
||||
// 8 | 15.0
|
||||
if (@available(macOS 13.0, *)) {
|
||||
return 6;
|
||||
} else if (@available(macOS 12.3, *)) {
|
||||
return 5;
|
||||
} else {
|
||||
return 4;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t MinSupportedESMessageVersion(es_event_type_t event_type) {
|
||||
switch (event_type) {
|
||||
// The following events are available beginning in macOS 10.15
|
||||
case ES_EVENT_TYPE_AUTH_EXEC:
|
||||
case ES_EVENT_TYPE_AUTH_OPEN:
|
||||
case ES_EVENT_TYPE_AUTH_KEXTLOAD:
|
||||
case ES_EVENT_TYPE_AUTH_MMAP:
|
||||
case ES_EVENT_TYPE_AUTH_MPROTECT:
|
||||
case ES_EVENT_TYPE_AUTH_MOUNT:
|
||||
case ES_EVENT_TYPE_AUTH_RENAME:
|
||||
case ES_EVENT_TYPE_AUTH_SIGNAL:
|
||||
case ES_EVENT_TYPE_AUTH_UNLINK:
|
||||
case ES_EVENT_TYPE_NOTIFY_EXEC:
|
||||
case ES_EVENT_TYPE_NOTIFY_OPEN:
|
||||
case ES_EVENT_TYPE_NOTIFY_FORK:
|
||||
case ES_EVENT_TYPE_NOTIFY_CLOSE:
|
||||
case ES_EVENT_TYPE_NOTIFY_CREATE:
|
||||
case ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA:
|
||||
case ES_EVENT_TYPE_NOTIFY_EXIT:
|
||||
case ES_EVENT_TYPE_NOTIFY_GET_TASK:
|
||||
case ES_EVENT_TYPE_NOTIFY_KEXTLOAD:
|
||||
case ES_EVENT_TYPE_NOTIFY_KEXTUNLOAD:
|
||||
case ES_EVENT_TYPE_NOTIFY_LINK:
|
||||
case ES_EVENT_TYPE_NOTIFY_MMAP:
|
||||
case ES_EVENT_TYPE_NOTIFY_MPROTECT:
|
||||
case ES_EVENT_TYPE_NOTIFY_MOUNT:
|
||||
case ES_EVENT_TYPE_NOTIFY_UNMOUNT:
|
||||
case ES_EVENT_TYPE_NOTIFY_IOKIT_OPEN:
|
||||
case ES_EVENT_TYPE_NOTIFY_RENAME:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETATTRLIST:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETEXTATTR:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETFLAGS:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETMODE:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETOWNER:
|
||||
case ES_EVENT_TYPE_NOTIFY_SIGNAL:
|
||||
case ES_EVENT_TYPE_NOTIFY_UNLINK:
|
||||
case ES_EVENT_TYPE_NOTIFY_WRITE:
|
||||
case ES_EVENT_TYPE_AUTH_FILE_PROVIDER_MATERIALIZE:
|
||||
case ES_EVENT_TYPE_NOTIFY_FILE_PROVIDER_MATERIALIZE:
|
||||
case ES_EVENT_TYPE_AUTH_FILE_PROVIDER_UPDATE:
|
||||
case ES_EVENT_TYPE_NOTIFY_FILE_PROVIDER_UPDATE:
|
||||
case ES_EVENT_TYPE_AUTH_READLINK:
|
||||
case ES_EVENT_TYPE_NOTIFY_READLINK:
|
||||
case ES_EVENT_TYPE_AUTH_TRUNCATE:
|
||||
case ES_EVENT_TYPE_NOTIFY_TRUNCATE:
|
||||
case ES_EVENT_TYPE_AUTH_LINK:
|
||||
case ES_EVENT_TYPE_NOTIFY_LOOKUP:
|
||||
case ES_EVENT_TYPE_AUTH_CREATE:
|
||||
case ES_EVENT_TYPE_AUTH_SETATTRLIST:
|
||||
case ES_EVENT_TYPE_AUTH_SETEXTATTR:
|
||||
case ES_EVENT_TYPE_AUTH_SETFLAGS:
|
||||
case ES_EVENT_TYPE_AUTH_SETMODE:
|
||||
case ES_EVENT_TYPE_AUTH_SETOWNER: return 1;
|
||||
|
||||
// The following events are available beginning in macOS 10.15.1
|
||||
case ES_EVENT_TYPE_AUTH_CHDIR:
|
||||
case ES_EVENT_TYPE_NOTIFY_CHDIR:
|
||||
case ES_EVENT_TYPE_AUTH_GETATTRLIST:
|
||||
case ES_EVENT_TYPE_NOTIFY_GETATTRLIST:
|
||||
case ES_EVENT_TYPE_NOTIFY_STAT:
|
||||
case ES_EVENT_TYPE_NOTIFY_ACCESS:
|
||||
case ES_EVENT_TYPE_AUTH_CHROOT:
|
||||
case ES_EVENT_TYPE_NOTIFY_CHROOT:
|
||||
case ES_EVENT_TYPE_AUTH_UTIMES:
|
||||
case ES_EVENT_TYPE_NOTIFY_UTIMES:
|
||||
case ES_EVENT_TYPE_AUTH_CLONE:
|
||||
case ES_EVENT_TYPE_NOTIFY_CLONE:
|
||||
case ES_EVENT_TYPE_NOTIFY_FCNTL:
|
||||
case ES_EVENT_TYPE_AUTH_GETEXTATTR:
|
||||
case ES_EVENT_TYPE_NOTIFY_GETEXTATTR:
|
||||
case ES_EVENT_TYPE_AUTH_LISTEXTATTR:
|
||||
case ES_EVENT_TYPE_NOTIFY_LISTEXTATTR:
|
||||
case ES_EVENT_TYPE_AUTH_READDIR:
|
||||
case ES_EVENT_TYPE_NOTIFY_READDIR:
|
||||
case ES_EVENT_TYPE_AUTH_DELETEEXTATTR:
|
||||
case ES_EVENT_TYPE_NOTIFY_DELETEEXTATTR:
|
||||
case ES_EVENT_TYPE_AUTH_FSGETPATH:
|
||||
case ES_EVENT_TYPE_NOTIFY_FSGETPATH:
|
||||
case ES_EVENT_TYPE_NOTIFY_DUP:
|
||||
case ES_EVENT_TYPE_AUTH_SETTIME:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETTIME:
|
||||
case ES_EVENT_TYPE_NOTIFY_UIPC_BIND:
|
||||
case ES_EVENT_TYPE_AUTH_UIPC_BIND:
|
||||
case ES_EVENT_TYPE_NOTIFY_UIPC_CONNECT:
|
||||
case ES_EVENT_TYPE_AUTH_UIPC_CONNECT:
|
||||
case ES_EVENT_TYPE_AUTH_EXCHANGEDATA:
|
||||
case ES_EVENT_TYPE_AUTH_SETACL:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETACL: return 1;
|
||||
|
||||
// The following events are available beginning in macOS 10.15.4
|
||||
case ES_EVENT_TYPE_NOTIFY_PTY_GRANT:
|
||||
case ES_EVENT_TYPE_NOTIFY_PTY_CLOSE:
|
||||
case ES_EVENT_TYPE_AUTH_PROC_CHECK:
|
||||
case ES_EVENT_TYPE_NOTIFY_PROC_CHECK:
|
||||
case ES_EVENT_TYPE_AUTH_GET_TASK: return 2;
|
||||
|
||||
// The following events are available beginning in macOS 11.0
|
||||
case ES_EVENT_TYPE_AUTH_SEARCHFS:
|
||||
case ES_EVENT_TYPE_NOTIFY_SEARCHFS:
|
||||
case ES_EVENT_TYPE_AUTH_FCNTL:
|
||||
case ES_EVENT_TYPE_AUTH_IOKIT_OPEN:
|
||||
case ES_EVENT_TYPE_AUTH_PROC_SUSPEND_RESUME:
|
||||
case ES_EVENT_TYPE_NOTIFY_PROC_SUSPEND_RESUME:
|
||||
case ES_EVENT_TYPE_NOTIFY_CS_INVALIDATED:
|
||||
case ES_EVENT_TYPE_NOTIFY_GET_TASK_NAME:
|
||||
case ES_EVENT_TYPE_NOTIFY_TRACE:
|
||||
case ES_EVENT_TYPE_NOTIFY_REMOTE_THREAD_CREATE:
|
||||
case ES_EVENT_TYPE_AUTH_REMOUNT:
|
||||
case ES_EVENT_TYPE_NOTIFY_REMOUNT: return 4;
|
||||
|
||||
// The following events are available beginning in macOS 11.3
|
||||
case ES_EVENT_TYPE_AUTH_GET_TASK_READ:
|
||||
case ES_EVENT_TYPE_NOTIFY_GET_TASK_READ:
|
||||
case ES_EVENT_TYPE_NOTIFY_GET_TASK_INSPECT: return 4;
|
||||
|
||||
// The following events are available beginning in macOS 12.0
|
||||
case ES_EVENT_TYPE_NOTIFY_SETUID:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETGID:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETEUID:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETEGID:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETREUID:
|
||||
case ES_EVENT_TYPE_NOTIFY_SETREGID:
|
||||
case ES_EVENT_TYPE_AUTH_COPYFILE:
|
||||
case ES_EVENT_TYPE_NOTIFY_COPYFILE: return 4;
|
||||
|
||||
#if HAVE_MACOS_13
|
||||
// The following events are available beginning in macOS 13.0
|
||||
case ES_EVENT_TYPE_NOTIFY_AUTHENTICATION:
|
||||
case ES_EVENT_TYPE_NOTIFY_XP_MALWARE_DETECTED:
|
||||
case ES_EVENT_TYPE_NOTIFY_XP_MALWARE_REMEDIATED:
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN:
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT:
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK:
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK:
|
||||
case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH:
|
||||
case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH:
|
||||
case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN:
|
||||
case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT:
|
||||
case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN:
|
||||
case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT:
|
||||
case ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_ADD:
|
||||
case ES_EVENT_TYPE_NOTIFY_BTM_LAUNCH_ITEM_REMOVE: return 6;
|
||||
#endif
|
||||
|
||||
#if HAVE_MACOS_14
|
||||
// The following events are available beginning in macOS 14.0
|
||||
case ES_EVENT_TYPE_NOTIFY_PROFILE_ADD:
|
||||
case ES_EVENT_TYPE_NOTIFY_PROFILE_REMOVE:
|
||||
case ES_EVENT_TYPE_NOTIFY_SU:
|
||||
case ES_EVENT_TYPE_NOTIFY_AUTHORIZATION_PETITION:
|
||||
case ES_EVENT_TYPE_NOTIFY_AUTHORIZATION_JUDGEMENT:
|
||||
case ES_EVENT_TYPE_NOTIFY_SUDO:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_GROUP_ADD:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_GROUP_REMOVE:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_GROUP_SET:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_MODIFY_PASSWORD:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_DISABLE_USER:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_ENABLE_USER:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_VALUE_ADD:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_VALUE_REMOVE:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_ATTRIBUTE_SET:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_CREATE_USER:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_CREATE_GROUP:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_DELETE_USER:
|
||||
case ES_EVENT_TYPE_NOTIFY_OD_DELETE_GROUP:
|
||||
case ES_EVENT_TYPE_NOTIFY_XPC_CONNECT: return 7;
|
||||
#endif
|
||||
|
||||
#if HAVE_MACOS_15
|
||||
case ES_EVENT_TYPE_NOTIFY_GATEKEEPER_USER_OVERRIDE: return 8;
|
||||
#endif
|
||||
|
||||
default: return UINT32_MAX;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,10 +15,10 @@
|
||||
#ifndef SANTA__COMMON__UNIT_H
|
||||
#define SANTA__COMMON__UNIT_H
|
||||
|
||||
namespace santa::common {
|
||||
namespace santa {
|
||||
|
||||
struct Unit {};
|
||||
|
||||
} // namespace santa::common
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -538,6 +538,225 @@ message FileAccess {
|
||||
optional PolicyDecision policy_decision = 6;
|
||||
}
|
||||
|
||||
// Session identifier for a graphical session
|
||||
// Note: Identifiers are opaque and have no meaning outside of correlating Santa
|
||||
// events with the same identifier
|
||||
message GraphicalSession {
|
||||
optional uint32 id = 1;
|
||||
}
|
||||
|
||||
// Information about a socket address and its type
|
||||
message SocketAddress {
|
||||
// The socket address
|
||||
optional bytes address = 1;
|
||||
|
||||
enum Type {
|
||||
TYPE_UNKNOWN = 0;
|
||||
TYPE_NONE = 1;
|
||||
TYPE_IPV4 = 2;
|
||||
TYPE_IPV6 = 3;
|
||||
TYPE_NAMED_SOCKET = 4;
|
||||
}
|
||||
|
||||
// The type of the socket address
|
||||
optional Type type = 2;
|
||||
}
|
||||
|
||||
// Information about a user logging in via loginwindow
|
||||
message LoginWindowSessionLogin {
|
||||
// The process that emitted the login event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Name of the user logging in
|
||||
optional UserInfo user = 2;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 3;
|
||||
}
|
||||
|
||||
// Information about a user logging out via loginwindow
|
||||
message LoginWindowSessionLogout {
|
||||
// The process that emitted the logout event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Name of the user logging out
|
||||
optional UserInfo user = 2;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 3;
|
||||
}
|
||||
|
||||
// Information about a user locking their session via loginwindow
|
||||
message LoginWindowSessionLock {
|
||||
// The process that emitted the lock event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Name of the user locking their session
|
||||
optional UserInfo user = 2;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 3;
|
||||
}
|
||||
|
||||
// Information about a user unlocking their session via loginwindow
|
||||
message LoginWindowSessionUnlock {
|
||||
// The process that emitted the unlock event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Name of the user unlocking their session
|
||||
optional UserInfo user = 2;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 3;
|
||||
}
|
||||
|
||||
// Information about loginwindow events
|
||||
message LoginWindowSession {
|
||||
oneof event {
|
||||
LoginWindowSessionLogin login = 1;
|
||||
LoginWindowSessionLogout logout = 2;
|
||||
LoginWindowSessionLock lock = 3;
|
||||
LoginWindowSessionUnlock unlock = 4;
|
||||
}
|
||||
}
|
||||
|
||||
// Information about a login event from the `login(1)` utility
|
||||
message Login {
|
||||
// The process that emitted the login event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Whether or not the login was successful
|
||||
optional bool success = 2;
|
||||
|
||||
// Login failure message, if applicable
|
||||
optional bytes failure_message = 3;
|
||||
|
||||
// Information about the user that attempted to log in
|
||||
// Note: `uid` data may not always exist on failed attempts
|
||||
optional UserInfo user = 4;
|
||||
}
|
||||
|
||||
// Information about a logout event from the `login(1)` utility
|
||||
message Logout {
|
||||
// The process that emitted the logout event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Information about the user that logged out
|
||||
optional UserInfo user = 2;
|
||||
}
|
||||
|
||||
// Information about login and logout events from the `login(1)` utility
|
||||
message LoginLogout {
|
||||
oneof event {
|
||||
Login login = 1;
|
||||
Logout logout = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Information related to Screen Sharing attaching to a graphical session
|
||||
message ScreenSharingAttach {
|
||||
// The process that emitted the attach event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Whether or not the attach was successful
|
||||
optional bool success = 2;
|
||||
|
||||
// Source address information
|
||||
optional SocketAddress source = 3;
|
||||
|
||||
// Apple ID of the viewer
|
||||
optional bytes viewer = 4;
|
||||
|
||||
// Type of authentication used
|
||||
optional bytes authentication_type = 5;
|
||||
|
||||
// User that attempted authentication, if applicable
|
||||
optional UserInfo authentication_user = 6;
|
||||
|
||||
// Username of the loginwindow session, if available
|
||||
optional UserInfo session_user = 7;
|
||||
|
||||
// Whether or not there was an existing session
|
||||
optional bool existing_session = 8;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 9;
|
||||
}
|
||||
|
||||
// Information related to Screen Sharing detaching from a graphical session
|
||||
message ScreenSharingDetach {
|
||||
// The process that emitted the detach event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Source address information
|
||||
optional SocketAddress source = 2;
|
||||
|
||||
// Apple ID of the viewer
|
||||
optional bytes viewer = 3;
|
||||
|
||||
// Graphical session information for this session
|
||||
optional GraphicalSession graphical_session = 4;
|
||||
}
|
||||
|
||||
// Information about Screen Sharing attach and detach events
|
||||
message ScreenSharing {
|
||||
oneof event {
|
||||
ScreenSharingAttach attach = 1;
|
||||
ScreenSharingDetach detach = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// Information about SSH login events from the macOS OpenSSH implementation
|
||||
message OpenSSHLogin {
|
||||
// The process that emitted the login event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
enum Result {
|
||||
RESULT_UNKNOWN = 0;
|
||||
RESULT_LOGIN_EXCEED_MAXTRIES = 1;
|
||||
RESULT_LOGIN_ROOT_DENIED = 2;
|
||||
RESULT_AUTH_SUCCESS = 3;
|
||||
RESULT_AUTH_FAIL_NONE = 4;
|
||||
RESULT_AUTH_FAIL_PASSWD = 5;
|
||||
RESULT_AUTH_FAIL_KBDINT = 6;
|
||||
RESULT_AUTH_FAIL_PUBKEY = 7;
|
||||
RESULT_AUTH_FAIL_HOSTBASED = 8;
|
||||
RESULT_AUTH_FAIL_GSSAPI = 9;
|
||||
RESULT_INVALID_USER = 10;
|
||||
}
|
||||
|
||||
// The result of the login attempt
|
||||
// Note: Successful if type == `RESULT_AUTH_SUCCESS`
|
||||
optional Result result = 2;
|
||||
|
||||
// Source address of the connection
|
||||
optional SocketAddress source = 3;
|
||||
|
||||
// Name of the user that attempted to login
|
||||
// Note: `uid` data may not always exist on failed attempts
|
||||
optional UserInfo user = 4;
|
||||
}
|
||||
|
||||
// Information about SSH logout events from the macOS OpenSSH implementation
|
||||
message OpenSSHLogout {
|
||||
// The process that emitted the logout event
|
||||
optional ProcessInfoLight instigator = 1;
|
||||
|
||||
// Source address of the connection
|
||||
optional SocketAddress source = 2;
|
||||
|
||||
// Information about the user that logged out
|
||||
optional UserInfo user = 3;
|
||||
}
|
||||
|
||||
// Information about login/logout events from the macOS OpenSSH implementation
|
||||
message OpenSSH {
|
||||
oneof event {
|
||||
OpenSSHLogin login = 1;
|
||||
OpenSSHLogout logout = 2;
|
||||
}
|
||||
}
|
||||
|
||||
// A message encapsulating a single event
|
||||
message SantaMessage {
|
||||
// Machine ID of the host emitting this log
|
||||
@@ -565,6 +784,10 @@ message SantaMessage {
|
||||
Allowlist allowlist = 20;
|
||||
FileAccess file_access = 21;
|
||||
CodesigningInvalidated codesigning_invalidated = 22;
|
||||
LoginWindowSession login_window_session = 23;
|
||||
LoginLogout login_logout = 24;
|
||||
ScreenSharing screen_sharing = 25;
|
||||
OpenSSH open_ssh = 26;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ macos_application(
|
||||
"//conditions:default": None,
|
||||
}),
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "11.0",
|
||||
minimum_os_version = "12.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<outlet property="foundFileCountLabel" destination="LHV-gV-vyf" id="Sr0-T2-xGx"/>
|
||||
<outlet property="hashingIndicator" destination="VyY-Yg-JOe" id="Yq4-tZ-9ep"/>
|
||||
<outlet property="openEventButton" destination="7ua-5a-uSd" id="9s4-ZA-Vlo"/>
|
||||
<outlet property="dismissEventButton" destination="BbV-3h-mmL" id="5s4-ZB-xlo"/>
|
||||
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
|
||||
</connections>
|
||||
</customObject>
|
||||
@@ -286,7 +287,7 @@ DQ
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
|
||||
<rect key="frame" x="271" y="28" width="124" height="34"/>
|
||||
<buttonCell key="cell" type="push" title="Ignore" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<buttonCell key="cell" type="push" title="Dismiss" bezelStyle="rounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
|
||||
@@ -54,6 +54,11 @@
|
||||
///
|
||||
@property(weak) IBOutlet NSButton *openEventButton;
|
||||
|
||||
///
|
||||
/// Reference to the "Dismiss Event" button in the XIB. Used to update its title.
|
||||
///
|
||||
@property(weak) IBOutlet NSButton *dismissEventButton;
|
||||
|
||||
///
|
||||
/// The execution event that this window is for
|
||||
///
|
||||
|
||||
@@ -93,11 +93,17 @@
|
||||
if (eventDetailText) {
|
||||
[self.openEventButton setTitle:eventDetailText];
|
||||
// Require the button keyEquivalent set to be CMD + Return
|
||||
[self.openEventButton setKeyEquivalent:@"\r"]; // Return Key
|
||||
[self.openEventButton setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; // Command Key
|
||||
[self.openEventButton setKeyEquivalent:@"\r"]; // Return Key
|
||||
[self.openEventButton
|
||||
setKeyEquivalentModifierMask:NSEventModifierFlagCommand]; // Command Key
|
||||
}
|
||||
}
|
||||
|
||||
NSString *dismissButtonText = [[SNTConfigurator configurator] dismissText];
|
||||
if (dismissButtonText.length) {
|
||||
[self.dismissEventButton setTitle:dismissButtonText];
|
||||
}
|
||||
|
||||
if (!self.event.needsBundleHash) {
|
||||
[self.bundleHashLabel removeFromSuperview];
|
||||
[self.hashingIndicator removeFromSuperview];
|
||||
|
||||
@@ -50,18 +50,9 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
customMsg:self.attributedCustomMessage];
|
||||
self.window.delegate = self;
|
||||
|
||||
// Add app to Cmd+Tab and Dock.
|
||||
NSApp.activationPolicy = NSApplicationActivationPolicyRegular;
|
||||
|
||||
[super showWindow:sender];
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
// Remove app from Cmd+Tab and Dock.
|
||||
NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
|
||||
[super windowWillClose:notification];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
return [SNTBlockMessage formatMessage:self.customMessage];
|
||||
}
|
||||
|
||||
@@ -66,18 +66,9 @@
|
||||
|
||||
self.window.delegate = self;
|
||||
|
||||
// Make sure app doesn't appear in Cmd+Tab or Dock.
|
||||
NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
|
||||
|
||||
[super showWindow:sender];
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
// Remove app from Cmd+Tab and Dock.
|
||||
NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
|
||||
[super windowWillClose:notification];
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
return [SNTBlockMessage attributedBlockMessageForFileAccessEvent:self.event
|
||||
customMessage:self.customMessage];
|
||||
|
||||
@@ -71,6 +71,8 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
[bc resume];
|
||||
[[bc remoteObjectProxy] spindown];
|
||||
[bc invalidate];
|
||||
// Remove app from Cmd+Tab and Dock.
|
||||
NSApp.activationPolicy = NSApplicationActivationPolicyAccessory;
|
||||
[NSApp hide:self];
|
||||
}
|
||||
}
|
||||
@@ -101,32 +103,37 @@ static NSString *const silencedNotificationsKey = @"SilencedNotifications";
|
||||
// If GUI is in silent mode or if there's already a notification queued for
|
||||
// this message, don't do anything else.
|
||||
if ([SNTConfigurator configurator].enableSilentMode) return;
|
||||
if ([self notificationAlreadyQueued:pendingMsg]) return;
|
||||
|
||||
// See if this message has been user-silenced.
|
||||
NSString *messageHash = [pendingMsg messageHash];
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
|
||||
if ([silenceDate isKindOfClass:[NSDate class]]) {
|
||||
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
|
||||
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
|
||||
LOGI(@"Notification silence: date is in the future, ignoring");
|
||||
[self updateSilenceDate:nil forHash:messageHash];
|
||||
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
|
||||
LOGI(@"Notification silence: date is more than one day ago, ignoring");
|
||||
[self updateSilenceDate:nil forHash:messageHash];
|
||||
} else {
|
||||
LOGI(@"Notification silence: dropping notification for %@", messageHash);
|
||||
return;
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
if ([self notificationAlreadyQueued:pendingMsg]) return;
|
||||
|
||||
// See if this message has been user-silenced.
|
||||
NSString *messageHash = [pendingMsg messageHash];
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][messageHash];
|
||||
if ([silenceDate isKindOfClass:[NSDate class]]) {
|
||||
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
|
||||
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
|
||||
LOGI(@"Notification silence: date is in the future, ignoring");
|
||||
[self updateSilenceDate:nil forHash:messageHash];
|
||||
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
|
||||
LOGI(@"Notification silence: date is more than one day ago, ignoring");
|
||||
[self updateSilenceDate:nil forHash:messageHash];
|
||||
} else {
|
||||
LOGI(@"Notification silence: dropping notification for %@", messageHash);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
|
||||
if (!self.currentWindowController) {
|
||||
[self showQueuedWindow];
|
||||
}
|
||||
if (!self.currentWindowController) {
|
||||
// Add app to Cmd+Tab and Dock.
|
||||
NSApp.activationPolicy = NSApplicationActivationPolicyRegular;
|
||||
[self showQueuedWindow];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// For blocked execution notifications, post an NSDistributedNotificationCenter
|
||||
|
||||
@@ -28,24 +28,22 @@
|
||||
|
||||
- (OSSystemExtensionReplacementAction)request:(OSSystemExtensionRequest *)request
|
||||
actionForReplacingExtension:(OSSystemExtensionProperties *)old
|
||||
withExtension:
|
||||
(OSSystemExtensionProperties *)new API_AVAILABLE(macos(10.15)) {
|
||||
withExtension:(OSSystemExtensionProperties *)new {
|
||||
NSLog(@"SystemExtension \"%@\" request for replacement", request.identifier);
|
||||
return OSSystemExtensionReplacementActionReplace;
|
||||
}
|
||||
|
||||
- (void)requestNeedsUserApproval:(OSSystemExtensionRequest *)request API_AVAILABLE(macos(10.15)) {
|
||||
- (void)requestNeedsUserApproval:(OSSystemExtensionRequest *)request {
|
||||
NSLog(@"SystemExtension \"%@\" request needs user approval", request.identifier);
|
||||
}
|
||||
|
||||
- (void)request:(OSSystemExtensionRequest *)request
|
||||
didFailWithError:(NSError *)error API_AVAILABLE(macos(10.15)) {
|
||||
- (void)request:(OSSystemExtensionRequest *)request didFailWithError:(NSError *)error {
|
||||
NSLog(@"SystemExtension \"%@\" request did fail: %@", request.identifier, error);
|
||||
exit((int)error.code);
|
||||
}
|
||||
|
||||
- (void)request:(OSSystemExtensionRequest *)request
|
||||
didFinishWithResult:(OSSystemExtensionRequestResult)result API_AVAILABLE(macos(10.15)) {
|
||||
didFinishWithResult:(OSSystemExtensionRequestResult)result {
|
||||
NSLog(@"SystemExtension \"%@\" request did finish: %ld", request.identifier, (long)result);
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ objc_library(
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SNTXPCBundleServiceInterface",
|
||||
"//Source/common:SNTXPCNotifierInterface",
|
||||
"//Source/common:SigningIDHelpers",
|
||||
"@FMDB",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
@@ -34,7 +35,7 @@ macos_command_line_application(
|
||||
"--options library,kill,runtime",
|
||||
],
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "11.0",
|
||||
minimum_os_version = "12.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTXPCNotifierInterface.h"
|
||||
#import "Source/common/SigningIDHelpers.h"
|
||||
|
||||
@interface SNTBundleService ()
|
||||
@property MOLXPCConnection *notifierConnection;
|
||||
@@ -226,6 +227,9 @@
|
||||
|
||||
MOLCodesignChecker *cs = [fi codesignCheckerWithError:NULL];
|
||||
se.signingChain = cs.certificates;
|
||||
se.cdhash = cs.cdhash;
|
||||
se.teamID = cs.teamID;
|
||||
se.signingID = FormatSigningID(cs);
|
||||
|
||||
dispatch_sync(dispatch_get_main_queue(), ^{
|
||||
relatedEvents[se.fileSHA256] = se;
|
||||
|
||||
@@ -76,6 +76,7 @@ objc_library(
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SNTXPCSyncServiceInterface",
|
||||
"//Source/common:SNTXPCUnprivilegedControlInterface",
|
||||
"//Source/common:SigningIDHelpers",
|
||||
"//Source/santasyncservice:sync_lib",
|
||||
"@FMDB",
|
||||
"@MOLCertificate",
|
||||
@@ -93,7 +94,7 @@ macos_command_line_application(
|
||||
"--options library,kill,runtime",
|
||||
],
|
||||
infoplists = ["Info.plist"],
|
||||
minimum_os_version = "11.0",
|
||||
minimum_os_version = "12.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:santa_dev",
|
||||
@@ -121,6 +122,7 @@ santa_unit_test(
|
||||
"//Source/common:SNTStoredEvent",
|
||||
"//Source/common:SNTXPCBundleServiceInterface",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SigningIDHelpers",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTXPCBundleServiceInterface.h"
|
||||
#import "Source/common/SNTXPCControlInterface.h"
|
||||
#import "Source/common/SigningIDHelpers.h"
|
||||
#import "Source/santactl/SNTCommand.h"
|
||||
#import "Source/santactl/SNTCommandController.h"
|
||||
|
||||
@@ -84,6 +85,7 @@ NSString *formattedStringForKeyArray(NSArray<NSString *> *array) {
|
||||
@property(nonatomic) BOOL recursive;
|
||||
@property(nonatomic) BOOL jsonOutput;
|
||||
@property(nonatomic) BOOL bundleInfo;
|
||||
@property(nonatomic) BOOL filterInclusive;
|
||||
@property(nonatomic) NSNumber *certIndex;
|
||||
@property(nonatomic, copy) NSArray<NSString *> *outputKeyList;
|
||||
@property(nonatomic, copy) NSDictionary<NSString *, NSRegularExpression *> *outputFilters;
|
||||
@@ -188,6 +190,10 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
@" case-insensitive regular expression which must match anywhere in\n"
|
||||
@" the keyed property value for the file's info to be displayed.\n"
|
||||
@" You may specify multiple filters by repeating this flag.\n"
|
||||
@" If multiple filters are specified, any match will display the\n"
|
||||
@" file.\n"
|
||||
@" --filter-inclusive: If multiple filters are specified, they must all match\n"
|
||||
@" for the file to be displayed.\n"
|
||||
@" --bundleinfo: If the file is part of a bundle, will also display bundle\n"
|
||||
@" hash information and hashes of all bundle executables.\n"
|
||||
@" Incompatible with --recursive and --cert-index.\n"
|
||||
@@ -382,18 +388,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
|
||||
NSString *cdhash = csc.cdhash;
|
||||
NSString *teamID = csc.teamID;
|
||||
NSString *identifier = csc.signingID;
|
||||
|
||||
NSString *signingID;
|
||||
if (identifier) {
|
||||
if (teamID) {
|
||||
signingID = [NSString stringWithFormat:@"%@:%@", teamID, identifier];
|
||||
} else {
|
||||
if (csc.platformBinary) {
|
||||
signingID = [NSString stringWithFormat:@"platform:%@", identifier];
|
||||
}
|
||||
}
|
||||
}
|
||||
NSString *signingID = FormatSigningID(csc);
|
||||
|
||||
struct RuleIdentifiers identifiers = {
|
||||
.cdhash = cdhash,
|
||||
@@ -524,7 +519,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
- (SNTAttributeBlock)signingID {
|
||||
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
|
||||
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
|
||||
return csc.signingID;
|
||||
|
||||
return FormatSigningID(csc);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -609,6 +605,31 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
NSOperationQueue *operationQueue = [[NSOperationQueue alloc] init];
|
||||
operationQueue.qualityOfService = NSQualityOfServiceUserInitiated;
|
||||
|
||||
// Limit the number of concurrent operations to 2. By default it is unlimited. Querying for the
|
||||
// `Rule` results in an XPC message to the santa daemon. On an M1 Max we are
|
||||
// seeing issues with dropped XPC messages when there are 64 or more in-flight messages. The
|
||||
// number of in-flight requests to the santa daemon to will be capped to
|
||||
// `maxConcurrentOperationCount`.
|
||||
//
|
||||
// Why 2? We are seeing diminishing wall-time improvements for anything over 2 Qs.
|
||||
//
|
||||
// 1 Q
|
||||
// bazel run //Source/santactl -- fileinfo --recursive --key Path --key Rule /usr/libexec/
|
||||
// 1.16s user 0.92s system 35% cpu 5.775 total
|
||||
|
||||
// 2 Qs
|
||||
// bazel run //Source/santactl -- fileinfo --recursive --key Path --key Rule /usr/libexec/
|
||||
// 1.22s user 1.07s system 62% cpu 3.675 total
|
||||
|
||||
// 4 Qs
|
||||
// bazel run //Source/santactl -- fileinfo --recursive --key Path --key Rule /usr/libexec/
|
||||
// 1.22s user 1.16s system 72% cpu 3.275 total
|
||||
|
||||
// 8 Qs
|
||||
// bazel run //Source/santactl -- fileinfo --recursive --key Path --key Rule /usr/libexec/
|
||||
// 1.25s user 1.26s system 75% cpu 3.304 total
|
||||
operationQueue.maxConcurrentOperationCount = 2;
|
||||
|
||||
if (isDir && self.recursive) {
|
||||
NSDirectoryEnumerator *dirEnum = [fm enumeratorAtPath:path];
|
||||
NSString *file = [dirEnum nextObject];
|
||||
@@ -638,8 +659,29 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
[operationQueue waitUntilAllOperationsAreFinished];
|
||||
}
|
||||
|
||||
- (BOOL)shouldOutputValueToDictionary:(NSMutableDictionary *)outputDict
|
||||
valueForKey:(NSString * (^)(NSString *key))valueForKey {
|
||||
if (self.outputFilters.count == 0) return YES;
|
||||
|
||||
int matches = 0;
|
||||
for (NSString *key in self.outputFilters) {
|
||||
NSString *value = valueForKey(key);
|
||||
NSRegularExpression *regex = self.outputFilters[key];
|
||||
if (![regex firstMatchInString:value options:0 range:NSMakeRange(0, value.length)]) continue;
|
||||
// If this is a value we want to show, store it in the output dictionary.
|
||||
// This does a linear search on an array, but it's a small array.
|
||||
if (outputDict && value.length && [self.outputKeyList containsObject:key]) {
|
||||
outputDict[key] = value;
|
||||
}
|
||||
++matches;
|
||||
}
|
||||
|
||||
return self.filterInclusive ? matches == self.outputFilters.count : matches > 0;
|
||||
}
|
||||
|
||||
// Prints out the info for a single (non-directory) file. Which info is printed is controlled
|
||||
// by the keys in self.outputKeyList.
|
||||
// TODO: Refactor so this method is testable.
|
||||
- (void)printInfoForFile:(NSString *)path {
|
||||
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:path];
|
||||
if (!fileInfo) {
|
||||
@@ -673,41 +715,31 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
NSDictionary *cert = signingChain[index];
|
||||
|
||||
// Check if we should skip over this item based on outputFilters.
|
||||
BOOL filterMatch = self.outputFilters.count == 0;
|
||||
for (NSString *key in self.outputFilters) {
|
||||
NSString *value = cert[key] ?: @"";
|
||||
NSRegularExpression *regex = self.outputFilters[key];
|
||||
if (![regex firstMatchInString:value options:0 range:NSMakeRange(0, value.length)]) continue;
|
||||
filterMatch = YES;
|
||||
break;
|
||||
BOOL shouldOutput = [self shouldOutputValueToDictionary:nil
|
||||
valueForKey:^NSString *(NSString *key) {
|
||||
return cert[key] ?: @"";
|
||||
}];
|
||||
if (!shouldOutput) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!filterMatch) return;
|
||||
|
||||
// Filter out the info we want now, in case JSON output
|
||||
for (NSString *key in self.outputKeyList) {
|
||||
outputDict[key] = cert[key];
|
||||
}
|
||||
} else {
|
||||
// Check if we should skip over this item based on outputFilters. We do this before collecting
|
||||
// Check if we should skip over this item based on outputFilters. We do this before collecting
|
||||
// output info because there's a chance that we can bail out early if a filter doesn't match.
|
||||
// However we also don't want to recompute info, so we save any values that we plan to show.
|
||||
BOOL filterMatch = self.outputFilters.count == 0;
|
||||
for (NSString *key in self.outputFilters) {
|
||||
NSString *value = self.propertyMap[key](self, fileInfo) ?: @"";
|
||||
NSRegularExpression *regex = self.outputFilters[key];
|
||||
if (![regex firstMatchInString:value options:0 range:NSMakeRange(0, value.length)]) continue;
|
||||
// If this is a value we want to show, store it in the output dictionary.
|
||||
// This does a linear search on an array, but it's a small array.
|
||||
if (value.length && [self.outputKeyList containsObject:key]) {
|
||||
outputDict[key] = value;
|
||||
}
|
||||
filterMatch = YES;
|
||||
break;
|
||||
BOOL shouldOutput =
|
||||
[self shouldOutputValueToDictionary:outputDict
|
||||
valueForKey:^NSString *(NSString *key) {
|
||||
return self.propertyMap[key](self, fileInfo) ?: @"";
|
||||
}];
|
||||
if (!shouldOutput) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!filterMatch) return;
|
||||
|
||||
// Then fill the outputDict with the rest of the missing values.
|
||||
for (NSString *key in self.outputKeyList) {
|
||||
if (outputDict[key]) continue; // ignore keys that we've already set due to a filter
|
||||
@@ -880,6 +912,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
@"\n--bundleinfo is incompatible with --recursive and --cert-index"];
|
||||
}
|
||||
self.bundleInfo = YES;
|
||||
} else if ([arg caseInsensitiveCompare:@"--filter-inclusive"] == NSOrderedSame) {
|
||||
self.filterInclusive = YES;
|
||||
} else {
|
||||
[paths addObject:arg];
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
|
||||
@property(nonatomic) BOOL recursive;
|
||||
@property(nonatomic) BOOL jsonOutput;
|
||||
@property(nonatomic) BOOL filterInclusive;
|
||||
@property(nonatomic) NSNumber *certIndex;
|
||||
@property(nonatomic, copy) NSArray<NSString *> *outputKeyList;
|
||||
@property(nonatomic) NSDictionary<NSString *, SNTAttributeBlock> *propertyMap;
|
||||
@@ -250,4 +251,10 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
|
||||
XCTAssertEqualObjects(self.cfi.codeSigned(self.cfi, self.fileInfo), expected);
|
||||
}
|
||||
|
||||
- (void)testParseArgumentsFilterInclusiveTrue {
|
||||
NSArray *filePaths = [self.cfi parseArguments:@[ @"--filter-inclusive", @"/usr/bin/yes" ]];
|
||||
XCTAssertTrue(self.cfi.filterInclusive);
|
||||
XCTAssertTrue([filePaths containsObject:@"/usr/bin/yes"]);
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -67,7 +67,7 @@ REGISTER_COMMAND_NAME(@"printlog")
|
||||
- (void)runWithArguments:(NSArray *)arguments {
|
||||
JsonPrintOptions options;
|
||||
options.always_print_enums_as_ints = false;
|
||||
options.always_print_primitive_fields = true;
|
||||
options.always_print_fields_with_no_presence = true;
|
||||
options.preserve_proto_field_names = true;
|
||||
options.add_whitespace = true;
|
||||
|
||||
|
||||
@@ -61,7 +61,8 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
@"\n"
|
||||
@" One of:\n"
|
||||
@" --path {path}: path of binary/bundle to add/remove.\n"
|
||||
@" Will add the hash of the file currently at that path.\n"
|
||||
@" Will add an appropriate rule for the file currently at that path.\n"
|
||||
@" Defaults to a SHA-256 rule unless overridden with another flag.\n"
|
||||
@" Does not work with --check. Use the fileinfo verb to check.\n"
|
||||
@" the rule state of a file.\n"
|
||||
@" --identifier {sha256|teamID|signingID|cdhash}: identifier to add/remove/check\n"
|
||||
@@ -259,9 +260,17 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
newRule.identifier = cs.leafCertificate.SHA256;
|
||||
} else if (newRule.type == SNTRuleTypeCDHash) {
|
||||
MOLCodesignChecker *cs = [fi codesignCheckerWithError:NULL];
|
||||
newRule.identifier = cs.signingID;
|
||||
} else if (newRule.type == SNTRuleTypeTeamID || newRule.type == SNTRuleTypeSigningID) {
|
||||
// noop
|
||||
newRule.identifier = cs.cdhash;
|
||||
} else if (newRule.type == SNTRuleTypeTeamID) {
|
||||
MOLCodesignChecker *cs = [fi codesignCheckerWithError:NULL];
|
||||
newRule.identifier = cs.teamID;
|
||||
} else if (newRule.type == SNTRuleTypeSigningID) {
|
||||
MOLCodesignChecker *cs = [fi codesignCheckerWithError:NULL];
|
||||
if (cs.teamID.length) {
|
||||
newRule.identifier = [NSString stringWithFormat:@"%@:%@", cs.teamID, cs.signingID];
|
||||
} else if (cs.platformBinary) {
|
||||
newRule.identifier = [NSString stringWithFormat:@"platform:%@", cs.signingID];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -290,7 +299,8 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
if (newRule.state == SNTRuleStateUnknown) {
|
||||
[self printErrorUsageAndExit:@"No state specified"];
|
||||
} else if (!newRule.identifier) {
|
||||
[self printErrorUsageAndExit:@"Either SHA-256, team ID, or path to file must be specified"];
|
||||
[self printErrorUsageAndExit:
|
||||
@"A valid SHA-256, CDHash, Signing ID, team ID, or path to file must be specified"];
|
||||
}
|
||||
|
||||
[[self.daemonConn remoteObjectProxy]
|
||||
|
||||
@@ -193,7 +193,7 @@ objc_library(
|
||||
|
||||
objc_library(
|
||||
name = "SNTPolicyProcessor",
|
||||
srcs = ["SNTPolicyProcessor.m"],
|
||||
srcs = ["SNTPolicyProcessor.mm"],
|
||||
hdrs = ["SNTPolicyProcessor.h"],
|
||||
deps = [
|
||||
":SNTRuleTable",
|
||||
@@ -205,10 +205,24 @@ objc_library(
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:SNTRuleIdentifiers",
|
||||
"//Source/common:SigningIDHelpers",
|
||||
"@FMDB",
|
||||
"@MOLCertificate",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
"@com_google_absl//absl/container:flat_hash_map",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTPolicyProcessorTest",
|
||||
srcs = ["SNTPolicyProcessorTest.mm"],
|
||||
deps = [
|
||||
":SNTPolicyProcessor",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTRule",
|
||||
"//Source/common:TestUtils",
|
||||
],
|
||||
)
|
||||
|
||||
@@ -321,6 +335,7 @@ objc_library(
|
||||
":SNTCompilerController",
|
||||
":SNTEndpointSecurityEventHandler",
|
||||
":SNTEndpointSecurityTreeAwareClient",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
@@ -458,8 +473,10 @@ objc_library(
|
||||
hdrs = ["EventProviders/EndpointSecurity/Enricher.h"],
|
||||
deps = [
|
||||
":EndpointSecurityEnrichedTypes",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SantaCache",
|
||||
"//Source/common:String",
|
||||
"//Source/santad/ProcessTree:SNTEndpointSecurityAdapter",
|
||||
"//Source/santad/ProcessTree:process_tree",
|
||||
],
|
||||
@@ -538,6 +555,7 @@ objc_library(
|
||||
":EndpointSecuritySerializer",
|
||||
":EndpointSecuritySerializerUtilities",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTStoredEvent",
|
||||
@@ -553,6 +571,7 @@ objc_library(
|
||||
":EndpointSecuritySerializer",
|
||||
":EndpointSecuritySerializerUtilities",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
@@ -645,6 +664,7 @@ objc_library(
|
||||
deps = [
|
||||
":EndpointSecurityClient",
|
||||
":WatchItemPolicy",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/santad/ProcessTree:process_tree",
|
||||
],
|
||||
)
|
||||
@@ -705,7 +725,9 @@ objc_library(
|
||||
srcs = ["Metrics.mm"],
|
||||
hdrs = ["Metrics.h"],
|
||||
deps = [
|
||||
":EndpointSecurityMessage",
|
||||
":SNTApplicationCoreMetrics",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
@@ -779,6 +801,7 @@ objc_library(
|
||||
"//Source/common:SNTXPCUnprivilegedControlInterface",
|
||||
"//Source/common:Unit",
|
||||
"//Source/santad/ProcessTree:process_tree",
|
||||
"//Source/santad/ProcessTree/annotations:originator",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
@@ -800,6 +823,7 @@ objc_library(
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:SNTLogging",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:SNTSystemInfo",
|
||||
"//Source/common:SNTXPCControlInterface",
|
||||
"//Source/common:SystemResources",
|
||||
],
|
||||
@@ -821,7 +845,7 @@ macos_bundle(
|
||||
}),
|
||||
infoplists = ["Info.plist"],
|
||||
linkopts = ["-execute"],
|
||||
minimum_os_version = "11.0",
|
||||
minimum_os_version = "12.0",
|
||||
provisioning_profile = select({
|
||||
"//:adhoc_build": None,
|
||||
"//conditions:default": "//profiles:daemon_dev",
|
||||
@@ -912,10 +936,6 @@ santa_unit_test(
|
||||
santa_unit_test(
|
||||
name = "SantadTest",
|
||||
srcs = ["SantadTest.mm"],
|
||||
data = [
|
||||
"//Source/santad/testdata:binaryrules_testdata",
|
||||
],
|
||||
minimum_os_version = "11.0",
|
||||
sdk_dylibs = [
|
||||
"bsm",
|
||||
"EndpointSecurity",
|
||||
@@ -923,6 +943,9 @@ santa_unit_test(
|
||||
sdk_frameworks = [
|
||||
"DiskArbitration",
|
||||
],
|
||||
structured_resources = [
|
||||
"//Source/santad/testdata:binaryrules_testdata",
|
||||
],
|
||||
tags = ["exclusive"],
|
||||
deps = [
|
||||
":EndpointSecurityMessage",
|
||||
@@ -948,7 +971,6 @@ santa_unit_test(
|
||||
srcs = [
|
||||
"SNTApplicationCoreMetricsTest.mm",
|
||||
],
|
||||
minimum_os_version = "11.0",
|
||||
deps = [
|
||||
":SNTApplicationCoreMetrics",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
@@ -1001,6 +1023,7 @@ santa_unit_test(
|
||||
":EndpointSecuritySerializerBasicString",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
@@ -1014,7 +1037,7 @@ santa_unit_test(
|
||||
santa_unit_test(
|
||||
name = "EndpointSecuritySerializerProtobufTest",
|
||||
srcs = ["Logs/EndpointSecurity/Serializers/ProtobufTest.mm"],
|
||||
data = [
|
||||
structured_resources = [
|
||||
"//Source/santad/testdata:protobuf_json_testdata",
|
||||
],
|
||||
deps = [
|
||||
@@ -1025,6 +1048,7 @@ santa_unit_test(
|
||||
":EndpointSecuritySerializerProtobuf",
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTDecisionCache",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTConfigurator",
|
||||
@@ -1169,7 +1193,10 @@ santa_unit_test(
|
||||
name = "MetricsTest",
|
||||
srcs = ["MetricsTest.mm"],
|
||||
deps = [
|
||||
":EndpointSecurityMessage",
|
||||
":Metrics",
|
||||
":MockEndpointSecurityAPI",
|
||||
"//Source/common:SNTCommonEnums",
|
||||
"//Source/common:SNTMetricSet",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
@@ -1334,6 +1361,7 @@ santa_unit_test(
|
||||
":MockEndpointSecurityAPI",
|
||||
":SNTCompilerController",
|
||||
":SNTEndpointSecurityRecorder",
|
||||
"//Source/common:Platform",
|
||||
"//Source/common:PrefixTree",
|
||||
"//Source/common:SNTConfigurator",
|
||||
"//Source/common:TestUtils",
|
||||
@@ -1416,10 +1444,13 @@ test_suite(
|
||||
":SNTEndpointSecurityTamperResistanceTest",
|
||||
":SNTEventTableTest",
|
||||
":SNTExecutionControllerTest",
|
||||
":SNTPolicyProcessorTest",
|
||||
":SNTRuleTableTest",
|
||||
":SantadTest",
|
||||
":WatchItemsTest",
|
||||
"//Source/santad/Logs/EndpointSecurity/Writers/FSSpool:fsspool_test",
|
||||
"//Source/santad/ProcessTree:process_tree_test",
|
||||
"//Source/santad/ProcessTree/annotations:originator_test",
|
||||
],
|
||||
visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
@@ -33,13 +33,7 @@ static const int64_t kTransitiveRuleCullingThreshold = 500000;
|
||||
// Consider transitive rules out of date if they haven't been used in six months.
|
||||
static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
|
||||
|
||||
static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABLE(macos(12.0)) {
|
||||
// Note: This function uses API introduced in macOS 12, but we want to continue to support
|
||||
// building in older environments. API Availability checks do not help for this use case,
|
||||
// instead we use the following preprocessor macros to conditionally compile these API. The
|
||||
// drawback here is that if a pre-macOS 12 SDK is used to build Santa and it is then deployed
|
||||
// on macOS 12 or later, the dynamic mute set will not be computed.
|
||||
#if HAVE_MACOS_12
|
||||
static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) {
|
||||
// Create a temporary ES client in order to grab the default set of muted paths.
|
||||
// TODO(mlw): Reorganize this code so that a temporary ES client doesn't need to be created
|
||||
es_client_t *client = NULL;
|
||||
@@ -69,7 +63,6 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
|
||||
es_release_muted_paths(mps);
|
||||
es_delete_client(client);
|
||||
#endif
|
||||
}
|
||||
|
||||
@interface SNTRuleTable ()
|
||||
@@ -125,10 +118,8 @@ static void addPathsFromDefaultMuteSet(NSMutableSet *criticalPaths) API_AVAILABL
|
||||
NSMutableSet *superSet = [NSMutableSet setWithSet:fallbackDefaultMuteSet];
|
||||
[superSet unionSet:santaDefinedCriticalPaths];
|
||||
|
||||
if (@available(macOS 12.0, *)) {
|
||||
// Attempt to add the real default mute set
|
||||
addPathsFromDefaultMuteSet(superSet);
|
||||
}
|
||||
// Attempt to add the real default mute set
|
||||
addPathsFromDefaultMuteSet(superSet);
|
||||
|
||||
criticalPaths = [superSet allObjects];
|
||||
});
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
namespace santa::santad::data_layer {
|
||||
namespace santa {
|
||||
|
||||
enum class WatchItemPathType {
|
||||
kPrefix,
|
||||
@@ -117,6 +117,6 @@ struct WatchItemPolicy {
|
||||
std::string version = "temp_version";
|
||||
};
|
||||
|
||||
} // namespace santa::santad::data_layer
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -52,11 +52,11 @@ extern NSString *const kWatchItemConfigKeyProcessesCDHash;
|
||||
extern NSString *const kWatchItemConfigKeyProcessesPlatformBinary;
|
||||
|
||||
// Forward declarations
|
||||
namespace santa::santad::data_layer {
|
||||
namespace santa {
|
||||
class WatchItemsPeer;
|
||||
}
|
||||
|
||||
namespace santa::santad::data_layer {
|
||||
namespace santa {
|
||||
|
||||
struct WatchItemsState {
|
||||
uint64_t rule_count;
|
||||
@@ -69,7 +69,7 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
|
||||
public:
|
||||
using VersionAndPolicies =
|
||||
std::pair<std::string, std::vector<std::optional<std::shared_ptr<WatchItemPolicy>>>>;
|
||||
using WatchItemsTree = santa::common::PrefixTree<std::shared_ptr<WatchItemPolicy>>;
|
||||
using WatchItemsTree = santa::PrefixTree<std::shared_ptr<WatchItemPolicy>>;
|
||||
|
||||
// Factory
|
||||
static std::shared_ptr<WatchItems> Create(NSString *config_path,
|
||||
@@ -99,7 +99,7 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
|
||||
std::pair<NSString *, NSString *> EventDetailLinkInfo(
|
||||
const std::shared_ptr<WatchItemPolicy> &watch_item);
|
||||
|
||||
friend class santa::santad::data_layer::WatchItemsPeer;
|
||||
friend class santa::WatchItemsPeer;
|
||||
|
||||
private:
|
||||
static std::shared_ptr<WatchItems> CreateInternal(NSString *config_path, NSDictionary *config,
|
||||
@@ -135,6 +135,6 @@ class WatchItems : public std::enable_shared_from_this<WatchItems> {
|
||||
NSString *policy_event_detail_text_ ABSL_GUARDED_BY(lock_);
|
||||
};
|
||||
|
||||
} // namespace santa::santad::data_layer
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -38,12 +38,12 @@
|
||||
#import "Source/common/Unit.h"
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
|
||||
using santa::common::NSStringToUTF8String;
|
||||
using santa::common::NSStringToUTF8StringView;
|
||||
using santa::common::PrefixTree;
|
||||
using santa::common::Unit;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::data_layer::WatchItemPolicy;
|
||||
using santa::NSStringToUTF8String;
|
||||
using santa::NSStringToUTF8StringView;
|
||||
using santa::PrefixTree;
|
||||
using santa::Unit;
|
||||
using santa::WatchItemPathType;
|
||||
using santa::WatchItemPolicy;
|
||||
|
||||
NSString *const kWatchItemConfigKeyVersion = @"Version";
|
||||
NSString *const kWatchItemConfigKeyEventDetailURL = @"EventDetailURL";
|
||||
@@ -96,13 +96,15 @@ static constexpr NSUInteger kWatchItemConfigEventDetailTextMaxLength = 48;
|
||||
// max is used here.
|
||||
static constexpr NSUInteger kWatchItemConfigEventDetailURLMaxLength = 6000;
|
||||
|
||||
namespace santa::santad::data_layer {
|
||||
namespace santa {
|
||||
|
||||
namespace {
|
||||
// Type aliases
|
||||
using ValidatorBlock = bool (^)(id, NSError **);
|
||||
using PathAndTypePair = std::pair<std::string, WatchItemPathType>;
|
||||
using PathList = std::vector<PathAndTypePair>;
|
||||
using ProcessList = std::vector<WatchItemPolicy::Process>;
|
||||
using PathAndTypeVec = std::vector<PathAndTypePair>;
|
||||
using PolicyProcessVec = std::vector<WatchItemPolicy::Process>;
|
||||
} // namespace
|
||||
|
||||
static void PopulateError(NSError **err, NSString *msg) {
|
||||
if (err) {
|
||||
@@ -265,8 +267,8 @@ bool VerifyConfigKeyArray(NSDictionary *dict, NSString *key, Class expected, NSE
|
||||
/// <true/>
|
||||
/// </dict>
|
||||
/// </array>
|
||||
std::variant<Unit, PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSError **err) {
|
||||
PathList path_list;
|
||||
std::variant<Unit, PathAndTypeVec> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSError **err) {
|
||||
PathAndTypeVec path_list;
|
||||
|
||||
for (id path in paths) {
|
||||
if ([path isKindOfClass:[NSDictionary class]]) {
|
||||
@@ -336,9 +338,9 @@ std::variant<Unit, PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths, NSEr
|
||||
/// <string>EEEE</string>
|
||||
/// </dict>
|
||||
/// </array>
|
||||
std::variant<Unit, ProcessList> VerifyConfigWatchItemProcesses(NSDictionary *watch_item,
|
||||
NSError **err) {
|
||||
__block ProcessList proc_list;
|
||||
std::variant<Unit, PolicyProcessVec> VerifyConfigWatchItemProcesses(NSDictionary *watch_item,
|
||||
NSError **err) {
|
||||
__block PolicyProcessVec proc_list;
|
||||
|
||||
if (!VerifyConfigKeyArray(
|
||||
watch_item, kWatchItemConfigKeyProcesses, [NSDictionary class], err,
|
||||
@@ -429,7 +431,7 @@ bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
|
||||
return false;
|
||||
}
|
||||
|
||||
std::variant<Unit, PathList> path_list =
|
||||
std::variant<Unit, PathAndTypeVec> path_list =
|
||||
VerifyConfigWatchItemPaths(watch_item[kWatchItemConfigKeyPaths], err);
|
||||
|
||||
if (std::holds_alternative<Unit>(path_list)) {
|
||||
@@ -485,19 +487,19 @@ bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
|
||||
bool enable_silent_tty_mode = GetBoolValue(options, kWatchItemConfigKeyOptionsEnableSilentTTYMode,
|
||||
kWatchItemPolicyDefaultEnableSilentTTYMode);
|
||||
|
||||
std::variant<Unit, ProcessList> proc_list = VerifyConfigWatchItemProcesses(watch_item, err);
|
||||
std::variant<Unit, PolicyProcessVec> proc_list = VerifyConfigWatchItemProcesses(watch_item, err);
|
||||
if (std::holds_alternative<Unit>(proc_list)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const PathAndTypePair &path_type_pair : std::get<PathList>(path_list)) {
|
||||
for (const PathAndTypePair &path_type_pair : std::get<PathAndTypeVec>(path_list)) {
|
||||
policies.push_back(std::make_shared<WatchItemPolicy>(
|
||||
NSStringToUTF8StringView(name), path_type_pair.first, path_type_pair.second,
|
||||
allow_read_access, audit_only, invert_process_exceptions, enable_silent_mode,
|
||||
enable_silent_tty_mode,
|
||||
NSStringToUTF8StringView(options[kWatchItemConfigKeyOptionsCustomMessage]),
|
||||
options[kWatchItemConfigKeyOptionsEventDetailURL],
|
||||
options[kWatchItemConfigKeyOptionsEventDetailText], std::get<ProcessList>(proc_list)));
|
||||
options[kWatchItemConfigKeyOptionsEventDetailText], std::get<PolicyProcessVec>(proc_list)));
|
||||
}
|
||||
|
||||
return true;
|
||||
@@ -897,4 +899,4 @@ std::pair<NSString *, NSString *> WatchItems::EventDetailLinkInfo(
|
||||
return {url, text};
|
||||
}
|
||||
|
||||
} // namespace santa::santad::data_layer
|
||||
} // namespace santa
|
||||
|
||||
@@ -32,23 +32,23 @@
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#include "Source/santad/DataLayer/WatchItems.h"
|
||||
|
||||
using santa::common::Unit;
|
||||
using santa::santad::data_layer::kWatchItemPolicyDefaultAllowReadAccess;
|
||||
using santa::santad::data_layer::kWatchItemPolicyDefaultAuditOnly;
|
||||
using santa::santad::data_layer::kWatchItemPolicyDefaultInvertProcessExceptions;
|
||||
using santa::santad::data_layer::kWatchItemPolicyDefaultPathType;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::data_layer::WatchItemPolicy;
|
||||
using santa::santad::data_layer::WatchItems;
|
||||
using santa::santad::data_layer::WatchItemsState;
|
||||
using santa::kWatchItemPolicyDefaultAllowReadAccess;
|
||||
using santa::kWatchItemPolicyDefaultAuditOnly;
|
||||
using santa::kWatchItemPolicyDefaultInvertProcessExceptions;
|
||||
using santa::kWatchItemPolicyDefaultPathType;
|
||||
using santa::Unit;
|
||||
using santa::WatchItemPathType;
|
||||
using santa::WatchItemPolicy;
|
||||
using santa::WatchItems;
|
||||
using santa::WatchItemsState;
|
||||
|
||||
namespace santatest {
|
||||
namespace {
|
||||
using PathAndTypePair = std::pair<std::string, WatchItemPathType>;
|
||||
using PathList = std::vector<PathAndTypePair>;
|
||||
using ProcessList = std::vector<WatchItemPolicy::Process>;
|
||||
} // namespace santatest
|
||||
using PathAndTypeVec = std::vector<PathAndTypePair>;
|
||||
using PolicyProcessVec = std::vector<WatchItemPolicy::Process>;
|
||||
} // namespace
|
||||
|
||||
namespace santa::santad::data_layer {
|
||||
namespace santa {
|
||||
|
||||
extern bool ParseConfig(NSDictionary *config,
|
||||
std::vector<std::shared_ptr<WatchItemPolicy>> &policies, NSError **err);
|
||||
@@ -56,10 +56,10 @@ extern bool IsWatchItemNameValid(NSString *watch_item_name, NSError **err);
|
||||
extern bool ParseConfigSingleWatchItem(NSString *name, NSDictionary *watch_item,
|
||||
std::vector<std::shared_ptr<WatchItemPolicy>> &policies,
|
||||
NSError **err);
|
||||
extern std::variant<Unit, santatest::PathList> VerifyConfigWatchItemPaths(NSArray<id> *paths,
|
||||
NSError **err);
|
||||
extern std::variant<Unit, santatest::ProcessList> VerifyConfigWatchItemProcesses(
|
||||
NSDictionary *watch_item, NSError **err);
|
||||
extern std::variant<Unit, PathAndTypeVec> VerifyConfigWatchItemPaths(NSArray<id> *paths,
|
||||
NSError **err);
|
||||
extern std::variant<Unit, PolicyProcessVec> VerifyConfigWatchItemProcesses(NSDictionary *watch_item,
|
||||
NSError **err);
|
||||
class WatchItemsPeer : public WatchItems {
|
||||
public:
|
||||
using WatchItems::WatchItems;
|
||||
@@ -72,14 +72,14 @@ class WatchItemsPeer : public WatchItems {
|
||||
using WatchItems::embedded_config_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::data_layer
|
||||
} // namespace santa
|
||||
|
||||
using santa::santad::data_layer::IsWatchItemNameValid;
|
||||
using santa::santad::data_layer::ParseConfig;
|
||||
using santa::santad::data_layer::ParseConfigSingleWatchItem;
|
||||
using santa::santad::data_layer::VerifyConfigWatchItemPaths;
|
||||
using santa::santad::data_layer::VerifyConfigWatchItemProcesses;
|
||||
using santa::santad::data_layer::WatchItemsPeer;
|
||||
using santa::IsWatchItemNameValid;
|
||||
using santa::ParseConfig;
|
||||
using santa::ParseConfigSingleWatchItem;
|
||||
using santa::VerifyConfigWatchItemPaths;
|
||||
using santa::VerifyConfigWatchItemProcesses;
|
||||
using santa::WatchItemsPeer;
|
||||
|
||||
static constexpr std::string_view kBadPolicyName("__BAD_NAME__");
|
||||
static constexpr std::string_view kBadPolicyPath("__BAD_PATH__");
|
||||
@@ -440,7 +440,7 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
|
||||
}
|
||||
|
||||
- (void)testVerifyConfigWatchItemPaths {
|
||||
std::variant<Unit, santatest::PathList> path_list;
|
||||
std::variant<Unit, PathAndTypeVec> path_list;
|
||||
NSError *err;
|
||||
|
||||
// Test no paths specified
|
||||
@@ -466,29 +466,28 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
|
||||
|
||||
// Test path array dictionary with default path type
|
||||
path_list = VerifyConfigWatchItemPaths(@[ @{kWatchItemConfigKeyPathsPath : @"A"} ], &err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::PathList>(path_list));
|
||||
XCTAssertEqual(std::get<santatest::PathList>(path_list).size(), 1);
|
||||
XCTAssertCStringEqual(std::get<santatest::PathList>(path_list)[0].first.c_str(), "A");
|
||||
XCTAssertEqual(std::get<santatest::PathList>(path_list)[0].second,
|
||||
kWatchItemPolicyDefaultPathType);
|
||||
XCTAssertTrue(std::holds_alternative<PathAndTypeVec>(path_list));
|
||||
XCTAssertEqual(std::get<PathAndTypeVec>(path_list).size(), 1);
|
||||
XCTAssertCStringEqual(std::get<PathAndTypeVec>(path_list)[0].first.c_str(), "A");
|
||||
XCTAssertEqual(std::get<PathAndTypeVec>(path_list)[0].second, kWatchItemPolicyDefaultPathType);
|
||||
|
||||
// Test path array dictionary with custom path type
|
||||
path_list = VerifyConfigWatchItemPaths(
|
||||
@[ @{kWatchItemConfigKeyPathsPath : @"A", kWatchItemConfigKeyPathsIsPrefix : @(YES)} ], &err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::PathList>(path_list));
|
||||
XCTAssertEqual(std::get<santatest::PathList>(path_list).size(), 1);
|
||||
XCTAssertCStringEqual(std::get<santatest::PathList>(path_list)[0].first.c_str(), "A");
|
||||
XCTAssertEqual(std::get<santatest::PathList>(path_list)[0].second, WatchItemPathType::kPrefix);
|
||||
XCTAssertTrue(std::holds_alternative<PathAndTypeVec>(path_list));
|
||||
XCTAssertEqual(std::get<PathAndTypeVec>(path_list).size(), 1);
|
||||
XCTAssertCStringEqual(std::get<PathAndTypeVec>(path_list)[0].first.c_str(), "A");
|
||||
XCTAssertEqual(std::get<PathAndTypeVec>(path_list)[0].second, WatchItemPathType::kPrefix);
|
||||
}
|
||||
|
||||
- (void)testVerifyConfigWatchItemProcesses {
|
||||
std::variant<Unit, santatest::ProcessList> proc_list;
|
||||
std::variant<Unit, PolicyProcessVec> proc_list;
|
||||
NSError *err;
|
||||
|
||||
// Non-existent process list parses successfully, but has no items
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{}, &err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 0);
|
||||
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 0);
|
||||
|
||||
// Process list fails to parse if contains non-array type
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @""}, &err);
|
||||
@@ -498,7 +497,7 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @{}}, &err);
|
||||
XCTAssertTrue(std::holds_alternative<Unit>(proc_list));
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @[]}, &err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
|
||||
|
||||
// Test a process dictionary with no valid attributes set
|
||||
proc_list = VerifyConfigWatchItemProcesses(@{kWatchItemConfigKeyProcesses : @[ @{} ]}, &err);
|
||||
@@ -516,9 +515,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
|
||||
proc_list = VerifyConfigWatchItemProcesses(
|
||||
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesBinaryPath : @"mypath"} ]},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
|
||||
WatchItemPolicy::Process("mypath", "", "", {}, "", std::nullopt));
|
||||
|
||||
// Test SigningID length limits
|
||||
@@ -535,9 +534,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
|
||||
@[ @{kWatchItemConfigKeyProcessesSigningID : @"com.google.test"} ]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
|
||||
WatchItemPolicy::Process("", "com.google.test", "", {}, "", std::nullopt));
|
||||
|
||||
// Test TeamID length limits
|
||||
@@ -552,9 +551,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
|
||||
proc_list = VerifyConfigWatchItemProcesses(
|
||||
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesTeamID : @"myvalidtid"} ]},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
|
||||
WatchItemPolicy::Process("", "", "myvalidtid", {}, "", std::nullopt));
|
||||
|
||||
// Test CDHash length limits
|
||||
@@ -579,9 +578,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
|
||||
std::fill(cdhashBytes.begin(), cdhashBytes.end(), 0xAA);
|
||||
proc_list = VerifyConfigWatchItemProcesses(
|
||||
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesCDHash : cdhash} ]}, &err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
|
||||
WatchItemPolicy::Process("", "", "", cdhashBytes, "", std::nullopt));
|
||||
|
||||
// Test Cert Hash length limits
|
||||
@@ -610,9 +609,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
|
||||
kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesCertificateSha256 : certHash} ]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
|
||||
WatchItemPolicy::Process("", "", "", {}, [certHash UTF8String], std::nullopt));
|
||||
|
||||
// Test valid invalid PlatformBinary type
|
||||
@@ -625,9 +624,9 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
|
||||
proc_list = VerifyConfigWatchItemProcesses(
|
||||
@{kWatchItemConfigKeyProcesses : @[ @{kWatchItemConfigKeyProcessesPlatformBinary : @(YES)} ]},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 1);
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
|
||||
WatchItemPolicy::Process("", "", "", {}, "", std::make_optional(true)));
|
||||
|
||||
// Test valid multiple attributes, multiple procs
|
||||
@@ -652,12 +651,12 @@ static NSMutableDictionary *WrapWatchItemsConfig(NSDictionary *config) {
|
||||
]
|
||||
},
|
||||
&err);
|
||||
XCTAssertTrue(std::holds_alternative<santatest::ProcessList>(proc_list));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list).size(), 2);
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[0],
|
||||
XCTAssertTrue(std::holds_alternative<PolicyProcessVec>(proc_list));
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list).size(), 2);
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[0],
|
||||
WatchItemPolicy::Process("mypath1", "com.google.test1", "validtid_1", cdhashBytes,
|
||||
[certHash UTF8String], std::make_optional(true)));
|
||||
XCTAssertEqual(std::get<santatest::ProcessList>(proc_list)[1],
|
||||
XCTAssertEqual(std::get<PolicyProcessVec>(proc_list)[1],
|
||||
WatchItemPolicy::Process("mypath2", "com.google.test2", "validtid_2", cdhashBytes,
|
||||
[certHash UTF8String], std::make_optional(false)));
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@
|
||||
#import "Source/common/SantaVnode.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
|
||||
|
||||
namespace santa::santad::event_providers {
|
||||
namespace santa {
|
||||
|
||||
enum class FlushCacheMode {
|
||||
kNonRootOnly,
|
||||
@@ -52,13 +52,12 @@ class AuthResultCache {
|
||||
// previously denied binary is allowed, it can be re-executed by the user in a
|
||||
// timely manner. But the value should be high enough to allow the cache to be
|
||||
// effective in the event the binary is executed in rapid succession.
|
||||
static std::unique_ptr<AuthResultCache> Create(
|
||||
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
|
||||
SNTMetricSet *metric_set, uint64_t cache_deny_time_ms = 1500);
|
||||
static std::unique_ptr<AuthResultCache> Create(std::shared_ptr<santa::EndpointSecurityAPI> esapi,
|
||||
SNTMetricSet *metric_set,
|
||||
uint64_t cache_deny_time_ms = 1500);
|
||||
|
||||
AuthResultCache(
|
||||
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi,
|
||||
SNTMetricCounter *flush_count, uint64_t cache_deny_time_ms = 1500);
|
||||
AuthResultCache(std::shared_ptr<santa::EndpointSecurityAPI> esapi, SNTMetricCounter *flush_count,
|
||||
uint64_t cache_deny_time_ms = 1500);
|
||||
virtual ~AuthResultCache();
|
||||
|
||||
AuthResultCache(AuthResultCache &&other) = delete;
|
||||
@@ -81,13 +80,13 @@ class AuthResultCache {
|
||||
SantaCache<SantaVnode, uint64_t> *root_cache_;
|
||||
SantaCache<SantaVnode, uint64_t> *nonroot_cache_;
|
||||
|
||||
std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI> esapi_;
|
||||
std::shared_ptr<santa::EndpointSecurityAPI> esapi_;
|
||||
SNTMetricCounter *flush_count_;
|
||||
uint64_t root_devno_;
|
||||
uint64_t cache_deny_time_ns_;
|
||||
dispatch_queue_t q_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
#import "Source/common/SantaVnodeHash.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
using santa::Client;
|
||||
using santa::EndpointSecurityAPI;
|
||||
|
||||
static NSString *const kFlushCacheReasonClientModeChanged = @"ClientModeChanged";
|
||||
static NSString *const kFlushCacheReasonPathRegexChanged = @"PathRegexChanged";
|
||||
@@ -36,7 +36,7 @@ static NSString *const kFlushCacheReasonEntitlementsPrefixFilterChanged =
|
||||
static NSString *const kFlushCacheReasonEntitlementsTeamIDFilterChanged =
|
||||
@"EntitlementsTeamIDFilterChanged";
|
||||
|
||||
namespace santa::santad::event_providers {
|
||||
namespace santa {
|
||||
|
||||
static inline uint64_t GetCurrentUptime() {
|
||||
return clock_gettime_nsec_np(CLOCK_MONOTONIC);
|
||||
@@ -185,4 +185,4 @@ NSArray<NSNumber *> *AuthResultCache::CacheCounts() {
|
||||
return @[ @(root_cache_->count()), @(nonroot_cache_->count()) ];
|
||||
}
|
||||
|
||||
} // namespace santa::santad::event_providers
|
||||
} // namespace santa
|
||||
|
||||
@@ -27,15 +27,15 @@
|
||||
#include "Source/santad/EventProviders/AuthResultCache.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h"
|
||||
|
||||
using santa::santad::event_providers::AuthResultCache;
|
||||
using santa::santad::event_providers::FlushCacheMode;
|
||||
using santa::santad::event_providers::FlushCacheReason;
|
||||
using santa::AuthResultCache;
|
||||
using santa::FlushCacheMode;
|
||||
using santa::FlushCacheReason;
|
||||
|
||||
namespace santa::santad::event_providers {
|
||||
namespace santa {
|
||||
extern NSString *const FlushCacheReasonToString(FlushCacheReason reason);
|
||||
}
|
||||
} // namespace santa
|
||||
|
||||
using santa::santad::event_providers::FlushCacheReasonToString;
|
||||
using santa::FlushCacheReasonToString;
|
||||
|
||||
// Grab the st_dev number of the root volume to match the root cache
|
||||
static uint64_t RootDevno() {
|
||||
|
||||
@@ -150,15 +150,18 @@ void DARegisterDiskAppearedCallback(DASessionRef session, CFDictionaryRef __null
|
||||
}];
|
||||
}
|
||||
|
||||
// Disabling clang format due to local/remote version differences.
|
||||
// clang-format off
|
||||
void DARegisterDiskDisappearedCallback(DASessionRef session, CFDictionaryRef __nullable match,
|
||||
DADiskDisappearedCallback callback,
|
||||
void *__nullable context) {};
|
||||
void *__nullable context) {}
|
||||
|
||||
void DARegisterDiskDescriptionChangedCallback(DASessionRef session,
|
||||
CFDictionaryRef __nullable match,
|
||||
CFArrayRef __nullable watch,
|
||||
DADiskDescriptionChangedCallback callback,
|
||||
void *__nullable context) {};
|
||||
void *__nullable context) {}
|
||||
// clang-format on
|
||||
|
||||
void DASessionSetDispatchQueue(DASessionRef session, dispatch_queue_t __nullable queue) {
|
||||
MockDiskArbitration *mockDA = [MockDiskArbitration mockDiskArbitration];
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
namespace santa {
|
||||
|
||||
class Client {
|
||||
public:
|
||||
@@ -64,6 +64,6 @@ class Client {
|
||||
es_new_client_result_t result_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
using santa::Client;
|
||||
|
||||
// Global semaphore used for custom `es_delete_client` function
|
||||
dispatch_semaphore_t gSema;
|
||||
|
||||
@@ -25,7 +25,7 @@
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Client.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
namespace santa {
|
||||
|
||||
class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurityAPI> {
|
||||
public:
|
||||
@@ -43,9 +43,9 @@ class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurity
|
||||
virtual bool InvertTargetPathMuting(const Client &client);
|
||||
|
||||
virtual bool MuteTargetPath(const Client &client, std::string_view path,
|
||||
santa::santad::data_layer::WatchItemPathType path_type);
|
||||
santa::WatchItemPathType path_type);
|
||||
virtual bool UnmuteTargetPath(const Client &client, std::string_view path,
|
||||
santa::santad::data_layer::WatchItemPathType path_type);
|
||||
santa::WatchItemPathType path_type);
|
||||
|
||||
virtual void RetainMessage(const es_message_t *msg);
|
||||
virtual void ReleaseMessage(const es_message_t *msg);
|
||||
@@ -69,6 +69,6 @@ class EndpointSecurityAPI : public std::enable_shared_from_this<EndpointSecurity
|
||||
virtual const es_fd_t *ExecFD(const es_event_exec_t *event, uint32_t index);
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
|
||||
#include "Source/common/Platform.h"
|
||||
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::WatchItemPathType;
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
namespace santa {
|
||||
|
||||
Client EndpointSecurityAPI::NewClient(void (^message_handler)(es_client_t *, Message)) {
|
||||
es_client_t *client = NULL;
|
||||
@@ -156,19 +156,11 @@ es_string_token_t EndpointSecurityAPI::ExecEnv(const es_event_exec_t *event, uin
|
||||
}
|
||||
|
||||
uint32_t EndpointSecurityAPI::ExecFDCount(const es_event_exec_t *event) {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
return es_exec_fd_count(event);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
return es_exec_fd_count(event);
|
||||
}
|
||||
|
||||
const es_fd_t *EndpointSecurityAPI::ExecFD(const es_event_exec_t *event, uint32_t index) {
|
||||
if (@available(macOS 11.0, *)) {
|
||||
return es_exec_fd(event, index);
|
||||
} else {
|
||||
return NULL;
|
||||
}
|
||||
return es_exec_fd(event, index);
|
||||
}
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
} // namespace santa
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
#include "Source/santad/ProcessTree/process_tree.pb.h"
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
namespace santa {
|
||||
|
||||
class EnrichedFile {
|
||||
public:
|
||||
@@ -151,7 +151,8 @@ class EnrichedEventType {
|
||||
|
||||
virtual ~EnrichedEventType() = default;
|
||||
|
||||
const es_message_t &es_msg() const { return *es_msg_; }
|
||||
inline const es_message_t *operator->() const { return es_msg_.operator->(); }
|
||||
|
||||
const EnrichedProcess &instigator() const { return instigator_; }
|
||||
struct timespec enrichment_time() const {
|
||||
// No reason to return a reference
|
||||
@@ -340,10 +341,108 @@ class EnrichedCSInvalidated : public EnrichedEventType {
|
||||
EnrichedCSInvalidated(const EnrichedCSInvalidated &other) = delete;
|
||||
};
|
||||
|
||||
using EnrichedType =
|
||||
std::variant<EnrichedClose, EnrichedExchange, EnrichedExec, EnrichedExit,
|
||||
EnrichedFork, EnrichedLink, EnrichedRename, EnrichedUnlink,
|
||||
EnrichedCSInvalidated>;
|
||||
// Note: All EnrichedLoginWindowSession* classes currently have the same
|
||||
// data and implementation. To improve maintainability but still provide
|
||||
// individual types, an internal EnrichedLoginWindowSession base class is
|
||||
// defined that is derived by each desired types.
|
||||
// EnrichedLoginWindowSession is wrapped in an `internal` namespace as it
|
||||
// shouldn't be directly used outside of this file.
|
||||
namespace internal {
|
||||
|
||||
class EnrichedLoginWindowSession : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedLoginWindowSession(Message &&es_msg, EnrichedProcess instigator,
|
||||
std::optional<uid_t> uid)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)),
|
||||
uid_(std::move(uid)) {}
|
||||
|
||||
EnrichedLoginWindowSession(EnrichedLoginWindowSession &&) = default;
|
||||
|
||||
virtual ~EnrichedLoginWindowSession() = default;
|
||||
inline std::optional<uid_t> UID() const { return uid_; }
|
||||
|
||||
private:
|
||||
std::optional<uid_t> uid_;
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
class EnrichedLoginWindowSessionLogin
|
||||
: public internal::EnrichedLoginWindowSession {
|
||||
using EnrichedLoginWindowSession::EnrichedLoginWindowSession;
|
||||
};
|
||||
|
||||
class EnrichedLoginWindowSessionLogout
|
||||
: public internal::EnrichedLoginWindowSession {
|
||||
using EnrichedLoginWindowSession::EnrichedLoginWindowSession;
|
||||
};
|
||||
|
||||
class EnrichedLoginWindowSessionLock
|
||||
: public internal::EnrichedLoginWindowSession {
|
||||
using EnrichedLoginWindowSession::EnrichedLoginWindowSession;
|
||||
};
|
||||
|
||||
class EnrichedLoginWindowSessionUnlock
|
||||
: public internal::EnrichedLoginWindowSession {
|
||||
using EnrichedLoginWindowSession::EnrichedLoginWindowSession;
|
||||
};
|
||||
|
||||
class EnrichedScreenSharingAttach : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedScreenSharingAttach(Message &&es_msg, EnrichedProcess instigator)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
|
||||
|
||||
EnrichedScreenSharingAttach(EnrichedScreenSharingAttach &&) = default;
|
||||
};
|
||||
|
||||
class EnrichedScreenSharingDetach : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedScreenSharingDetach(Message &&es_msg, EnrichedProcess instigator)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
|
||||
|
||||
EnrichedScreenSharingDetach(EnrichedScreenSharingDetach &&) = default;
|
||||
};
|
||||
|
||||
class EnrichedOpenSSHLogin : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedOpenSSHLogin(Message &&es_msg, EnrichedProcess instigator)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
|
||||
|
||||
EnrichedOpenSSHLogin(EnrichedOpenSSHLogin &&) = default;
|
||||
};
|
||||
|
||||
class EnrichedOpenSSHLogout : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedOpenSSHLogout(Message &&es_msg, EnrichedProcess instigator)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
|
||||
|
||||
EnrichedOpenSSHLogout(EnrichedOpenSSHLogout &&) = default;
|
||||
};
|
||||
|
||||
class EnrichedLoginLogin : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedLoginLogin(Message &&es_msg, EnrichedProcess instigator)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
|
||||
|
||||
EnrichedLoginLogin(EnrichedLoginLogin &&) = default;
|
||||
};
|
||||
|
||||
class EnrichedLoginLogout : public EnrichedEventType {
|
||||
public:
|
||||
EnrichedLoginLogout(Message &&es_msg, EnrichedProcess instigator)
|
||||
: EnrichedEventType(std::move(es_msg), std::move(instigator)) {}
|
||||
|
||||
EnrichedLoginLogout(EnrichedLoginLogout &&) = default;
|
||||
};
|
||||
|
||||
using EnrichedType = std::variant<
|
||||
EnrichedClose, EnrichedExchange, EnrichedExec, EnrichedExit, EnrichedFork,
|
||||
EnrichedLink, EnrichedRename, EnrichedUnlink, EnrichedCSInvalidated,
|
||||
EnrichedLoginWindowSessionLogin, EnrichedLoginWindowSessionLogout,
|
||||
EnrichedLoginWindowSessionLock, EnrichedLoginWindowSessionUnlock,
|
||||
EnrichedScreenSharingAttach, EnrichedScreenSharingDetach,
|
||||
EnrichedOpenSSHLogin, EnrichedOpenSSHLogout, EnrichedLoginLogin,
|
||||
EnrichedLoginLogout>;
|
||||
|
||||
class EnrichedMessage {
|
||||
public:
|
||||
@@ -355,6 +454,6 @@ class EnrichedMessage {
|
||||
EnrichedType msg_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -15,12 +15,13 @@
|
||||
#define SANTA__SANTAD__EVENTPROVIDERS_ENDPOINTSECURITY_ENRICHER_H
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#include "Source/common/SantaCache.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
|
||||
#include "Source/santad/ProcessTree/process_tree.h"
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
namespace santa {
|
||||
|
||||
enum class EnrichOptions {
|
||||
// Specifies default enricher operation.
|
||||
@@ -33,7 +34,8 @@ enum class EnrichOptions {
|
||||
|
||||
class Enricher {
|
||||
public:
|
||||
Enricher(std::shared_ptr<process_tree::ProcessTree> pt = nullptr);
|
||||
Enricher(
|
||||
std::shared_ptr<santa::santad::process_tree::ProcessTree> pt = nullptr);
|
||||
virtual ~Enricher() = default;
|
||||
virtual std::unique_ptr<EnrichedMessage> Enrich(Message &&msg);
|
||||
virtual EnrichedProcess Enrich(
|
||||
@@ -47,14 +49,19 @@ class Enricher {
|
||||
virtual std::optional<std::shared_ptr<std::string>> UsernameForGID(
|
||||
gid_t gid, EnrichOptions options = EnrichOptions::kDefault);
|
||||
|
||||
// This method does not chache. It should not be used on a hot path.
|
||||
virtual std::optional<uid_t> UIDForUsername(
|
||||
std::string_view username,
|
||||
EnrichOptions options = EnrichOptions::kDefault);
|
||||
|
||||
private:
|
||||
SantaCache<uid_t, std::optional<std::shared_ptr<std::string>>>
|
||||
username_cache_;
|
||||
SantaCache<gid_t, std::optional<std::shared_ptr<std::string>>>
|
||||
groupname_cache_;
|
||||
std::shared_ptr<process_tree::ProcessTree> process_tree_;
|
||||
std::shared_ptr<santa::santad::process_tree::ProcessTree> process_tree_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -23,13 +23,17 @@
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
|
||||
#include "Source/common/Platform.h"
|
||||
#include "Source/common/SNTLogging.h"
|
||||
#include "Source/common/String.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EnrichedTypes.h"
|
||||
#include "Source/santad/ProcessTree/SNTEndpointSecurityAdapter.h"
|
||||
#include "Source/santad/ProcessTree/process_tree.h"
|
||||
#include "Source/santad/ProcessTree/process_tree_macos.h"
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
using santa::StringTokenToStringView;
|
||||
|
||||
namespace santa {
|
||||
|
||||
Enricher::Enricher(std::shared_ptr<::santa::santad::process_tree::ProcessTree> pt)
|
||||
: username_cache_(256), groupname_cache_(256), process_tree_(std::move(pt)) {}
|
||||
@@ -81,6 +85,42 @@ std::unique_ptr<EnrichedMessage> Enricher::Enrich(Message &&es_msg) {
|
||||
case ES_EVENT_TYPE_NOTIFY_CS_INVALIDATED:
|
||||
return std::make_unique<EnrichedMessage>(
|
||||
EnrichedCSInvalidated(std::move(es_msg), Enrich(*es_msg->process)));
|
||||
#if HAVE_MACOS_13
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN:
|
||||
return std::make_unique<EnrichedMessage>(EnrichedLoginWindowSessionLogin(
|
||||
std::move(es_msg), Enrich(*es_msg->process),
|
||||
UIDForUsername(StringTokenToStringView(es_msg->event.lw_session_login->username))));
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT:
|
||||
return std::make_unique<EnrichedMessage>(EnrichedLoginWindowSessionLogout(
|
||||
std::move(es_msg), Enrich(*es_msg->process),
|
||||
UIDForUsername(StringTokenToStringView(es_msg->event.lw_session_logout->username))));
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK:
|
||||
return std::make_unique<EnrichedMessage>(EnrichedLoginWindowSessionLock(
|
||||
std::move(es_msg), Enrich(*es_msg->process),
|
||||
UIDForUsername(StringTokenToStringView(es_msg->event.lw_session_lock->username))));
|
||||
case ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK:
|
||||
return std::make_unique<EnrichedMessage>(EnrichedLoginWindowSessionUnlock(
|
||||
std::move(es_msg), Enrich(*es_msg->process),
|
||||
UIDForUsername(StringTokenToStringView(es_msg->event.lw_session_unlock->username))));
|
||||
case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH:
|
||||
return std::make_unique<EnrichedMessage>(
|
||||
EnrichedScreenSharingAttach(std::move(es_msg), Enrich(*es_msg->process)));
|
||||
case ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH:
|
||||
return std::make_unique<EnrichedMessage>(
|
||||
EnrichedScreenSharingDetach(std::move(es_msg), Enrich(*es_msg->process)));
|
||||
case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN:
|
||||
return std::make_unique<EnrichedMessage>(
|
||||
EnrichedOpenSSHLogin(std::move(es_msg), Enrich(*es_msg->process)));
|
||||
case ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT:
|
||||
return std::make_unique<EnrichedMessage>(
|
||||
EnrichedOpenSSHLogout(std::move(es_msg), Enrich(*es_msg->process)));
|
||||
case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN:
|
||||
return std::make_unique<EnrichedMessage>(
|
||||
EnrichedLoginLogin(std::move(es_msg), Enrich(*es_msg->process)));
|
||||
case ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT:
|
||||
return std::make_unique<EnrichedMessage>(
|
||||
EnrichedLoginLogout(std::move(es_msg), Enrich(*es_msg->process)));
|
||||
#endif
|
||||
default:
|
||||
// This is a programming error
|
||||
LOGE(@"Attempting to enrich an unhandled event type: %d", es_msg->event_type);
|
||||
@@ -94,9 +134,10 @@ EnrichedProcess Enricher::Enrich(const es_process_t &es_proc, EnrichOptions opti
|
||||
UsernameForUID(audit_token_to_ruid(es_proc.audit_token), options),
|
||||
UsernameForGID(audit_token_to_rgid(es_proc.audit_token), options),
|
||||
Enrich(*es_proc.executable, options),
|
||||
process_tree_ ? process_tree_->ExportAnnotations(
|
||||
process_tree::PidFromAuditToken(es_proc.audit_token))
|
||||
: std::nullopt);
|
||||
process_tree_
|
||||
? process_tree_->ExportAnnotations(
|
||||
santa::santad::process_tree::PidFromAuditToken(es_proc.audit_token))
|
||||
: std::nullopt);
|
||||
}
|
||||
|
||||
EnrichedFile Enricher::Enrich(const es_file_t &es_file, EnrichOptions options) {
|
||||
@@ -152,4 +193,14 @@ std::optional<std::shared_ptr<std::string>> Enricher::UsernameForGID(gid_t gid,
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
std::optional<uid_t> Enricher::UIDForUsername(std::string_view username, EnrichOptions options) {
|
||||
if (options == EnrichOptions::kLocalOnly) {
|
||||
// If `kLocalOnly` option is set, do not attempt a lookup
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
struct passwd *pw = getpwnam(username.data());
|
||||
return pw ? std::make_optional(pw->pw_uid) : std::nullopt;
|
||||
}
|
||||
|
||||
} // namespace santa
|
||||
|
||||
@@ -18,7 +18,7 @@
|
||||
#include "Source/common/TestUtils.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Enricher.h"
|
||||
|
||||
using santa::santad::event_providers::endpoint_security::Enricher;
|
||||
using santa::Enricher;
|
||||
|
||||
@interface EnricherTest : XCTestCase
|
||||
@end
|
||||
|
||||
@@ -20,11 +20,13 @@
|
||||
#include <memory>
|
||||
#include <string>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
#include "Source/santad/ProcessTree/process_tree.h"
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
namespace santa {
|
||||
|
||||
class EndpointSecurityAPI;
|
||||
class MessagePeer;
|
||||
|
||||
class Message {
|
||||
public:
|
||||
@@ -39,11 +41,11 @@ class Message {
|
||||
Message(const Message& other);
|
||||
Message& operator=(const Message& other) = delete;
|
||||
|
||||
void SetProcessToken(process_tree::ProcessToken tok);
|
||||
void SetProcessToken(santa::santad::process_tree::ProcessToken tok);
|
||||
|
||||
// Operators to access underlying es_message_t
|
||||
const es_message_t* operator->() const { return es_msg_; }
|
||||
const es_message_t& operator*() const { return *es_msg_; }
|
||||
inline const es_message_t* operator->() const { return es_msg_; }
|
||||
inline const es_message_t& operator*() const { return *es_msg_; }
|
||||
|
||||
// Helper to get the API associated with this message.
|
||||
// Used for things like es_exec_arg_count.
|
||||
@@ -53,14 +55,24 @@ class Message {
|
||||
|
||||
std::string ParentProcessName() const;
|
||||
|
||||
void UpdateStatState(enum StatChangeStep step) const;
|
||||
|
||||
inline StatChangeStep StatChangeStep() const { return stat_change_step_; }
|
||||
inline StatResult StatResult() const { return stat_result_; }
|
||||
|
||||
friend class santa::MessagePeer;
|
||||
|
||||
private:
|
||||
std::shared_ptr<EndpointSecurityAPI> esapi_;
|
||||
const es_message_t* es_msg_;
|
||||
std::optional<process_tree::ProcessToken> process_token_;
|
||||
std::optional<santa::santad::process_tree::ProcessToken> process_token_;
|
||||
|
||||
std::string GetProcessName(pid_t pid) const;
|
||||
|
||||
mutable enum StatChangeStep stat_change_step_ = StatChangeStep::kNoChange;
|
||||
mutable enum StatResult stat_result_ = StatResult::kOK;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -16,14 +16,16 @@
|
||||
|
||||
#include <bsm/libbsm.h>
|
||||
#include <libproc.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
|
||||
|
||||
namespace santa::santad::event_providers::endpoint_security {
|
||||
namespace santa {
|
||||
|
||||
Message::Message(std::shared_ptr<EndpointSecurityAPI> esapi, const es_message_t *es_msg)
|
||||
: esapi_(std::move(esapi)), es_msg_(es_msg), process_token_(std::nullopt) {
|
||||
esapi_->RetainMessage(es_msg);
|
||||
UpdateStatState(StatChangeStep::kMessageCreate);
|
||||
}
|
||||
|
||||
Message::~Message() {
|
||||
@@ -38,6 +40,8 @@ Message::Message(Message &&other) {
|
||||
other.es_msg_ = nullptr;
|
||||
process_token_ = std::move(other.process_token_);
|
||||
other.process_token_ = std::nullopt;
|
||||
stat_change_step_ = other.stat_change_step_;
|
||||
stat_result_ = other.stat_result_;
|
||||
}
|
||||
|
||||
Message::Message(const Message &other) {
|
||||
@@ -45,9 +49,30 @@ Message::Message(const Message &other) {
|
||||
es_msg_ = other.es_msg_;
|
||||
esapi_->RetainMessage(es_msg_);
|
||||
process_token_ = other.process_token_;
|
||||
stat_change_step_ = other.stat_change_step_;
|
||||
stat_result_ = other.stat_result_;
|
||||
}
|
||||
|
||||
void Message::SetProcessToken(process_tree::ProcessToken tok) {
|
||||
void Message::UpdateStatState(enum StatChangeStep step) const {
|
||||
// Only update state for AUTH EXEC events and if no previous change was detected
|
||||
if (es_msg_->event_type == ES_EVENT_TYPE_AUTH_EXEC &&
|
||||
stat_change_step_ == StatChangeStep::kNoChange &&
|
||||
// Note: The following checks are required due to tests that only
|
||||
// partially construct an es_message_t.
|
||||
es_msg_->event.exec.target && es_msg_->event.exec.target->executable) {
|
||||
struct stat &es_sb = es_msg_->event.exec.target->executable->stat;
|
||||
struct stat sb;
|
||||
int ret = stat(es_msg_->event.exec.target->executable->path.data, &sb);
|
||||
// If stat failed, or if devno/inode changed, update state.
|
||||
if (ret != 0 || es_sb.st_ino != sb.st_ino || es_sb.st_dev != sb.st_dev) {
|
||||
stat_change_step_ = step;
|
||||
// Determine the specific condition that failed for tracking purposes
|
||||
stat_result_ = (ret != 0) ? StatResult::kStatError : StatResult::kDevnoInodeMismatch;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void Message::SetProcessToken(santa::santad::process_tree::ProcessToken tok) {
|
||||
process_token_ = std::move(tok);
|
||||
}
|
||||
|
||||
@@ -66,4 +91,4 @@ std::string Message::GetProcessName(pid_t pid) const {
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace santa::santad::event_providers::endpoint_security
|
||||
} // namespace santa
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/MockEndpointSecurityAPI.h"
|
||||
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::Message;
|
||||
|
||||
bool IsPidInUse(pid_t pid) {
|
||||
char pname[MAXCOMLEN * 2 + 1] = {};
|
||||
|
||||
@@ -27,18 +27,13 @@
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/EndpointSecurityAPI.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
using santa::Client;
|
||||
|
||||
class MockEndpointSecurityAPI
|
||||
: public santa::santad::event_providers::endpoint_security::EndpointSecurityAPI {
|
||||
class MockEndpointSecurityAPI : public santa::EndpointSecurityAPI {
|
||||
public:
|
||||
MOCK_METHOD(santa::santad::event_providers::endpoint_security::Client, NewClient,
|
||||
(void (^message_handler)(
|
||||
es_client_t *, santa::santad::event_providers::endpoint_security::Message)));
|
||||
MOCK_METHOD(santa::Client, NewClient, (void (^message_handler)(es_client_t *, santa::Message)));
|
||||
|
||||
MOCK_METHOD(bool, Subscribe,
|
||||
(const santa::santad::event_providers::endpoint_security::Client &,
|
||||
const std::set<es_event_type_t> &));
|
||||
MOCK_METHOD(bool, Subscribe, (const santa::Client &, const std::set<es_event_type_t> &));
|
||||
MOCK_METHOD(bool, UnsubscribeAll, (const Client &client));
|
||||
|
||||
MOCK_METHOD(bool, UnmuteAllPaths, (const Client &client));
|
||||
@@ -48,30 +43,23 @@ class MockEndpointSecurityAPI
|
||||
MOCK_METHOD(bool, InvertTargetPathMuting, (const Client &client));
|
||||
|
||||
MOCK_METHOD(bool, MuteTargetPath,
|
||||
(const Client &client, std::string_view path,
|
||||
santa::santad::data_layer::WatchItemPathType path_type));
|
||||
(const Client &client, std::string_view path, santa::WatchItemPathType path_type));
|
||||
MOCK_METHOD(bool, UnmuteTargetPath,
|
||||
(const Client &client, std::string_view path,
|
||||
santa::santad::data_layer::WatchItemPathType path_type));
|
||||
(const Client &client, std::string_view path, santa::WatchItemPathType path_type));
|
||||
|
||||
MOCK_METHOD(void, RetainMessage, (const es_message_t *msg));
|
||||
MOCK_METHOD(void, ReleaseMessage, (const es_message_t *msg));
|
||||
|
||||
MOCK_METHOD(bool, RespondAuthResult,
|
||||
(const santa::santad::event_providers::endpoint_security::Client &,
|
||||
const santa::santad::event_providers::endpoint_security::Message &msg,
|
||||
es_auth_result_t result, bool cache));
|
||||
(const santa::Client &, const santa::Message &msg, es_auth_result_t result,
|
||||
bool cache));
|
||||
MOCK_METHOD(bool, RespondFlagsResult,
|
||||
(const santa::santad::event_providers::endpoint_security::Client &client,
|
||||
const santa::santad::event_providers::endpoint_security::Message &msg,
|
||||
uint32_t allowed_flags, bool cache));
|
||||
(const santa::Client &client, const santa::Message &msg, uint32_t allowed_flags,
|
||||
bool cache));
|
||||
|
||||
MOCK_METHOD(bool, MuteProcess,
|
||||
(const santa::santad::event_providers::endpoint_security::Client &,
|
||||
const audit_token_t *tok));
|
||||
MOCK_METHOD(bool, MuteProcess, (const santa::Client &, const audit_token_t *tok));
|
||||
|
||||
MOCK_METHOD(bool, ClearCache,
|
||||
(const santa::santad::event_providers::endpoint_security::Client &));
|
||||
MOCK_METHOD(bool, ClearCache, (const santa::Client &));
|
||||
|
||||
MOCK_METHOD(uint32_t, ExecArgCount, (const es_event_exec_t *event));
|
||||
MOCK_METHOD(es_string_token_t, ExecArg, (const es_event_exec_t *event, uint32_t index));
|
||||
@@ -84,8 +72,7 @@ class MockEndpointSecurityAPI
|
||||
|
||||
void SetExpectationsESNewClient() {
|
||||
EXPECT_CALL(*this, NewClient)
|
||||
.WillOnce(testing::Return(santa::santad::event_providers::endpoint_security::Client(
|
||||
nullptr, ES_NEW_CLIENT_RESULT_SUCCESS)));
|
||||
.WillOnce(testing::Return(santa::Client(nullptr, ES_NEW_CLIENT_RESULT_SUCCESS)));
|
||||
EXPECT_CALL(*this, MuteProcess).WillOnce(testing::Return(true));
|
||||
EXPECT_CALL(*this, ClearCache).WillRepeatedly(testing::Return(true));
|
||||
EXPECT_CALL(*this, Subscribe).WillRepeatedly(testing::Return(true));
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
#include "Source/santad/Metrics.h"
|
||||
|
||||
// Forward declarations
|
||||
namespace santa::santad::event_providers {
|
||||
namespace santa {
|
||||
class RateLimiterPeer;
|
||||
}
|
||||
} // namespace santa
|
||||
|
||||
namespace santa::santad::event_providers {
|
||||
namespace santa {
|
||||
|
||||
// Very basic rate limiting infrastructure.
|
||||
// Currently only handles X events per duration.
|
||||
@@ -39,12 +39,11 @@ class RateLimiter {
|
||||
public:
|
||||
// Factory
|
||||
static std::shared_ptr<RateLimiter> Create(
|
||||
std::shared_ptr<santa::santad::Metrics> metrics,
|
||||
santa::santad::Processor processor, uint16_t max_qps,
|
||||
NSTimeInterval reset_duration = kDefaultResetDuration);
|
||||
std::shared_ptr<santa::Metrics> metrics, santa::Processor processor,
|
||||
uint16_t max_qps, NSTimeInterval reset_duration = kDefaultResetDuration);
|
||||
|
||||
RateLimiter(std::shared_ptr<santa::santad::Metrics> metrics,
|
||||
santa::santad::Processor processor, uint16_t max_qps,
|
||||
RateLimiter(std::shared_ptr<santa::Metrics> metrics,
|
||||
santa::Processor processor, uint16_t max_qps,
|
||||
NSTimeInterval reset_duration);
|
||||
|
||||
enum class Decision {
|
||||
@@ -54,7 +53,7 @@ class RateLimiter {
|
||||
|
||||
Decision Decide(uint64_t cur_mach_time);
|
||||
|
||||
friend class santa::santad::event_providers::RateLimiterPeer;
|
||||
friend class santa::RateLimiterPeer;
|
||||
|
||||
private:
|
||||
bool ShouldRateLimitLocked();
|
||||
@@ -63,8 +62,8 @@ class RateLimiter {
|
||||
|
||||
static constexpr NSTimeInterval kDefaultResetDuration = 15.0;
|
||||
|
||||
std::shared_ptr<santa::santad::Metrics> metrics_;
|
||||
santa::santad::Processor processor_;
|
||||
std::shared_ptr<santa::Metrics> metrics_;
|
||||
santa::Processor processor_;
|
||||
size_t log_count_total_ = 0;
|
||||
size_t max_log_count_total_;
|
||||
uint64_t reset_mach_time_;
|
||||
@@ -72,6 +71,6 @@ class RateLimiter {
|
||||
dispatch_queue_t q_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers
|
||||
} // namespace santa
|
||||
|
||||
#endif
|
||||
|
||||
@@ -17,10 +17,10 @@
|
||||
#include "Source/common/BranchPrediction.h"
|
||||
#include "Source/common/SystemResources.h"
|
||||
|
||||
using santa::santad::Metrics;
|
||||
using santa::santad::Processor;
|
||||
using santa::Metrics;
|
||||
using santa::Processor;
|
||||
|
||||
namespace santa::santad::event_providers {
|
||||
namespace santa {
|
||||
|
||||
std::shared_ptr<RateLimiter> RateLimiter::Create(std::shared_ptr<Metrics> metrics,
|
||||
Processor processor, uint16_t max_qps,
|
||||
@@ -82,4 +82,4 @@ RateLimiter::Decision RateLimiter::Decide(uint64_t cur_mach_time) {
|
||||
return decision;
|
||||
}
|
||||
|
||||
} // namespace santa::santad::event_providers
|
||||
} // namespace santa
|
||||
|
||||
@@ -20,12 +20,11 @@
|
||||
#include "Source/common/SystemResources.h"
|
||||
#include "Source/santad/Metrics.h"
|
||||
|
||||
using santa::santad::event_providers::RateLimiter;
|
||||
using santa::RateLimiter;
|
||||
|
||||
static const santa::santad::Processor kDefaultProcessor =
|
||||
santa::santad::Processor::kFileAccessAuthorizer;
|
||||
static const santa::Processor kDefaultProcessor = santa::Processor::kFileAccessAuthorizer;
|
||||
|
||||
namespace santa::santad::event_providers {
|
||||
namespace santa {
|
||||
|
||||
class RateLimiterPeer : public RateLimiter {
|
||||
public:
|
||||
@@ -39,9 +38,9 @@ class RateLimiterPeer : public RateLimiter {
|
||||
using RateLimiter::reset_mach_time_;
|
||||
};
|
||||
|
||||
} // namespace santa::santad::event_providers
|
||||
} // namespace santa
|
||||
|
||||
using santa::santad::event_providers::RateLimiterPeer;
|
||||
using santa::RateLimiterPeer;
|
||||
|
||||
@interface RateLimiterTest : XCTestCase
|
||||
@end
|
||||
|
||||
@@ -27,14 +27,10 @@
|
||||
@interface SNTEndpointSecurityAuthorizer
|
||||
: SNTEndpointSecurityClient <SNTEndpointSecurityEventHandler>
|
||||
|
||||
- (instancetype)
|
||||
initWithESAPI:
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)
|
||||
esApi
|
||||
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
|
||||
execController:(SNTExecutionController *)execController
|
||||
compilerController:(SNTCompilerController *)compilerController
|
||||
authResultCache:
|
||||
(std::shared_ptr<santa::santad::event_providers::AuthResultCache>)authResultCache;
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::Metrics>)metrics
|
||||
execController:(SNTExecutionController *)execController
|
||||
compilerController:(SNTCompilerController *)compilerController
|
||||
authResultCache:(std::shared_ptr<santa::AuthResultCache>)authResultCache;
|
||||
|
||||
@end
|
||||
|
||||
@@ -26,10 +26,10 @@
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
#include "Source/santad/Metrics.h"
|
||||
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::event_providers::AuthResultCache;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::AuthResultCache;
|
||||
using santa::EndpointSecurityAPI;
|
||||
using santa::EventDisposition;
|
||||
using santa::Message;
|
||||
|
||||
@interface SNTEndpointSecurityAuthorizer ()
|
||||
@property SNTCompilerController *compilerController;
|
||||
@@ -41,13 +41,13 @@ using santa::santad::event_providers::endpoint_security::Message;
|
||||
}
|
||||
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
|
||||
metrics:(std::shared_ptr<santa::Metrics>)metrics
|
||||
execController:(SNTExecutionController *)execController
|
||||
compilerController:(SNTCompilerController *)compilerController
|
||||
authResultCache:(std::shared_ptr<AuthResultCache>)authResultCache {
|
||||
self = [super initWithESAPI:std::move(esApi)
|
||||
metrics:std::move(metrics)
|
||||
processor:santa::santad::Processor::kAuthorizer];
|
||||
processor:santa::Processor::kAuthorizer];
|
||||
if (self) {
|
||||
_execController = execController;
|
||||
_compilerController = compilerController;
|
||||
|
||||
@@ -33,9 +33,9 @@
|
||||
#import "Source/santad/SNTCompilerController.h"
|
||||
#import "Source/santad/SNTExecutionController.h"
|
||||
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::event_providers::AuthResultCache;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::AuthResultCache;
|
||||
using santa::EventDisposition;
|
||||
using santa::Message;
|
||||
|
||||
class MockAuthResultCache : public AuthResultCache {
|
||||
public:
|
||||
@@ -72,7 +72,7 @@ class MockAuthResultCache : public AuthResultCache {
|
||||
id authClient =
|
||||
[[SNTEndpointSecurityAuthorizer alloc] initWithESAPI:mockESApi
|
||||
metrics:nullptr
|
||||
processor:santa::santad::Processor::kAuthorizer];
|
||||
processor:santa::Processor::kAuthorizer];
|
||||
|
||||
EXPECT_CALL(*mockESApi, ClearCache)
|
||||
.After(EXPECT_CALL(*mockESApi, Subscribe(testing::_, expectedEventSubs))
|
||||
@@ -81,6 +81,10 @@ class MockAuthResultCache : public AuthResultCache {
|
||||
|
||||
[authClient enable];
|
||||
|
||||
for (const auto &event : expectedEventSubs) {
|
||||
XCTAssertNoThrow(santa::EventTypeToString(event));
|
||||
}
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
|
||||
|
||||
@@ -37,14 +37,14 @@
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
#include "Source/santad/Metrics.h"
|
||||
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::Metrics;
|
||||
using santa::santad::Processor;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::Client;
|
||||
using santa::EndpointSecurityAPI;
|
||||
using santa::EnrichedMessage;
|
||||
using santa::EventDisposition;
|
||||
using santa::Message;
|
||||
using santa::Metrics;
|
||||
using santa::Processor;
|
||||
using santa::WatchItemPathType;
|
||||
|
||||
constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db",
|
||||
"/private/var/db/santa/events.db"};
|
||||
@@ -92,7 +92,7 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
|
||||
_notifyQueue = dispatch_queue_create(
|
||||
"com.google.santa.daemon.notify_queue",
|
||||
dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT_WITH_AUTORELEASE_POOL,
|
||||
QOS_CLASS_BACKGROUND, 0));
|
||||
QOS_CLASS_UTILITY, 0));
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -146,12 +146,10 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
|
||||
// sequence numbers are processed in order.
|
||||
self->_metrics->UpdateEventStats(self->_processor, esMsg.operator->());
|
||||
|
||||
es_event_type_t eventType = esMsg->event_type;
|
||||
|
||||
if ([self handleContextMessage:esMsg]) {
|
||||
int64_t processingEnd = clock_gettime_nsec_np(CLOCK_MONOTONIC);
|
||||
self->_metrics->SetEventMetrics(self->_processor, eventType, EventDisposition::kProcessed,
|
||||
processingEnd - processingStart);
|
||||
self->_metrics->SetEventMetrics(self->_processor, EventDisposition::kProcessed,
|
||||
processingEnd - processingStart, esMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -159,13 +157,13 @@ constexpr std::string_view kProtectedFiles[] = {"/private/var/db/santa/rules.db"
|
||||
[self handleMessage:std::move(esMsg)
|
||||
recordEventMetrics:^(EventDisposition disposition) {
|
||||
int64_t processingEnd = clock_gettime_nsec_np(CLOCK_MONOTONIC);
|
||||
self->_metrics->SetEventMetrics(self->_processor, eventType, disposition,
|
||||
processingEnd - processingStart);
|
||||
self->_metrics->SetEventMetrics(self->_processor, disposition,
|
||||
processingEnd - processingStart, esMsg);
|
||||
}];
|
||||
} else {
|
||||
int64_t processingEnd = clock_gettime_nsec_np(CLOCK_MONOTONIC);
|
||||
self->_metrics->SetEventMetrics(self->_processor, eventType, EventDisposition::kDropped,
|
||||
processingEnd - processingStart);
|
||||
self->_metrics->SetEventMetrics(self->_processor, EventDisposition::kDropped,
|
||||
processingEnd - processingStart, esMsg);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -29,11 +29,9 @@
|
||||
|
||||
@protocol SNTEndpointSecurityClientBase
|
||||
|
||||
- (instancetype)
|
||||
initWithESAPI:
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
|
||||
processor:(santa::santad::Processor)processor;
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::Metrics>)metrics
|
||||
processor:(santa::Processor)processor;
|
||||
|
||||
/// @note If this fails to establish a new ES client via `es_new_client`, an exception is raised
|
||||
/// that should terminate the program.
|
||||
@@ -52,9 +50,9 @@
|
||||
- (bool)unmuteAllTargetPaths;
|
||||
- (bool)enableTargetPathWatching;
|
||||
- (bool)muteTargetPaths:
|
||||
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)paths;
|
||||
(const std::vector<std::pair<std::string, santa::WatchItemPathType>> &)paths;
|
||||
- (bool)unmuteTargetPaths:
|
||||
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)paths;
|
||||
(const std::vector<std::pair<std::string, santa::WatchItemPathType>> &)paths;
|
||||
|
||||
/// Responds to the Message with the given auth result
|
||||
///
|
||||
@@ -66,27 +64,18 @@
|
||||
/// @note If the msg event type requires a flags response, the correct ES API will automatically
|
||||
/// be called. ALLOWED results will be translated to having all flags set, and DENIED results
|
||||
/// will be translated to having all flags cleared.
|
||||
- (bool)respondToMessage:(const santa::santad::event_providers::endpoint_security::Message &)msg
|
||||
- (bool)respondToMessage:(const santa::Message &)msg
|
||||
withAuthResult:(es_auth_result_t)result
|
||||
cacheable:(bool)cacheable;
|
||||
|
||||
- (void)
|
||||
processEnrichedMessage:
|
||||
(std::unique_ptr<santa::santad::event_providers::endpoint_security::EnrichedMessage>)msg
|
||||
handler:
|
||||
(void (^)(std::unique_ptr<
|
||||
santa::santad::event_providers::endpoint_security::EnrichedMessage>))
|
||||
messageHandler;
|
||||
- (void)processEnrichedMessage:(std::unique_ptr<santa::EnrichedMessage>)msg
|
||||
handler:(void (^)(std::unique_ptr<santa::EnrichedMessage>))messageHandler;
|
||||
|
||||
- (void)asynchronouslyProcess:(santa::santad::event_providers::endpoint_security::Message)msg
|
||||
handler:
|
||||
(void (^)(santa::santad::event_providers::endpoint_security::Message &&))
|
||||
messageHandler;
|
||||
- (void)asynchronouslyProcess:(santa::Message)msg
|
||||
handler:(void (^)(santa::Message &&))messageHandler;
|
||||
|
||||
- (void)processMessage:(santa::santad::event_providers::endpoint_security::Message &&)msg
|
||||
handler:
|
||||
(void (^)(const santa::santad::event_providers::endpoint_security::Message &))
|
||||
messageHandler;
|
||||
- (void)processMessage:(santa::Message &&)msg
|
||||
handler:(void (^)(const santa::Message &))messageHandler;
|
||||
|
||||
- (bool)clearCache;
|
||||
|
||||
|
||||
@@ -34,21 +34,21 @@
|
||||
#import "Source/santad/EventProviders/SNTEndpointSecurityClient.h"
|
||||
#include "Source/santad/Metrics.h"
|
||||
|
||||
using santa::santad::Processor;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedClose;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedFile;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedProcess;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::Client;
|
||||
using santa::EnrichedClose;
|
||||
using santa::EnrichedFile;
|
||||
using santa::EnrichedMessage;
|
||||
using santa::EnrichedProcess;
|
||||
using santa::Message;
|
||||
using santa::Processor;
|
||||
using santa::WatchItemPathType;
|
||||
|
||||
@interface SNTEndpointSecurityClient (Testing)
|
||||
- (void)establishClientOrDie;
|
||||
- (bool)muteSelf;
|
||||
- (NSString *)errorMessageForNewClientResult:(es_new_client_result_t)result;
|
||||
- (void)handleMessage:(Message &&)esMsg
|
||||
recordEventMetrics:(void (^)(santa::santad::EventDisposition disposition))recordEventMetrics;
|
||||
recordEventMetrics:(void (^)(santa::EventDisposition disposition))recordEventMetrics;
|
||||
- (BOOL)shouldHandleMessage:(const Message &)esMsg;
|
||||
- (int64_t)computeBudgetForDeadline:(uint64_t)deadline currentTime:(uint64_t)currentTime;
|
||||
|
||||
|
||||
@@ -39,17 +39,13 @@ typedef void (^SNTDeviceBlockCallback)(SNTDeviceEvent *event);
|
||||
@property(nonatomic, readwrite, nullable) NSArray<NSString *> *remountArgs;
|
||||
@property(nonatomic, nullable) SNTDeviceBlockCallback deviceBlockCallback;
|
||||
|
||||
- (instancetype)
|
||||
initWithESAPI:
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)
|
||||
esApi
|
||||
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
|
||||
logger:(std::shared_ptr<santa::santad::logs::endpoint_security::Logger>)logger
|
||||
authResultCache:
|
||||
(std::shared_ptr<santa::santad::event_providers::AuthResultCache>)authResultCache
|
||||
blockUSBMount:(BOOL)blockUSBMount
|
||||
remountUSBMode:(nullable NSArray<NSString *> *)remountUSBMode
|
||||
startupPreferences:(SNTDeviceManagerStartupPreferences)startupPrefs;
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::Metrics>)metrics
|
||||
logger:(std::shared_ptr<santa::Logger>)logger
|
||||
authResultCache:(std::shared_ptr<santa::AuthResultCache>)authResultCache
|
||||
blockUSBMount:(BOOL)blockUSBMount
|
||||
remountUSBMode:(nullable NSArray<NSString *> *)remountUSBMode
|
||||
startupPreferences:(SNTDeviceManagerStartupPreferences)startupPrefs;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -33,13 +33,13 @@
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
#include "Source/santad/Metrics.h"
|
||||
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::event_providers::AuthResultCache;
|
||||
using santa::santad::event_providers::FlushCacheMode;
|
||||
using santa::santad::event_providers::FlushCacheReason;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::santad::logs::endpoint_security::Logger;
|
||||
using santa::AuthResultCache;
|
||||
using santa::EndpointSecurityAPI;
|
||||
using santa::EventDisposition;
|
||||
using santa::FlushCacheMode;
|
||||
using santa::FlushCacheReason;
|
||||
using santa::Logger;
|
||||
using santa::Message;
|
||||
|
||||
// Defined operations for startup metrics:
|
||||
// Device shouldn't be operated on (e.g. not a mass storage device)
|
||||
@@ -178,7 +178,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
}
|
||||
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
|
||||
metrics:(std::shared_ptr<santa::Metrics>)metrics
|
||||
logger:(std::shared_ptr<Logger>)logger
|
||||
authResultCache:(std::shared_ptr<AuthResultCache>)authResultCache
|
||||
blockUSBMount:(BOOL)blockUSBMount
|
||||
@@ -186,7 +186,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
startupPreferences:(SNTDeviceManagerStartupPreferences)startupPrefs {
|
||||
self = [super initWithESAPI:std::move(esApi)
|
||||
metrics:std::move(metrics)
|
||||
processor:santa::santad::Processor::kDeviceManager];
|
||||
processor:santa::Processor::kDeviceManager];
|
||||
if (self) {
|
||||
_logger = logger;
|
||||
_authResultCache = authResultCache;
|
||||
|
||||
@@ -38,11 +38,11 @@
|
||||
#import "Source/santad/EventProviders/SNTEndpointSecurityDeviceManager.h"
|
||||
#include "Source/santad/Metrics.h"
|
||||
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::event_providers::AuthResultCache;
|
||||
using santa::santad::event_providers::FlushCacheMode;
|
||||
using santa::santad::event_providers::FlushCacheReason;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::AuthResultCache;
|
||||
using santa::EventDisposition;
|
||||
using santa::FlushCacheMode;
|
||||
using santa::FlushCacheReason;
|
||||
using santa::Message;
|
||||
|
||||
class MockAuthResultCache : public AuthResultCache {
|
||||
public:
|
||||
@@ -384,6 +384,8 @@ class MockAuthResultCache : public AuthResultCache {
|
||||
on:@"v2"
|
||||
flags:@(MNT_RDONLY | MNT_NOEXEC | MNT_JOURNALED)]];
|
||||
|
||||
// Disabling clang format due to local/remote version differences.
|
||||
// clang-format off
|
||||
// Create mock disks with desired args
|
||||
MockDADisk * (^CreateMockDisk)(NSString *, NSString *) =
|
||||
^MockDADisk *(NSString *mountOn, NSString *mountFrom) {
|
||||
@@ -396,6 +398,7 @@ class MockAuthResultCache : public AuthResultCache {
|
||||
|
||||
return mockDisk;
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
// Reset the Mock DA property, setup disks and remount args, then trigger the test
|
||||
void (^PerformStartupTest)(NSArray<MockDADisk *> *, NSArray<NSString *> *,
|
||||
@@ -495,10 +498,10 @@ class MockAuthResultCache : public AuthResultCache {
|
||||
};
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
|
||||
id deviceClient = [[SNTEndpointSecurityDeviceManager alloc]
|
||||
initWithESAPI:mockESApi
|
||||
metrics:nullptr
|
||||
processor:santa::santad::Processor::kDeviceManager];
|
||||
id deviceClient =
|
||||
[[SNTEndpointSecurityDeviceManager alloc] initWithESAPI:mockESApi
|
||||
metrics:nullptr
|
||||
processor:santa::Processor::kDeviceManager];
|
||||
|
||||
EXPECT_CALL(*mockESApi, ClearCache(testing::_))
|
||||
.After(EXPECT_CALL(*mockESApi, Subscribe(testing::_, expectedEventSubs))
|
||||
@@ -507,6 +510,10 @@ class MockAuthResultCache : public AuthResultCache {
|
||||
|
||||
[deviceClient enable];
|
||||
|
||||
for (const auto &event : expectedEventSubs) {
|
||||
XCTAssertNoThrow(santa::EventTypeToString(event));
|
||||
}
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
|
||||
|
||||
@@ -26,8 +26,8 @@
|
||||
|
||||
// Called Synchronously and serially for each message provided by the
|
||||
// EndpointSecurity framework.
|
||||
- (void)handleMessage:(santa::santad::event_providers::endpoint_security::Message &&)esMsg
|
||||
recordEventMetrics:(void (^)(santa::santad::EventDisposition))recordEventMetrics;
|
||||
- (void)handleMessage:(santa::Message &&)esMsg
|
||||
recordEventMetrics:(void (^)(santa::EventDisposition))recordEventMetrics;
|
||||
|
||||
// Called after Santa has finished initializing itself.
|
||||
// This is an optimal place to subscribe to ES events
|
||||
@@ -43,13 +43,10 @@
|
||||
// Called when a client should no longer receive events.
|
||||
- (void)disable;
|
||||
|
||||
- (void)
|
||||
watchItemsCount:(size_t)count
|
||||
newPaths:
|
||||
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>>
|
||||
&)newPaths
|
||||
removedPaths:
|
||||
(const std::vector<std::pair<std::string, santa::santad::data_layer::WatchItemPathType>> &)
|
||||
removedPaths;
|
||||
- (void)watchItemsCount:(size_t)count
|
||||
newPaths:
|
||||
(const std::vector<std::pair<std::string, santa::WatchItemPathType>> &)newPaths
|
||||
removedPaths:
|
||||
(const std::vector<std::pair<std::string, santa::WatchItemPathType>> &)removedPaths;
|
||||
|
||||
@end
|
||||
|
||||
@@ -33,16 +33,13 @@ typedef void (^SNTFileAccessBlockCallback)(SNTFileAccessEvent *event, NSString *
|
||||
@interface SNTEndpointSecurityFileAccessAuthorizer
|
||||
: SNTEndpointSecurityClient <SNTEndpointSecurityDynamicEventHandler>
|
||||
|
||||
- (instancetype)
|
||||
initWithESAPI:
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
|
||||
logger:(std::shared_ptr<santa::santad::logs::endpoint_security::Logger>)logger
|
||||
watchItems:(std::shared_ptr<santa::santad::data_layer::WatchItems>)watchItems
|
||||
enricher:
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::Enricher>)enricher
|
||||
decisionCache:(SNTDecisionCache *)decisionCache
|
||||
ttyWriter:(std::shared_ptr<santa::santad::TTYWriter>)ttyWriter;
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::Metrics>)metrics
|
||||
logger:(std::shared_ptr<santa::Logger>)logger
|
||||
watchItems:(std::shared_ptr<santa::WatchItems>)watchItems
|
||||
enricher:(std::shared_ptr<santa::Enricher>)enricher
|
||||
decisionCache:(SNTDecisionCache *)decisionCache
|
||||
ttyWriter:(std::shared_ptr<santa::TTYWriter>)ttyWriter;
|
||||
|
||||
@property SNTFileAccessBlockCallback fileAccessBlockCallback;
|
||||
|
||||
|
||||
@@ -51,21 +51,21 @@
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "absl/container/flat_hash_set.h"
|
||||
|
||||
using santa::common::OptionalStringToNSString;
|
||||
using santa::common::StringToNSString;
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::FileAccessMetricStatus;
|
||||
using santa::santad::Metrics;
|
||||
using santa::santad::TTYWriter;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::data_layer::WatchItemPolicy;
|
||||
using santa::santad::data_layer::WatchItems;
|
||||
using santa::santad::event_providers::RateLimiter;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
using santa::santad::event_providers::endpoint_security::Enricher;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichOptions;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::santad::logs::endpoint_security::Logger;
|
||||
using santa::EndpointSecurityAPI;
|
||||
using santa::Enricher;
|
||||
using santa::EnrichOptions;
|
||||
using santa::EventDisposition;
|
||||
using santa::FileAccessMetricStatus;
|
||||
using santa::Logger;
|
||||
using santa::Message;
|
||||
using santa::Metrics;
|
||||
using santa::OptionalStringToNSString;
|
||||
using santa::RateLimiter;
|
||||
using santa::StringToNSString;
|
||||
using santa::TTYWriter;
|
||||
using santa::WatchItemPathType;
|
||||
using santa::WatchItemPolicy;
|
||||
using santa::WatchItems;
|
||||
|
||||
NSString *kBadCertHash = @"BAD_CERT_HASH";
|
||||
|
||||
@@ -396,19 +396,16 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
|
||||
std::shared_ptr<Metrics> _metrics;
|
||||
}
|
||||
|
||||
- (instancetype)
|
||||
initWithESAPI:
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<Metrics>)metrics
|
||||
logger:(std::shared_ptr<santa::santad::logs::endpoint_security::Logger>)logger
|
||||
watchItems:(std::shared_ptr<WatchItems>)watchItems
|
||||
enricher:
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::Enricher>)enricher
|
||||
decisionCache:(SNTDecisionCache *)decisionCache
|
||||
ttyWriter:(std::shared_ptr<santa::santad::TTYWriter>)ttyWriter {
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<Metrics>)metrics
|
||||
logger:(std::shared_ptr<santa::Logger>)logger
|
||||
watchItems:(std::shared_ptr<WatchItems>)watchItems
|
||||
enricher:(std::shared_ptr<santa::Enricher>)enricher
|
||||
decisionCache:(SNTDecisionCache *)decisionCache
|
||||
ttyWriter:(std::shared_ptr<santa::TTYWriter>)ttyWriter {
|
||||
self = [super initWithESAPI:std::move(esApi)
|
||||
metrics:metrics
|
||||
processor:santa::santad::Processor::kFileAccessAuthorizer];
|
||||
processor:santa::Processor::kFileAccessAuthorizer];
|
||||
if (self) {
|
||||
_watchItems = std::move(watchItems);
|
||||
_logger = std::move(logger);
|
||||
@@ -419,8 +416,8 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
|
||||
|
||||
_configurator = [SNTConfigurator configurator];
|
||||
|
||||
_rateLimiter = RateLimiter::Create(_metrics, santa::santad::Processor::kFileAccessAuthorizer,
|
||||
kDefaultRateLimitQPS);
|
||||
_rateLimiter =
|
||||
RateLimiter::Create(_metrics, santa::Processor::kFileAccessAuthorizer, kDefaultRateLimitQPS);
|
||||
|
||||
SNTMetricBooleanGauge *famEnabled = [[SNTMetricSet sharedInstance]
|
||||
booleanGaugeWithName:@"/santa/fam_enabled"
|
||||
@@ -691,7 +688,7 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
|
||||
|
||||
// Notify users on block decisions
|
||||
if (ShouldNotifyUserDecision(policyDecision) &&
|
||||
(!policy->silent || (!policy->silent_tty && msg->process->tty->path.length > 0))) {
|
||||
(!policy->silent || (!policy->silent_tty && TTYWriter::CanWrite(msg->process)))) {
|
||||
SNTCachedDecision *cd =
|
||||
[self.decisionCache cachedDecisionForFile:msg->process->executable->stat];
|
||||
|
||||
@@ -797,7 +794,7 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
|
||||
cacheable:(policyResult == ES_AUTH_RESULT_ALLOW && !allow_read_access)];
|
||||
}
|
||||
|
||||
- (void)handleMessage:(santa::santad::event_providers::endpoint_security::Message &&)esMsg
|
||||
- (void)handleMessage:(santa::Message &&)esMsg
|
||||
recordEventMetrics:(void (^)(EventDisposition))recordEventMetrics {
|
||||
SNTOverrideFileAccessAction overrideAction = [self.configurator overrideFileAccessAction];
|
||||
|
||||
@@ -834,17 +831,12 @@ bool ShouldMessageTTY(const std::shared_ptr<WatchItemPolicy> &policy, const Mess
|
||||
|
||||
- (void)enable {
|
||||
std::set<es_event_type_t> events = {
|
||||
ES_EVENT_TYPE_AUTH_CLONE, ES_EVENT_TYPE_AUTH_CREATE, ES_EVENT_TYPE_AUTH_EXCHANGEDATA,
|
||||
ES_EVENT_TYPE_AUTH_LINK, ES_EVENT_TYPE_AUTH_OPEN, ES_EVENT_TYPE_AUTH_RENAME,
|
||||
ES_EVENT_TYPE_AUTH_TRUNCATE, ES_EVENT_TYPE_AUTH_UNLINK, ES_EVENT_TYPE_NOTIFY_EXIT,
|
||||
ES_EVENT_TYPE_AUTH_CLONE, ES_EVENT_TYPE_AUTH_COPYFILE, ES_EVENT_TYPE_AUTH_CREATE,
|
||||
ES_EVENT_TYPE_AUTH_EXCHANGEDATA, ES_EVENT_TYPE_AUTH_LINK, ES_EVENT_TYPE_AUTH_OPEN,
|
||||
ES_EVENT_TYPE_AUTH_RENAME, ES_EVENT_TYPE_AUTH_TRUNCATE, ES_EVENT_TYPE_AUTH_UNLINK,
|
||||
ES_EVENT_TYPE_NOTIFY_EXIT,
|
||||
};
|
||||
|
||||
#if HAVE_MACOS_12
|
||||
if (@available(macOS 12.0, *)) {
|
||||
events.insert(ES_EVENT_TYPE_AUTH_COPYFILE);
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!self.isSubscribed) {
|
||||
if ([super subscribe:events]) {
|
||||
self.isSubscribed = true;
|
||||
|
||||
@@ -44,8 +44,8 @@
|
||||
#include "Source/santad/Logs/EndpointSecurity/MockLogger.h"
|
||||
#include "Source/santad/SNTDecisionCache.h"
|
||||
|
||||
using santa::santad::data_layer::WatchItemPolicy;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::Message;
|
||||
using santa::WatchItemPolicy;
|
||||
|
||||
extern NSString *kBadCertHash;
|
||||
|
||||
@@ -729,17 +729,12 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
|
||||
|
||||
- (void)testEnable {
|
||||
std::set<es_event_type_t> expectedEventSubs = {
|
||||
ES_EVENT_TYPE_AUTH_CLONE, ES_EVENT_TYPE_AUTH_CREATE, ES_EVENT_TYPE_AUTH_EXCHANGEDATA,
|
||||
ES_EVENT_TYPE_AUTH_LINK, ES_EVENT_TYPE_AUTH_OPEN, ES_EVENT_TYPE_AUTH_RENAME,
|
||||
ES_EVENT_TYPE_AUTH_TRUNCATE, ES_EVENT_TYPE_AUTH_UNLINK, ES_EVENT_TYPE_NOTIFY_EXIT,
|
||||
ES_EVENT_TYPE_AUTH_CLONE, ES_EVENT_TYPE_AUTH_COPYFILE, ES_EVENT_TYPE_AUTH_CREATE,
|
||||
ES_EVENT_TYPE_AUTH_EXCHANGEDATA, ES_EVENT_TYPE_AUTH_LINK, ES_EVENT_TYPE_AUTH_OPEN,
|
||||
ES_EVENT_TYPE_AUTH_RENAME, ES_EVENT_TYPE_AUTH_TRUNCATE, ES_EVENT_TYPE_AUTH_UNLINK,
|
||||
ES_EVENT_TYPE_NOTIFY_EXIT,
|
||||
};
|
||||
|
||||
#if HAVE_MACOS_12
|
||||
if (@available(macOS 12.0, *)) {
|
||||
expectedEventSubs.insert(ES_EVENT_TYPE_AUTH_COPYFILE);
|
||||
}
|
||||
#endif
|
||||
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
EXPECT_CALL(*mockESApi, ClearCache)
|
||||
.After(EXPECT_CALL(*mockESApi, Subscribe(testing::_, expectedEventSubs))
|
||||
@@ -749,10 +744,14 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
|
||||
id fileAccessClient = [[SNTEndpointSecurityFileAccessAuthorizer alloc]
|
||||
initWithESAPI:mockESApi
|
||||
metrics:nullptr
|
||||
processor:santa::santad::Processor::kFileAccessAuthorizer];
|
||||
processor:santa::Processor::kFileAccessAuthorizer];
|
||||
|
||||
[fileAccessClient enable];
|
||||
|
||||
for (const auto &event : expectedEventSubs) {
|
||||
XCTAssertNoThrow(santa::EventTypeToString(event));
|
||||
}
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
|
||||
@@ -940,42 +939,40 @@ void ClearWatchItemPolicyProcess(WatchItemPolicy::Process &proc) {
|
||||
XCTAssertFalse(targets[0].devnoIno.has_value());
|
||||
}
|
||||
|
||||
if (@available(macOS 12.0, *)) {
|
||||
{
|
||||
esMsg.event_type = ES_EVENT_TYPE_AUTH_COPYFILE;
|
||||
esMsg.event.copyfile.source = &testFile1;
|
||||
esMsg.event.copyfile.target_dir = &testDir;
|
||||
esMsg.event.copyfile.target_name = testTok;
|
||||
|
||||
{
|
||||
esMsg.event_type = ES_EVENT_TYPE_AUTH_COPYFILE;
|
||||
esMsg.event.copyfile.source = &testFile1;
|
||||
esMsg.event.copyfile.target_dir = &testDir;
|
||||
esMsg.event.copyfile.target_name = testTok;
|
||||
esMsg.event.copyfile.target_file = nullptr;
|
||||
|
||||
{
|
||||
esMsg.event.copyfile.target_file = nullptr;
|
||||
std::vector<PathTarget> targets;
|
||||
PopulatePathTargets(msg, targets);
|
||||
|
||||
std::vector<PathTarget> targets;
|
||||
PopulatePathTargets(msg, targets);
|
||||
XCTAssertEqual(targets.size(), 2);
|
||||
XCTAssertCStringEqual(targets[0].path.c_str(), testFile1.path.data);
|
||||
XCTAssertTrue(targets[0].isReadable);
|
||||
XCTAssertEqual(targets[0].devnoIno.value(), FileID(testFile1));
|
||||
XCTAssertCppStringEqual(targets[1].path, dirTok);
|
||||
XCTAssertFalse(targets[1].isReadable);
|
||||
XCTAssertFalse(targets[1].devnoIno.has_value());
|
||||
}
|
||||
|
||||
XCTAssertEqual(targets.size(), 2);
|
||||
XCTAssertCStringEqual(targets[0].path.c_str(), testFile1.path.data);
|
||||
XCTAssertTrue(targets[0].isReadable);
|
||||
XCTAssertEqual(targets[0].devnoIno.value(), FileID(testFile1));
|
||||
XCTAssertCppStringEqual(targets[1].path, dirTok);
|
||||
XCTAssertFalse(targets[1].isReadable);
|
||||
XCTAssertFalse(targets[1].devnoIno.has_value());
|
||||
}
|
||||
{
|
||||
esMsg.event.copyfile.target_file = &testFile2;
|
||||
|
||||
{
|
||||
esMsg.event.copyfile.target_file = &testFile2;
|
||||
std::vector<PathTarget> targets;
|
||||
PopulatePathTargets(msg, targets);
|
||||
|
||||
std::vector<PathTarget> targets;
|
||||
PopulatePathTargets(msg, targets);
|
||||
|
||||
XCTAssertEqual(targets.size(), 2);
|
||||
XCTAssertCStringEqual(targets[0].path.c_str(), testFile1.path.data);
|
||||
XCTAssertTrue(targets[0].isReadable);
|
||||
XCTAssertEqual(targets[0].devnoIno.value(), FileID(testFile1));
|
||||
XCTAssertCStringEqual(targets[1].path.c_str(), testFile2.path.data);
|
||||
XCTAssertFalse(targets[1].isReadable);
|
||||
XCTAssertFalse(targets[1].devnoIno.has_value());
|
||||
}
|
||||
XCTAssertEqual(targets.size(), 2);
|
||||
XCTAssertCStringEqual(targets[0].path.c_str(), testFile1.path.data);
|
||||
XCTAssertTrue(targets[0].isReadable);
|
||||
XCTAssertEqual(targets[0].devnoIno.value(), FileID(testFile1));
|
||||
XCTAssertCStringEqual(targets[1].path.c_str(), testFile2.path.data);
|
||||
XCTAssertFalse(targets[1].isReadable);
|
||||
XCTAssertFalse(targets[1].devnoIno.has_value());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,18 +29,14 @@
|
||||
@interface SNTEndpointSecurityRecorder
|
||||
: SNTEndpointSecurityTreeAwareClient <SNTEndpointSecurityEventHandler>
|
||||
|
||||
- (instancetype)
|
||||
initWithESAPI:
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)
|
||||
esApi
|
||||
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
|
||||
logger:(std::shared_ptr<santa::santad::logs::endpoint_security::Logger>)logger
|
||||
enricher:
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::Enricher>)enricher
|
||||
compilerController:(SNTCompilerController *)compilerController
|
||||
authResultCache:
|
||||
(std::shared_ptr<santa::santad::event_providers::AuthResultCache>)authResultCache
|
||||
prefixTree:(std::shared_ptr<santa::common::PrefixTree<santa::common::Unit>>)prefixTree
|
||||
processTree:(std::shared_ptr<santa::santad::process_tree::ProcessTree>)processTree;
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::Metrics>)metrics
|
||||
logger:(std::shared_ptr<santa::Logger>)logger
|
||||
enricher:(std::shared_ptr<santa::Enricher>)enricher
|
||||
compilerController:(SNTCompilerController *)compilerController
|
||||
authResultCache:(std::shared_ptr<santa::AuthResultCache>)authResultCache
|
||||
prefixTree:(std::shared_ptr<santa::PrefixTree<santa::Unit>>)prefixTree
|
||||
processTree:
|
||||
(std::shared_ptr<santa::santad::process_tree::ProcessTree>)processTree;
|
||||
|
||||
@end
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#include <EndpointSecurity/EndpointSecurity.h>
|
||||
|
||||
#include "Source/common/Platform.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#include "Source/common/String.h"
|
||||
@@ -26,15 +27,15 @@
|
||||
#include "Source/santad/Metrics.h"
|
||||
#include "Source/santad/ProcessTree/process_tree.h"
|
||||
|
||||
using santa::common::PrefixTree;
|
||||
using santa::common::Unit;
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::event_providers::AuthResultCache;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
|
||||
using santa::santad::event_providers::endpoint_security::Enricher;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::santad::logs::endpoint_security::Logger;
|
||||
using santa::AuthResultCache;
|
||||
using santa::EndpointSecurityAPI;
|
||||
using santa::EnrichedMessage;
|
||||
using santa::Enricher;
|
||||
using santa::EventDisposition;
|
||||
using santa::Logger;
|
||||
using santa::Message;
|
||||
using santa::PrefixTree;
|
||||
using santa::Unit;
|
||||
using santa::santad::process_tree::ProcessTree;
|
||||
|
||||
es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
|
||||
@@ -61,7 +62,7 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
|
||||
}
|
||||
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
|
||||
metrics:(std::shared_ptr<santa::Metrics>)metrics
|
||||
logger:(std::shared_ptr<Logger>)logger
|
||||
enricher:(std::shared_ptr<Enricher>)enricher
|
||||
compilerController:(SNTCompilerController *)compilerController
|
||||
@@ -70,7 +71,7 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
|
||||
processTree:(std::shared_ptr<ProcessTree>)processTree {
|
||||
self = [super initWithESAPI:std::move(esApi)
|
||||
metrics:std::move(metrics)
|
||||
processor:santa::santad::Processor::kRecorder
|
||||
processor:santa::Processor::kRecorder
|
||||
processTree:std::move(processTree)];
|
||||
if (self) {
|
||||
_enricher = enricher;
|
||||
@@ -141,7 +142,7 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
|
||||
}
|
||||
|
||||
// Only log file changes that match the given regex
|
||||
NSString *targetPath = santa::common::StringToNSString(targetFile->path.data);
|
||||
NSString *targetPath = santa::StringToNSString(targetFile->path.data);
|
||||
if (![[self.configurator fileChangesRegex]
|
||||
numberOfMatchesInString:targetPath
|
||||
options:0
|
||||
@@ -186,16 +187,36 @@ es_file_t *GetTargetFileForPrefixTree(const es_message_t *msg) {
|
||||
}
|
||||
|
||||
- (void)enable {
|
||||
[super subscribe:{
|
||||
ES_EVENT_TYPE_NOTIFY_CLOSE,
|
||||
ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA,
|
||||
ES_EVENT_TYPE_NOTIFY_EXEC,
|
||||
ES_EVENT_TYPE_NOTIFY_EXIT,
|
||||
ES_EVENT_TYPE_NOTIFY_FORK,
|
||||
ES_EVENT_TYPE_NOTIFY_LINK,
|
||||
ES_EVENT_TYPE_NOTIFY_RENAME,
|
||||
ES_EVENT_TYPE_NOTIFY_UNLINK,
|
||||
}];
|
||||
// clang-format off
|
||||
std::set<es_event_type_t> events{
|
||||
ES_EVENT_TYPE_NOTIFY_CLOSE,
|
||||
ES_EVENT_TYPE_NOTIFY_EXCHANGEDATA,
|
||||
ES_EVENT_TYPE_NOTIFY_EXEC,
|
||||
ES_EVENT_TYPE_NOTIFY_EXIT,
|
||||
ES_EVENT_TYPE_NOTIFY_FORK,
|
||||
ES_EVENT_TYPE_NOTIFY_LINK,
|
||||
ES_EVENT_TYPE_NOTIFY_RENAME,
|
||||
ES_EVENT_TYPE_NOTIFY_UNLINK,
|
||||
};
|
||||
|
||||
#if HAVE_MACOS_13
|
||||
if (@available(macOS 13.0, *)) {
|
||||
events.insert({
|
||||
ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN,
|
||||
ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT,
|
||||
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN,
|
||||
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT,
|
||||
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK,
|
||||
ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK,
|
||||
ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH,
|
||||
ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH,
|
||||
ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN,
|
||||
ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT});
|
||||
}
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
[super subscribe:events];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <memory>
|
||||
#include <set>
|
||||
|
||||
#include "Source/common/Platform.h"
|
||||
#include "Source/common/PrefixTree.h"
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#include "Source/common/TestUtils.h"
|
||||
@@ -38,15 +39,15 @@
|
||||
#include "Source/santad/Metrics.h"
|
||||
#import "Source/santad/SNTCompilerController.h"
|
||||
|
||||
using santa::common::PrefixTree;
|
||||
using santa::common::Unit;
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::Processor;
|
||||
using santa::santad::event_providers::AuthResultCache;
|
||||
using santa::santad::event_providers::endpoint_security::EnrichedMessage;
|
||||
using santa::santad::event_providers::endpoint_security::Enricher;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::santad::logs::endpoint_security::Logger;
|
||||
using santa::AuthResultCache;
|
||||
using santa::EnrichedMessage;
|
||||
using santa::Enricher;
|
||||
using santa::EventDisposition;
|
||||
using santa::Logger;
|
||||
using santa::Message;
|
||||
using santa::PrefixTree;
|
||||
using santa::Processor;
|
||||
using santa::Unit;
|
||||
|
||||
class MockEnricher : public Enricher {
|
||||
public:
|
||||
@@ -90,6 +91,24 @@ class MockLogger : public Logger {
|
||||
ES_EVENT_TYPE_NOTIFY_FORK, ES_EVENT_TYPE_NOTIFY_EXIT, ES_EVENT_TYPE_NOTIFY_LINK,
|
||||
ES_EVENT_TYPE_NOTIFY_RENAME, ES_EVENT_TYPE_NOTIFY_UNLINK,
|
||||
};
|
||||
|
||||
#if HAVE_MACOS_13
|
||||
if (@available(macOS 13.0, *)) {
|
||||
expectedEventSubs.insert({
|
||||
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGIN,
|
||||
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOGOUT,
|
||||
ES_EVENT_TYPE_NOTIFY_LW_SESSION_LOCK,
|
||||
ES_EVENT_TYPE_NOTIFY_LW_SESSION_UNLOCK,
|
||||
ES_EVENT_TYPE_NOTIFY_SCREENSHARING_ATTACH,
|
||||
ES_EVENT_TYPE_NOTIFY_SCREENSHARING_DETACH,
|
||||
ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGIN,
|
||||
ES_EVENT_TYPE_NOTIFY_OPENSSH_LOGOUT,
|
||||
ES_EVENT_TYPE_NOTIFY_LOGIN_LOGIN,
|
||||
ES_EVENT_TYPE_NOTIFY_LOGIN_LOGOUT,
|
||||
});
|
||||
}
|
||||
#endif
|
||||
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
|
||||
id recorderClient = [[SNTEndpointSecurityRecorder alloc] initWithESAPI:mockESApi
|
||||
@@ -100,6 +119,10 @@ class MockLogger : public Logger {
|
||||
|
||||
[recorderClient enable];
|
||||
|
||||
for (const auto &event : expectedEventSubs) {
|
||||
XCTAssertNoThrow(santa::EventTypeToString(event));
|
||||
}
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
}
|
||||
|
||||
@@ -172,7 +195,7 @@ es_file_t targetFileMissesRegex = MakeESFile("/foo/misses");
|
||||
[mockCC stopMocking];
|
||||
}
|
||||
|
||||
- (void)testHandleMessageWithCloseMappedWriteable {
|
||||
- (void)testHandleEventCloseMappedWritableMatchesRegex {
|
||||
#if HAVE_MACOS_13
|
||||
if (@available(macOS 13.0, *)) {
|
||||
// CLOSE not modified, but was_mapped_writable, should remove from cache,
|
||||
@@ -199,12 +222,12 @@ es_file_t targetFileMissesRegex = MakeESFile("/foo/misses");
|
||||
XCTAssertSemaTrue(*sema, 5, "Log wasn't called within expected time window");
|
||||
};
|
||||
|
||||
[self handleMessageWithMatchCalls:YES withMissCalls:NO withBlock:testBlock];
|
||||
[self handleMessageShouldLog:YES shouldRemoveFromCache:YES withBlock:testBlock];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
- (void)testHandleEventCloseNotModifiedWithWasMappedWritable {
|
||||
- (void)testHandleEventCloseMappedWritableMissesRegex {
|
||||
#if HAVE_MACOS_13
|
||||
if (@available(macOS 13.0, *)) {
|
||||
// CLOSE not modified, but was_mapped_writable, remove from cache, and does not match
|
||||
@@ -220,13 +243,15 @@ es_file_t targetFileMissesRegex = MakeESFile("/foo/misses");
|
||||
esMsg->event.close.target = &targetFileMissesRegex;
|
||||
Message msg(mockESApi, esMsg);
|
||||
|
||||
OCMExpect([mockCC handleEvent:msg withLogger:nullptr]).ignoringNonObjectArgs();
|
||||
|
||||
XCTAssertNoThrow([recorderClient handleMessage:Message(mockESApi, esMsg)
|
||||
recordEventMetrics:^(EventDisposition d) {
|
||||
XCTFail("Metrics record callback should not be called here");
|
||||
}]);
|
||||
};
|
||||
|
||||
[self handleMessageWithMatchCalls:NO withMissCalls:YES withBlock:testBlock];
|
||||
[self handleMessageShouldLog:NO shouldRemoveFromCache:YES withBlock:testBlock];
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -26,10 +26,8 @@
|
||||
@interface SNTEndpointSecurityTamperResistance
|
||||
: SNTEndpointSecurityClient <SNTEndpointSecurityEventHandler>
|
||||
|
||||
- (instancetype)
|
||||
initWithESAPI:
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
|
||||
logger:(std::shared_ptr<santa::santad::logs::endpoint_security::Logger>)logger;
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::Metrics>)metrics
|
||||
logger:(std::shared_ptr<santa::Logger>)logger;
|
||||
|
||||
@end
|
||||
|
||||
@@ -15,18 +15,20 @@
|
||||
#import "Source/santad/EventProviders/SNTEndpointSecurityTamperResistance.h"
|
||||
|
||||
#include <EndpointSecurity/ESTypes.h>
|
||||
#include <bsm/libbsm.h>
|
||||
#include <string.h>
|
||||
#include <algorithm>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#include "Source/santad/DataLayer/WatchItemPolicy.h"
|
||||
#include "Source/santad/EventProviders/EndpointSecurity/Message.h"
|
||||
#include "Source/santad/Metrics.h"
|
||||
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::santad::logs::endpoint_security::Logger;
|
||||
using santa::EndpointSecurityAPI;
|
||||
using santa::EventDisposition;
|
||||
using santa::Logger;
|
||||
using santa::Message;
|
||||
using santa::WatchItemPathType;
|
||||
|
||||
static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-driver";
|
||||
|
||||
@@ -35,11 +37,11 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
|
||||
}
|
||||
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
|
||||
metrics:(std::shared_ptr<santa::Metrics>)metrics
|
||||
logger:(std::shared_ptr<Logger>)logger {
|
||||
self = [super initWithESAPI:std::move(esApi)
|
||||
metrics:std::move(metrics)
|
||||
processor:santa::santad::Processor::kTamperResistance];
|
||||
processor:santa::Processor::kTamperResistance];
|
||||
if (self) {
|
||||
_logger = logger;
|
||||
|
||||
@@ -85,6 +87,26 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
|
||||
break;
|
||||
}
|
||||
|
||||
case ES_EVENT_TYPE_AUTH_SIGNAL: {
|
||||
// Only block signals sent to us and not from launchd.
|
||||
if (audit_token_to_pid(esMsg->event.signal.target->audit_token) == getpid() &&
|
||||
audit_token_to_pid(esMsg->process->audit_token) != 1) {
|
||||
LOGW(@"Preventing attempt to kill Santa daemon");
|
||||
result = ES_AUTH_RESULT_DENY;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ES_EVENT_TYPE_AUTH_EXEC: {
|
||||
// When not running a debug build, prevent attempts to kill Santa
|
||||
// by launchctl commands.
|
||||
#ifndef DEBUG
|
||||
result = ValidateLaunchctlExec(esMsg);
|
||||
if (result == ES_AUTH_RESULT_DENY) LOGW(@"Preventing attempt to kill Santa daemon");
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
|
||||
case ES_EVENT_TYPE_AUTH_KEXTLOAD: {
|
||||
// TODO(mlw): Since we don't package the kext anymore, we should consider removing this.
|
||||
// TODO(mlw): Consider logging when kext loads are attempted.
|
||||
@@ -120,15 +142,53 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
|
||||
for (const auto &path : protectedPaths) {
|
||||
watchPaths.push_back({path, WatchItemPathType::kLiteral});
|
||||
}
|
||||
watchPaths.push_back({"/Library/SystemExtensions", WatchItemPathType::kPrefix});
|
||||
watchPaths.push_back({"/bin/launchctl", WatchItemPathType::kLiteral});
|
||||
|
||||
// Begin watching the protected set
|
||||
[super muteTargetPaths:watchPaths];
|
||||
|
||||
[super subscribeAndClearCache:{
|
||||
ES_EVENT_TYPE_AUTH_KEXTLOAD,
|
||||
ES_EVENT_TYPE_AUTH_SIGNAL,
|
||||
ES_EVENT_TYPE_AUTH_EXEC,
|
||||
ES_EVENT_TYPE_AUTH_UNLINK,
|
||||
ES_EVENT_TYPE_AUTH_RENAME,
|
||||
}];
|
||||
}
|
||||
|
||||
es_auth_result_t ValidateLaunchctlExec(const Message &esMsg) {
|
||||
es_string_token_t exec_path = esMsg->event.exec.target->executable->path;
|
||||
if (strncmp(exec_path.data, "/bin/launchctl", exec_path.length) != 0) {
|
||||
return ES_AUTH_RESULT_ALLOW;
|
||||
}
|
||||
|
||||
// Ensure there are at least 2 arguments after the command
|
||||
std::shared_ptr<EndpointSecurityAPI> esApi = esMsg.ESAPI();
|
||||
uint32_t argCount = esApi->ExecArgCount(&esMsg->event.exec);
|
||||
if (argCount < 2) {
|
||||
return ES_AUTH_RESULT_ALLOW;
|
||||
}
|
||||
|
||||
// Check for some allowed subcommands
|
||||
es_string_token_t arg = esApi->ExecArg(&esMsg->event.exec, 1);
|
||||
static const std::unordered_set<std::string> safe_commands{
|
||||
"blame", "help", "hostinfo", "list", "plist", "print", "procinfo",
|
||||
};
|
||||
if (safe_commands.find(std::string(arg.data, arg.length)) != safe_commands.end()) {
|
||||
return ES_AUTH_RESULT_ALLOW;
|
||||
}
|
||||
|
||||
// Check whether com.google.santa.daemon is in the argument list.
|
||||
// launchctl no longer accepts PIDs to operate on.
|
||||
for (int i = 2; i < argCount; i++) {
|
||||
es_string_token_t arg = esApi->ExecArg(&esMsg->event.exec, i);
|
||||
if (strnstr(arg.data, "com.google.santa.daemon", arg.length) != NULL) {
|
||||
return ES_AUTH_RESULT_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
return ES_AUTH_RESULT_ALLOW;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -31,10 +31,10 @@
|
||||
#import "Source/santad/EventProviders/SNTEndpointSecurityTamperResistance.h"
|
||||
#import "Source/santad/Metrics.h"
|
||||
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::data_layer::WatchItemPathType;
|
||||
using santa::santad::event_providers::endpoint_security::Client;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::Client;
|
||||
using santa::EventDisposition;
|
||||
using santa::Message;
|
||||
using santa::WatchItemPathType;
|
||||
|
||||
static constexpr std::string_view kEventsDBPath = "/private/var/db/santa/events.db";
|
||||
static constexpr std::string_view kRulesDBPath = "/private/var/db/santa/rules.db";
|
||||
@@ -49,9 +49,8 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
|
||||
- (void)testEnable {
|
||||
// Ensure the client subscribes to expected event types
|
||||
std::set<es_event_type_t> expectedEventSubs{
|
||||
ES_EVENT_TYPE_AUTH_KEXTLOAD,
|
||||
ES_EVENT_TYPE_AUTH_UNLINK,
|
||||
ES_EVENT_TYPE_AUTH_RENAME,
|
||||
ES_EVENT_TYPE_AUTH_KEXTLOAD, ES_EVENT_TYPE_AUTH_SIGNAL, ES_EVENT_TYPE_AUTH_EXEC,
|
||||
ES_EVENT_TYPE_AUTH_UNLINK, ES_EVENT_TYPE_AUTH_RENAME,
|
||||
};
|
||||
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
@@ -70,6 +69,8 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
|
||||
// Setup mocks to handle muting the rules db and events db
|
||||
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, testing::_, WatchItemPathType::kLiteral))
|
||||
.WillRepeatedly(testing::Return(true));
|
||||
EXPECT_CALL(*mockESApi, MuteTargetPath(testing::_, testing::_, WatchItemPathType::kPrefix))
|
||||
.WillRepeatedly(testing::Return(true));
|
||||
|
||||
SNTEndpointSecurityTamperResistance *tamperClient =
|
||||
[[SNTEndpointSecurityTamperResistance alloc] initWithESAPI:mockESApi
|
||||
@@ -79,6 +80,10 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
|
||||
|
||||
[mockTamperClient enable];
|
||||
|
||||
for (const auto &event : expectedEventSubs) {
|
||||
XCTAssertNoThrow(santa::EventTypeToString(event));
|
||||
}
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
[mockTamperClient stopMocking];
|
||||
}
|
||||
@@ -86,7 +91,7 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
|
||||
- (void)testHandleMessage {
|
||||
es_file_t file = MakeESFile("foo");
|
||||
es_process_t proc = MakeESProcess(&file);
|
||||
es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_AUTH_EXEC, &proc, ActionType::Auth);
|
||||
es_message_t esMsg = MakeESMessage(ES_EVENT_TYPE_AUTH_LINK, &proc, ActionType::Auth);
|
||||
|
||||
es_file_t fileEventsDB = MakeESFile(kEventsDBPath.data());
|
||||
es_file_t fileRulesDB = MakeESFile(kRulesDBPath.data());
|
||||
@@ -106,6 +111,12 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
|
||||
{&benignTok, ES_AUTH_RESULT_ALLOW},
|
||||
};
|
||||
|
||||
std::map<std::pair<pid_t, pid_t>, es_auth_result_t> pidsToResult{
|
||||
{{getpid(), 31838}, ES_AUTH_RESULT_DENY},
|
||||
{{getpid(), 1}, ES_AUTH_RESULT_ALLOW},
|
||||
{{435, 98381}, ES_AUTH_RESULT_ALLOW},
|
||||
};
|
||||
|
||||
dispatch_semaphore_t semaMetrics = dispatch_semaphore_create(0);
|
||||
|
||||
auto mockESApi = std::make_shared<MockEndpointSecurityAPI>();
|
||||
@@ -232,6 +243,31 @@ static constexpr std::string_view kSantaKextIdentifier = "com.google.santa-drive
|
||||
}
|
||||
}
|
||||
|
||||
// Check SIGNAL tamper events
|
||||
{
|
||||
esMsg.event_type = ES_EVENT_TYPE_AUTH_SIGNAL;
|
||||
|
||||
for (const auto &kv : pidsToResult) {
|
||||
Message msg(mockESApi, &esMsg);
|
||||
es_process_t target_proc = MakeESProcess(&file);
|
||||
target_proc.audit_token = MakeAuditToken(kv.first.first, 42);
|
||||
esMsg.event.signal.target = &target_proc;
|
||||
esMsg.process->audit_token = MakeAuditToken(kv.first.second, 42);
|
||||
|
||||
[mockTamperClient
|
||||
handleMessage:std::move(msg)
|
||||
recordEventMetrics:^(EventDisposition d) {
|
||||
XCTAssertEqual(d, kv.second == ES_AUTH_RESULT_DENY ? EventDisposition::kProcessed
|
||||
: EventDisposition::kDropped);
|
||||
dispatch_semaphore_signal(semaMetrics);
|
||||
}];
|
||||
|
||||
XCTAssertSemaTrue(semaMetrics, 5, "Metrics not recorded within expected window");
|
||||
XCTAssertEqual(gotAuthResult, kv.second);
|
||||
XCTAssertEqual(gotCachable, kv.second == ES_AUTH_RESULT_ALLOW);
|
||||
}
|
||||
}
|
||||
|
||||
XCTBubbleMockVerifyAndClearExpectations(mockESApi.get());
|
||||
XCTAssertTrue(OCMVerifyAll(mockTamperClient));
|
||||
|
||||
|
||||
@@ -21,10 +21,9 @@
|
||||
@interface SNTEndpointSecurityTreeAwareClient : SNTEndpointSecurityClient
|
||||
@property std::shared_ptr<santa::santad::process_tree::ProcessTree> processTree;
|
||||
|
||||
- (instancetype)
|
||||
initWithESAPI:
|
||||
(std::shared_ptr<santa::santad::event_providers::endpoint_security::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::santad::Metrics>)metrics
|
||||
processor:(santa::santad::Processor)processor
|
||||
processTree:(std::shared_ptr<santa::santad::process_tree::ProcessTree>)processTree;
|
||||
- (instancetype)initWithESAPI:(std::shared_ptr<santa::EndpointSecurityAPI>)esApi
|
||||
metrics:(std::shared_ptr<santa::Metrics>)metrics
|
||||
processor:(santa::Processor)processor
|
||||
processTree:
|
||||
(std::shared_ptr<santa::santad::process_tree::ProcessTree>)processTree;
|
||||
@end
|
||||
|
||||
@@ -23,11 +23,11 @@
|
||||
#include "Source/santad/ProcessTree/process_tree.h"
|
||||
#include "Source/santad/ProcessTree/process_tree_macos.h"
|
||||
|
||||
using santa::santad::EventDisposition;
|
||||
using santa::santad::Metrics;
|
||||
using santa::santad::Processor;
|
||||
using santa::santad::event_providers::endpoint_security::EndpointSecurityAPI;
|
||||
using santa::santad::event_providers::endpoint_security::Message;
|
||||
using santa::EndpointSecurityAPI;
|
||||
using santa::EventDisposition;
|
||||
using santa::Message;
|
||||
using santa::Metrics;
|
||||
using santa::Processor;
|
||||
|
||||
@implementation SNTEndpointSecurityTreeAwareClient {
|
||||
std::vector<bool> _addedEvents;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user