Compare commits

...

52 Commits

Author SHA1 Message Date
Pete Markowsky
82b71c0f20 Add a metrics command to santactl (#687)
Add a metrics command to santactl.
2021-12-02 14:30:39 -05:00
Allister Banks
10ccee9e4c Docs: EnableSysxCache docs, etc (#684)
* Add more Conf references, EnableSysxCache key, etc
* Updated link (even though previous config profile explainer link redirects accordingly) to profile spec.
* Added brief explanation of TCC/PPPC and made reference to the non-setting example MDM mobileconfig files in the repo
* Add sysext log stream example, update wording
* Pointed at events and configuration pages for details about logging
* New troubleshooting section
* Standardized on asterisks for page link markup in the TOC index page
2021-11-22 22:22:49 -05:00
Pete Markowsky
acbbb9e7b0 Add a configuration option for users to add their own root labels (#683)
Add an option for users to add their own root labels.

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2021-11-19 10:34:18 -05:00
Kent Ma
3939ad9813 Add santametricservice information to santactl status (#679) 2021-11-16 16:04:59 -05:00
Kent Ma
d20455252d Update santactl fileinfo, sync, and status to show teamID info (#678)
* Update santactl fileinfo, sync, and status to show teamID info
2021-11-16 14:57:02 -05:00
Pete Markowsky
5cd901034f Fixed up typo related to hostname vs. host_name (#676)
Fixed up typo related to hostname vs. host_name.
2021-11-15 15:28:41 -05:00
Kent Ma
4e82392370 Update cli flag for --teamid in santactl (#675) 2021-11-11 15:56:18 +00:00
np5
19710f7233 Do not store Allow TeamID events in the database (#674) 2021-11-11 10:44:39 -05:00
Russell Hancox
27e32bd9ff Tests: Update SNTMetricHTTPWriterTest (#673) 2021-11-11 08:59:14 -05:00
Kent Ma
c268ad4f9a Change SNTEventLog to be a singleton emit a singleton Logger object (#670)
* Change SNTEventLog to be a singleton emit a singleton Logger object
2021-11-10 17:23:01 -05:00
Russell Hancox
f7a1a4cb39 Tests: Fix MetricServiceTest compatible with public OCMock (#669) 2021-11-08 15:53:57 -05:00
Russell Hancox
ad6e03e6cc Tests: Stop using NSInvocation with OCMock's .andDo() (#667) 2021-11-08 12:19:20 -05:00
Russell Hancox
8ecc3f879a Tests: Fix some flaky tests. (#666)
1. OCMock objects don't need stopMocking to be called - it's only necessary to call that in cases where the original object behavior must be restored before the end of the test. Otherwise the mock automatically restores during deallocation.
2. SNTMetricRawJSONFormat still used a plain NSDateFormatter and so was applying timezone calculations. In tests we've switched to using NSISO8601DateFormatter but this requires 10.13 and our deployment target is still 10.9 so I've stuck to applying the UTC timezone to the formatter instead.
2021-11-05 18:03:57 -04:00
Pete Markowsky
d51093501c Fix Flaky Execution Controller Tests (#665)
* Fix up some issues with flaky tests.
2021-11-05 13:51:04 -04:00
np5
05dd1b6215 Add AboutText option for the Santa.app (#662) 2021-11-04 22:02:23 -04:00
Pete Markowsky
8c3320e3e9 Change NSDateFormatter to NSISO8601DateFormatter (#661)
Change NSDateFormatter to NSISO8601DateFormatter.
2021-11-02 13:11:51 -04:00
Tom Burgin
369dc9a63c Add KVO binding for EnableBadSignatureProtection (#659) 2021-10-28 17:34:56 -04:00
Pete Markowsky
7adc55007c Change to NSISO8601DateFormatter to ensure UTC timestamps in unit tests (#658)
Change to NSISO8601DateFormatter to ensure UTC timestamps in unit tests.
2021-10-28 15:34:31 -04:00
Edward Marczak
fe6be921d3 Add EnableBadSignatureProtection key (#656)
Add EnableBadSignatureProtection key and description into the configuration.md doc.
2021-10-28 10:02:24 -04:00
Pete Markowsky
23b31ec413 Add build matrix for build / test steps to shake out OS nuances (#654)
Add build matrix for build / test steps to shake out OS nuances.

Remove macos-latest from build matrix.
2021-10-26 16:14:24 -04:00
Pete Markowsky
727b009a1c Fixed one set of tests. (#652) 2021-10-26 15:36:54 -04:00
Pete Markowsky
1c42f06135 Add Metrics and Metrics Service to Santa (#641)
Add santametricservice and basic metrics to Santad.

This PR adds the santametricservice, and adds basic metrics to santad.  It also updates the SNTMetricSet to have and updates packaging scripts to include the santametricservice (aka metric service) in the final bundle.

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2021-10-26 09:25:10 -04:00
Kent Ma
e1cf8e70a3 Add continuous workflow job for checking for flakes (#650)
Co-authored-by: Pete Markowsky <pmarkowsky@users.noreply.github.com>
2021-10-25 10:23:42 -04:00
Russell Hancox
7a500b8135 Packaging: Fix syntax error in package_and_sign.sh (#651) 2021-10-22 09:15:20 -04:00
Pete Markowsky
3702af0309 Add description to SNTMetricSet and Fix issues with SNTMetricMonarchJSONFormat (#649)
* Added description to SNTMetricSet and fixed typos in SNTMonarchJSONFormat.
2021-10-21 16:41:24 -04:00
Russell Hancox
697cd29a0a Project: Include package files in release tarball (#648) 2021-10-20 21:31:33 +00:00
Kent Ma
5735a12424 Update list of critical system binaries and include comment about Monterrey behavior (#647) 2021-10-20 16:45:42 -04:00
Russell Hancox
07b8f2121d Project: Include new packaging files in release tarball (#646) 2021-10-20 12:54:00 -04:00
Russell Hancox
78a1a929fd Project: Check-in packaging and signing script. (#645)
This is largely a copy of what we've been using so far but with previously hardcoded stuff replaced with environment variables.
2021-10-20 11:47:30 -04:00
Russell Hancox
9163417b54 santad: enable sysx cache by default (#644)
We've had this enabled long enough now to know that it works correctly and helps performance considerably, so let's have it on by default.
2021-10-18 18:17:11 -04:00
Kent Ma
fa6630a31a Rename shasum to identifier in database (#643) 2021-10-18 13:27:36 -04:00
Kent Ma
1f2b82fc58 Allow banning of team IDs. (#640) 2021-10-18 09:52:56 -04:00
Kent Ma
b77b0142af Add microbenchmark for execs on SNTApplication (#639) 2021-10-15 15:57:04 -04:00
Russell Hancox
2f80a42845 Project: Build driver if files in Source/common/* change (#637) 2021-10-15 15:03:16 +00:00
Russell Hancox
67db370492 Common/Kernel: Add some missing defines to libs included in driver (#638) 2021-10-14 13:05:33 -04:00
Russell Hancox
a0319ecf52 Project: Bump to 2021.8 (#636)
Co-authored-by: Kent Ma <tnek@google.com>
2021-10-13 14:37:44 -04:00
Pete Markowsky
16d0bd6db6 Add Support for Formatting metrics for Monarch (#633)
* Initial commit of a Format that converts SNTMetricSet dictionaries to a format consumable by Monarch tooling.

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2021-10-11 16:01:50 -04:00
Kent Ma
9e3943ec68 Add error on lint failure and include a fix.sh (#632)
Add error on lint failure, include a fix.sh, and fix existing linter errors.
2021-10-11 11:33:10 -04:00
Kent Ma
e461b4bfbc Use direct path in integration_tests.sh instead of relative path (#631) 2021-10-07 13:07:14 -04:00
Russell Hancox
8f836afe86 * Project: Update README and docs/details/santactl (#630)
Re-organized some sections, removed some obsolete statements, fixed a few links.
2021-10-06 17:12:53 -04:00
Russell Hancox
04ad1c34ba Project: Update entitlements files (#629) 2021-10-06 11:36:58 -04:00
Pete Markowsky
c3042e21dc Add a workflow for checking links in Markdown files. (#628) 2021-10-05 19:28:03 -04:00
Russell Hancox
3ede20a121 Project: Fix issues link in README (#626) 2021-10-05 15:49:39 -04:00
Russell Hancox
976118cce4 santactl/sync: Fix tests for santactl/sync (#625)
This test has been around since early 2016 but has been un-runnable since early 2019.
2021-10-05 13:17:50 -04:00
Pete Markowsky
ea85f0f539 Initial commit of an HTTP writer for SNTMetricSets (#624)
* Initial commit of an HTTP writer for SNTMetricSets.

This PR adds support for shipping serialized SNTMetricSets to an HTTP server via POSTs.

Co-authored-by: Russell Hancox <russellhancox@users.noreply.github.com>
2021-10-04 19:49:40 -04:00
Russell Hancox
d193b05057 Tests: ensure SNTPrefixTree test finishes executing at appropriate time (#623) 2021-10-04 15:41:14 -04:00
Russell Hancox
9fb4f2e171 README: sync servers; remove upvote, sort the rest alphabetically. (#622) 2021-10-04 11:48:13 -04:00
Kent Ma
58cec5819a Add linter step with clang-format and buildifier. (#620)
Also lint our files accordingly
2021-10-01 16:51:06 -04:00
Kent Ma
6ba5831f2d Run buildifier (#619) 2021-10-01 15:18:33 -04:00
Kent Ma
a22e3ead83 Add regular execution integration tests (#618) 2021-10-01 15:07:56 -04:00
Kent Ma
2611b551ce Add provisioningprofile for santactl so that it's properly signed (#617) 2021-10-01 13:00:12 -04:00
Kent Ma
023f96f5c8 Detect existence of a provisionprofile and use that instead in build_and_sign.sh (#616) 2021-10-01 10:07:54 -04:00
115 changed files with 3799 additions and 606 deletions

View File

@@ -0,0 +1,13 @@
name: Check Markdown links
on:
pull_request:
paths:
- "**.md"
jobs:
markdown-link-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@master
- uses: gaurav-nelson/github-action-markdown-link-check@v1

View File

@@ -9,7 +9,7 @@ on:
jobs:
preqs:
runs-on: macos-latest
runs-on: ubuntu-latest
outputs:
run_build_and_tests: ${{ steps.step1.outputs.run_build_and_tests }}
build_driver: ${{ steps.step1.outputs.build_driver }}
@@ -32,29 +32,42 @@ jobs:
for file in `cat files.txt`; do
if [[ $file = Source/* ]]; then
build_and_run_test=1;
if [[ $file = Source/santa_driver/* ]]; then
if [[ $file = Source/santa_driver/* || $file = Source/common/* ]]; then
build_driver=1;
break;
fi
fi
done
if [[ $build_and_run_test != 0 ]]; then
if [[ $build_and_run_test != 0 ]]; then
echo "NEED TO RUN BUILD AND TESTS"
echo "::set-output name=run_build_and_tests::true"
else
else
echo "::set-output name=run_build_and_tests::false"
fi
if [[ $build_driver != 0 ]]; then
echo "NEED TO BUILD DRIVER"
echo "::set-output name=build_driver::true"
else
else
echo "::set-output name=build_driver::false"
fi
lint:
runs-on: ubuntu-latest
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
- uses: actions/checkout@v2
- name: Run linters
run: ./Testing/lint.sh
build_userspace:
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11]
runs-on: ${{ matrix.os }}
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
@@ -63,7 +76,11 @@ jobs:
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=ci
build_driver:
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11]
runs-on: ${{ matrix.os }}
needs: [preqs]
if: needs.preqs.outputs.build_driver == 'true'
steps:
@@ -72,7 +89,11 @@ jobs:
run: bazel build --apple_generate_dsym -c opt :release_driver --define=SANTA_BUILD_TYPE=ci
unit_tests:
runs-on: macos-latest
strategy:
fail-fast: false
matrix:
os: [macos-10.15, macos-11]
runs-on: ${{ matrix.os }}
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
@@ -81,7 +102,7 @@ jobs:
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=ci
test_coverage:
runs-on: macos-latest
runs-on: macos-11
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
@@ -94,3 +115,12 @@ jobs:
github-token: ${{ secrets.GITHUB_TOKEN }}
path-to-lcov: ./CoverageData/info.lcov
flag-name: Unit
benchmark:
runs-on: macos-11
needs: [preqs]
if: needs.preqs.outputs.run_build_and_tests == 'true'
steps:
- uses: actions/checkout@v2
- name: Run All Tests
run: ./Testing/benchmark.sh

13
.github/workflows/continuous.yml vendored Normal file
View File

@@ -0,0 +1,13 @@
name: continuous
on:
schedule:
- cron: '* 10 * * *' # Every day at 10:00 UTC
workflow_dispatch: # Allows you to run this workflow manually from the Actions tab
jobs:
preqs:
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- name: Checks for flaky tests
run: bazel test --test_strategy=exclusive --test_output=errors --runs_per_test 50 -t- :unit_tests --define=SANTA_BUILD_TYPE=ci

1
.gitignore vendored
View File

@@ -14,3 +14,4 @@ tulsigen-*
*.pem
*.p12
*.keychain
*.swp

33
BUILD
View File

@@ -40,7 +40,6 @@ package_group(
packages = ["//..."],
)
################################################################################
# Loading/Unloading/Reloading
################################################################################
@@ -49,6 +48,7 @@ run_command(
cmd = """
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.bundleservice.plist 2>/dev/null
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.metricservice.plist 2>/dev/null
sudo kextunload -b com.google.santa-driver 2>/dev/null
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
""",
@@ -59,6 +59,7 @@ run_command(
cmd = """
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
sudo launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
sudo launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
launchctl load /Library/LaunchAgents/com.google.santa.plist
""",
)
@@ -95,11 +96,14 @@ genrule(
"Conf/install.sh",
"Conf/uninstall.sh",
"Conf/com.google.santa.bundleservice.plist",
"Conf/com.google.santa.metricservice.plist",
"Conf/com.google.santad.plist",
"Conf/com.google.santa.plist",
"Conf/com.google.santa.asl.conf",
"Conf/com.google.santa.newsyslog.conf",
"Conf/Package/Makefile",
"Conf/Package/Distribution.xml",
"Conf/Package/notarization_tool.sh",
"Conf/Package/package_and_sign.sh",
"Conf/Package/postinstall",
"Conf/Package/preinstall",
],
@@ -120,9 +124,9 @@ genrule(
# Copy config files
for SRC in $(SRCS); do
if [[ "$$(dirname $${SRC})" == *"Conf" ]]; then
if [[ "$$(dirname $${SRC})" == *"Conf"* ]]; then
mkdir -p $(@D)/conf
cp $${SRC} $(@D)/conf/
cp -H $${SRC} $(@D)/conf/
fi
done
@@ -141,6 +145,10 @@ genrule(
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santabundleservice.dSYM
;;
*santametricservice.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santametricservice.dSYM
;;
*Santa.app.dSYM*Info.plist)
mkdir -p $(@D)/dsym
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/Santa.app.dSYM
@@ -223,15 +231,24 @@ genrule(
test_suite(
name = "unit_tests",
tests = [
"//Source/common:SantaCacheTest",
"//Source/common:SNTFileInfoTest",
"//Source/common:SNTMetricSetTest",
"//Source/common:SNTPrefixTreeTest",
"//Source/santactl:SNTCommandFileInfoTest",
"//Source/santactl:SNTCommandSyncTest",
"//Source/common:SantaCacheTest",
"//Source/santactl:unit_tests",
"//Source/santad:SNTApplicationCoreMetricsTest",
"//Source/santad:SNTApplicationTest",
"//Source/santad:SNTEndpointSecurityManagerTest",
"//Source/santad:SNTEventTableTest",
"//Source/santad:SNTExecutionControllerTest",
"//Source/santad:SNTRuleTableTest",
"//Source/santad:SNTEndpointSecurityManagerTest",
"//Source/santametricservice:unit_tests",
],
)
test_suite(
name = "benchmarks",
tests = [
"//Source/santad:SNTApplicationBenchmark",
],
)

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<installer-gui-script minSpecVersion="1">
<title>Santa</title>
<options customize="never" allow-external-scripts="no"/>
<choices-outline>
<line choice="default" />
</choices-outline>
<choice id="default">
<pkg-ref id="com.google.santa"/>
<pkg-ref id="com.google.santa-driver"/>
</choice>
<pkg-ref id="com.google.santa">app.pkg</pkg-ref>
<pkg-ref id="com.google.santa-driver" active="system.compareVersions(my.target.systemVersion.ProductVersion, '10.15') &lt; 0">kext.pkg</pkg-ref>
</installer-gui-script>

View File

@@ -1,95 +0,0 @@
#
# Package Makefile for Santa
# Requires TheLuggage (github.com/unixorn/luggage) to be installed
#
# Will generate a package based on the latest release. You can replace
# the PACKAGE_VERSION variable with a specific variable instead if you wish.
#
LUGGAGE:=/usr/local/share/luggage/luggage.make
include ${LUGGAGE}
TITLE:=santa
REVERSE_DOMAIN:=com.google
# Get latest Release version using the GitHub API. Each release is bound to a
# git tag, which should always be a semantic version number. The most recent
# release is always first in the API result.
PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/releases |\
python -c 'import json, sys; print json.load(sys.stdin)[0]["tag_name"]' 2>/dev/null)
# Get the download URL for the latest Release. Each release should have a
# tarball named santa-$version.tar.bz2 containing all of the files associated
# with that release. The tarball layout is:
#
# santa-$version.tar.bz2
# +--santa-$version
# |-- binaries
# | |-- santa-driver.kext
# | |-- Santa.app
# |-- conf
# | |-- install.sh
# | |-- com.google.santad.plist
# | |-- com.google.santagui.plist
# | +-- com.google.santa.asl.conf
# | +-- com.google.santa.newsyslog.conf
# +--dsym
# |-- santa-driver.kext.dSYM
# |-- Santa.app.dSYM
# |-- santad.dSYM
# +-- santactl.dSYM
PACKAGE_DOWNLOAD_URL:="https://github.com/google/santa/releases/download/${PACKAGE_VERSION}/santa-${PACKAGE_VERSION}.tar.bz2"
PAYLOAD:=pack-Library-Extensions-santa-driver.kext \
pack-applications-Santa.app \
pack-Library-LaunchDaemons-com.google.santad.plist \
pack-Library-LaunchAgents-com.google.santagui.plist \
pack-etc-asl-com.google.santa.asl.conf \
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf \
pack-script-preinstall \
pack-script-postinstall
santa-driver.kext: download
Santa.app: download
com.google.santad.plist: download
com.google.santagui.plist: download
com.google.santa.asl.conf: download
com.google.santa.newsyslog.conf: download
download:
$(if $(PACKAGE_VERSION),, $(error GitHub API returned unexpected result. Wait a while and try again))
@curl -fL ${PACKAGE_DOWNLOAD_URL} | tar xvj --strip=2
@rm -rf *.dSYM
pack-etc-asl-com.google.santa.asl.conf: com.google.santa.asl.conf l_private_etc
@sudo mkdir -p ${WORK_D}/private/etc/asl
@sudo chown root:wheel ${WORK_D}/private/etc/asl
@sudo chmod 755 ${WORK_D}/private/etc/asl
@sudo install -m 644 -o root -g wheel com.google.santa.asl.conf ${WORK_D}/private/etc/asl
pack-etc-newsyslog.d-com.google.santa.newsyslog.conf: com.google.santa.newsyslog.conf l_private_etc
@sudo mkdir -p ${WORK_D}/private/etc/newsyslog.d
@sudo chown root:wheel ${WORK_D}/private/etc/newsyslog.d
@sudo chmod 755 ${WORK_D}/private/etc/newsyslog.d
@sudo install -m 644 -o root -g wheel com.google.santa.newsyslog.conf ${WORK_D}/private/etc/newsyslog.d
pack-Library-Extensions-santa-driver.kext: santa-driver.kext l_Library
@sudo mkdir -p ${WORK_D}/Library/Extensions
@sudo ${DITTO} --noqtn santa-driver.kext ${WORK_D}/Library/Extensions/santa-driver.kext
@sudo chown -R root:wheel ${WORK_D}/Library/Extensions/santa-driver.kext
@sudo chmod -R 755 ${WORK_D}/Library/Extensions/santa-driver.kext
clean: myclean
myclean:
@rm -rf *.dSYM
@rm -rf Santa.app
@rm -rf santa-driver.kext
@rm -f config.plist
@rm -f com.google.santa.asl.conf
@rm -f com.google.santa.newsyslog.conf
@rm -f com.google.santad.plist
@rm -f com.google.santagui.plist
@rm -f install.sh
@rm -f uninstall.sh

View File

@@ -0,0 +1,6 @@
#!/bin/bash
# Example NOTARIZATION_TOOL wrapper.
/usr/bin/xcrun altool --notarize-app "${2}" --primary-bundle-id "${4}" \
-u "${NOTARIZATION_USERNAME}" -p "${NOTARIZATION_PASSWORD}"

201
Conf/Package/package_and_sign.sh Executable file
View File

@@ -0,0 +1,201 @@
#!/bin/bash
# This script signs all of Santa's components, verifies the signatures,
# notarizes all of the components, staples them, packages them up, signs the
# package, notarizes the package, puts the package in a DMG and notarizes the
# DMG. It also outputs a single release tarball.
# All of the following environment variables are required.
# RELEASE_ROOT is a required environment variable that points to the root
# of an extracted release tarball produced with the :release and :release_driver
# rules in Santa's main BUILD file.
[[ -n "${RELEASE_ROOT}" ]] || die "RELEASE_ROOT unset"
# SIGNING_IDENTITY, SIGNING_TEAMID and SIGNING_KEYCHAIN are required environment
# variables specifying the identity and keychain to pass to the codesign tool
# and the team ID to use for verification.
[[ -n "${SIGNING_IDENTITY}" ]] || die "SIGNING_IDENTITY unset"
[[ -n "${SIGNING_TEAMID}" ]] || die "SIGNING_TEAMID unset"
[[ -n "${SIGNING_KEYCHAIN}" ]] || die "SIGNING_KEYCHAIN unset"
# INSTALLER_SIGNING_IDENTITY and INSTALLER_SIGNING_KEYCHAIN are required
# environment variables specifying the identity and keychain to use when signing
# the distribution package.
[[ -n "${INSTALLER_SIGNING_IDENTITY}" ]] || die "INSTALLER_SIGNING_IDENTITY unset"
[[ -n "${INSTALLER_SIGNING_KEYCHAIN}" ]] || die "INSTALLER_SIGNING_KEYCHAIN unset"
# NOTARIZATION_TOOL is a required environment variable pointing to a wrapper
# tool around the tool to use for notarization. The tool must take 2 flags:
# --file
# - pointing at a zip file containing the artifact to notarize
# --primary-bundle-id
# - specifying the CFBundleID of the artifact being notarized
[[ -n "${NOTARIZATION_TOOL}" ]] || die "NOTARIZATION_TOOL unset"
# ARTIFACTS_DIR is a required environment variable pointing at a directory to
# place the output artifacts in.
[[ -n "${ARTIFACTS_DIR}" ]] || die "ARTIFACTS_DIR unset"
################################################################################
function die {
echo "${@}"
exit 2
}
readonly INPUT_APP="${RELEASE_ROOT}/binaries/Santa.app"
readonly INPUT_KEXT="${RELEASE_ROOT}/binaries/santa-driver.kext"
readonly INPUT_SYSX="${INPUT_APP}/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension"
readonly INPUT_SANTACTL="${INPUT_APP}/Contents/MacOS/santactl"
readonly INPUT_SANTABS="${INPUT_APP}/Contents/MacOS/santabundleservice"
readonly INPUT_SANTAMS="${INPUT_APP}/Contents/MacOS/santametricservice"
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleVersion)"
readonly SCRATCH=$(/usr/bin/mktemp -d "${TMPDIR}/santa-"XXXXXX)
readonly APP_PKG_ROOT="${SCRATCH}/app_pkg_root"
readonly KEXT_PKG_ROOT="${SCRATCH}/kext_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}" "${KEXT_PKG_ROOT}" "${APP_PKG_SCRIPTS}" "${ENTITLEMENTS}"
readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
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_SYSX}" "${INPUT_APP}" "${INPUT_KEXT}"; 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 \
--options library,kill,runtime "${ARTIFACT}"
done
# Notarize all the bundles
for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}" "${INPUT_KEXT}"; do
BN=$(/usr/bin/basename "${ARTIFACT}")
echo "zipping ${BN}"
/usr/bin/zip -9r "${SCRATCH}/${BN}.zip" "${ARTIFACT}"
echo "notarizing ${BN}"
PBID=$(/usr/bin/defaults read "${ARTIFACT}/Contents/Info.plist" CFBundleIdentifier)
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${BN}.zip" --primary-bundle-id "${PBID}"
done
# Staple the App and Kext.
for ARTIFACT in "${INPUT_APP}" "${INPUT_KEXT}"; do
BN=$(/usr/bin/basename "${ARTIFACT}")
echo "stapling ${BN}"
/usr/bin/xcrun stapler staple "${ARTIFACT}"
done
# Ensure _CodeSignature/CodeResources files have 0644 permissions so they can
# be verified without using sudo.
/usr/bin/find "${RELEASE_ROOT}/binaries" -type f -name CodeResources -exec chmod 0644 {} \;
/usr/bin/find "${RELEASE_ROOT}/binaries" -type d -exec chmod 0755 {} \;
/usr/bin/find "${RELEASE_ROOT}/conf" -type f -name "com.google.santa*" -exec chmod 0644 {} \;
echo "verifying signatures"
/usr/bin/codesign -vv -R="certificate leaf[subject.OU] = ${SIGNING_TEAMID}" \
"${RELEASE_ROOT}/binaries/"* || die "bad signature"
echo "creating fresh release tarball"
/bin/mkdir -p "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/binaries" "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/conf" "${RELEASE_ROOT}/${RELEASE_NAME}"
/bin/cp -r "${RELEASE_ROOT}/dsym" "${RELEASE_ROOT}/${RELEASE_NAME}"
/usr/bin/tar -C "${RELEASE_ROOT}" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
echo "creating app pkg"
/bin/mkdir -p "${APP_PKG_ROOT}/Applications" \
"${APP_PKG_ROOT}/Library/LaunchAgents" \
"${APP_PKG_ROOT}/Library/LaunchDaemons" \
"${APP_PKG_ROOT}/private/etc/asl" \
"${APP_PKG_ROOT}/private/etc/newsyslog.d"
/bin/cp -vXR "${RELEASE_ROOT}/binaries/Santa.app" "${APP_PKG_ROOT}/Applications/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santad.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.plist" "${APP_PKG_ROOT}/Library/LaunchAgents/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.bundleservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.metricservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.asl.conf" "${APP_PKG_ROOT}/private/etc/asl/"
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.newsyslog.conf" "${APP_PKG_ROOT}/private/etc/newsyslog.d/"
/bin/cp -vXL "${SCRIPT_PATH}/preinstall" "${APP_PKG_SCRIPTS}/"
/bin/cp -vXL "${SCRIPT_PATH}/postinstall" "${APP_PKG_SCRIPTS}/"
/bin/chmod +x "${APP_PKG_SCRIPTS}/"*
# Disable bundle relocation.
/usr/bin/pkgbuild --analyze --root "${APP_PKG_ROOT}" "${SCRATCH}/component.plist"
/usr/bin/plutil -replace BundleIsRelocatable -bool NO "${SCRATCH}/component.plist"
/usr/bin/plutil -replace BundleIsVersionChecked -bool NO "${SCRATCH}/component.plist"
/usr/bin/plutil -replace BundleOverwriteAction -string upgrade "${SCRATCH}/component.plist"
/usr/bin/plutil -replace ChildBundles -json "[]" "${SCRATCH}/component.plist"
# Build app package
/usr/bin/pkgbuild --identifier "com.google.santa" \
--version "$(echo "${RELEASE_NAME}" | cut -d - -f2)" \
--root "${APP_PKG_ROOT}" \
--component-plist "${SCRATCH}/component.plist" \
--scripts "${APP_PKG_SCRIPTS}" \
"${SCRATCH}/app.pkg"
echo "creating kext pkg"
/bin/mkdir -p "${KEXT_PKG_ROOT}/Library/Extensions"
/bin/cp -vXR "${RELEASE_ROOT}/binaries/santa-driver.kext" "${KEXT_PKG_ROOT}/Library/Extensions/"
/usr/bin/pkgbuild --analyze --root "${KEXT_PKG_ROOT}" "${SCRATCH}/component.plist"
/usr/bin/plutil -replace BundleIsRelocatable -bool NO "${SCRATCH}/component.plist"
/usr/bin/plutil -replace BundleIsVersionChecked -bool NO "${SCRATCH}/component.plist"
/usr/bin/plutil -replace BundleOverwriteAction -string upgrade "${SCRATCH}/component.plist"
/usr/bin/plutil -replace ChildBundles -json "[]" "${SCRATCH}/component.plist"
# Build kext package
/usr/bin/pkgbuild --identifier "com.google.santa-driver" \
--version "$(echo "${RELEASE_NAME}" | cut -d - -f2)" \
--root "${KEXT_PKG_ROOT}" \
--component-plist "${SCRATCH}/component.plist" \
"${SCRATCH}/kext.pkg"
# Build signed distribution package
echo "productbuild pkg"
/bin/mkdir -p "${SCRATCH}/${RELEASE_NAME}"
/usr/bin/productbuild \
--distribution "${SCRIPT_PATH}/Distribution.xml" \
--package-path "${SCRATCH}" \
--sign "${INSTALLER_SIGNING_IDENTITY}" --keychain "${INSTALLER_SIGNING_KEYCHAIN}" \
"${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg"
echo "verifying pkg signature"
/usr/sbin/pkgutil --check-signature "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" || die "bad pkg signature"
echo "notarizing pkg"
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" \
--primary-bundle-id "com.google.santa"
echo "stapling pkg"
/usr/bin/xcrun stapler staple "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" || die "failed to staple pkg"
echo "wrapping pkg in dmg"
/usr/bin/hdiutil create -fs HFS+ -format UDZO \
-volname "${RELEASE_NAME}" \
-ov -imagekey zlib-level=9 \
-srcfolder "${SCRATCH}/${RELEASE_NAME}/" "${DMG_PATH}" || die "failed to wrap pkg in dmg"
echo "notarizing dmg"
"${NOTARIZATION_TOOL}" --file "${DMG_PATH}" --primary-bundle-id "com.google.santa"
echo "stapling dmg"
/usr/bin/xcrun stapler staple "${DMG_PATH}" || die "failed to staple dmg"

View File

@@ -24,6 +24,9 @@ mkdir -p /usr/local/bin
# Load com.google.santa.bundleservice
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.bundleservice.plist
# Load com.google.santa.metricservice
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.metricservice.plist
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
[[ -z "${GUI_USER}" ]] && exit 0

View File

@@ -8,6 +8,7 @@
/bin/launchctl remove com.google.santad || true
/bin/launchctl remove com.google.santa.bundleservice || true
/bin/launchctl remove com.google.santa.metricservice || true
/bin/sleep 1

View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>com.google.santa.metricservice</string>
<key>ProgramArguments</key>
<array>
<string>/Applications/Santa.app/Contents/MacOS/santametricservice</string>
<string>--syslog</string>
</array>
<key>MachServices</key>
<dict>
<key>com.google.santa.metricservice</key>
<true/>
</dict>
<key>RunAtLoad</key>
<true/>
<key>KeepAlive</key>
<true/>
</dict>
</plist>

View File

@@ -24,6 +24,9 @@ fi
# Unload bundle service
/bin/launchctl remove com.google.santa.bundleservice >/dev/null 2>&1
# Unload metric service
/bin/launchctl remove com.google.santa.metricservice >/dev/null 2>&1
# Unload kext.
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
@@ -58,6 +61,7 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.metricservice.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
/bin/cp ${CONF}/com.google.santa.asl.conf /etc/asl/
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
@@ -71,6 +75,9 @@ GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
# Load com.google.santa.bundleservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
# Load com.google.santa.metricservice
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
# Load GUI agent if someone is logged in.
[[ -z "${GUI_USER}" ]] && exit 0

View File

@@ -24,6 +24,7 @@ user=$(/usr/bin/stat -f '%u' /dev/console)
/bin/rm -f /Library/LaunchAgents/com.google.santa.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
/bin/rm -f /Library/LaunchDaemons/com.google.santa.metricservice.plist
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
/bin/rm -f /private/etc/newsyslog.d/com.google.santa.newsyslog.conf
/bin/rm -f /usr/local/bin/santactl # just a symlink

View File

@@ -46,7 +46,7 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
SNTRule *newRule = [[SNTRule alloc] init];
newRule.state = (SNTRuleState)input_data.state;
newRule.type = (SNTRuleType)input_data.type;
newRule.shasum = @(input_data.hash);
newRule.identifier = @(input_data.hash);
newRule.customMsg = @"";
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
@@ -62,9 +62,9 @@ extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size
reply:^(NSError *error) {
if (!error) {
if (newRule.state == SNTRuleStateRemove) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
printf("Removed rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
printf("Added rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
}
}
}];

View File

@@ -4,22 +4,20 @@
<img src="./Source/santa/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
</p>
Santa is a binary authorization system for macOS. It consists of a kernel
extension (or a system extension on macOS 10.15+) that monitors for executions,
a userland daemon that makes execution decisions based on the contents of a
SQLite database, a GUI agent that notifies the user in case of a block decision
Santa is a binary authorization system for macOS. It consists of a system or
kernel extension (depending on the macOS version) that monitors for executions,
a daemon that makes execution decisions based on the contents of a local
database, a GUI agent that notifies the user in case of a block decision
and a command-line utility for managing the system and synchronizing the
database with a server.
It is named Santa because it keeps track of binaries that are naughty or nice.
Santa is a project of Google's Macintosh Operations Team.
# Docs
The Santa docs are stored in the
[Docs](https://github.com/google/santa/blob/master/docs) directory. The docs are
published at http://santa.dev.
[Docs](https://github.com/google/santa/blob/main/docs) directory and published
at http://santa.dev.
The docs include deployment options, details on how parts of Santa work and
instructions for developing Santa itself.
@@ -31,14 +29,14 @@ the [santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
great place.
If you believe you have a bug, feel free to report [an
issue](https://github.com/google/santa/isues) and we'll respond as soon as we
issue](https://github.com/google/santa/issues) and we'll respond as soon as we
can.
If you believe you've found a vulnerability, please read the
[security policy](https://github.com/google/santa/security/policy) for
disclosure reporting.
# Admin-Related Features
# Features
* Multiple modes: In the default MONITOR mode, all binaries except those marked
as blocked will be allowed to run, whilst being logged and recorded in
@@ -74,6 +72,14 @@ disclosure reporting.
common apps. Likewise, you cannot block Santa itself, and Santa uses a
distinct separate cert than other Google apps.
* Userland components validate each other: each of the userland components (the
daemon, the GUI agent and the command-line utility) communicate with each
other using XPC and check that their signing certificates are identical
before any communication is accepted.
* Caching: allowed binaries are cached so the processing required to make a
request is only done if the binary isn't already cached.
# Intentions and Expectations
No single system or process will stop *all* attacks, or provide 100% security.
@@ -89,36 +95,11 @@ protect hosts in whatever other ways you see fit.
# Security and Performance-Related Features
* In-kernel caching: allowed binaries are cached in the kernel so the
processing required to make a request is only done if the binary isn't
already cached.
* Userland components validate each other: each of the userland components (the
daemon, the GUI agent and the command-line utility) communicate with each
other using XPC and check that their signing certificates are identical
before any communication is accepted.
* Kext uses only KPIs: the kernel extension only uses provided kernel
programming interfaces to do its job. This means that the kext code should
continue to work across OS versions.
# Known Issues
* Santa only blocks execution (execve and variants), it doesn't protect against
dynamic libraries loaded with dlopen, libraries on disk that have been
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`. As of version
0.9.1 we *do* address [__PAGEZERO missing issues](b87482e) that were
exploited in some versions of macOS. We are working on also protecting
against similar avenues of attack.
* Kext communication security: the kext will only accept a connection from a
single client at a time and said client must be running as root. We haven't
yet found a good way to ensure the kext only accepts connections from a valid
client.
* Database protection: the SQLite database is installed with permissions so
that only the root user can read/write it. We're considering approaches to
secure this further.
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`.
* Scripts: Santa is currently written to ignore any execution that isn't a
binary. This is because after weighing the administration cost vs the
@@ -133,19 +114,17 @@ protect hosts in whatever other ways you see fit.
management server, which uploads events that have occurred on the machine and
downloads new rules. There are several open-source servers you can sync with:
* [Upvote](https://github.com/google/upvote) - An AppEngine-based server
that implements social voting to make managing a large fleet easier.
* [Moroz](https://github.com/groob/moroz) - A simple golang server that
serves hardcoded rules from simple configuration files.
* [Rudolph](https://github.com/airbnb/rudolph) - An AWS-based serverless sync service
primarily built on API GW, DynamoDB, and Lambda components to reduce operational burden.
Rudolph is designed to be fast, easy-to-use, and cost-efficient.
* [Zentral](https://github.com/zentralopensource/zentral/wiki) - A
centralized service that pulls data from multiple sources and deploy
configurations to multiple services.
* [Zercurity](https://github.com/zercurity/zercurity) - A dockerized service
for managing and monitoring applications across a large fleet utilizing
Santa + Osquery.
* [Rudolph](https://github.com/airbnb/rudolph) - An AWS-based serverless sync service
primarily built on API GW, DynamoDB, and Lambda components to reduce operational burden.
Rudolph is designed to be fast, easy-to-use, and cost-efficient.
* Alternatively, `santactl` can configure rules locally (without a sync
server).
@@ -158,31 +137,9 @@ instead.
<p align="center"> <img src="https://thumbs.gfycat.com/MadFatalAmphiuma-small.gif" alt="Santa Block Video" /> </p>
# Kext Signing
Kernel extensions on macOS 10.9 and later must be signed using an Apple-provided
Developer ID certificate with a kernel extension flag. Without it, the only way
to load an extension is to enable kext-dev-mode or disable SIP, depending on
the OS version.
There are two possible solutions for this, for distribution purposes:
1) Use a [pre-built, pre-signed
version](https://github.com/google/santa/releases) of the kext that we supply.
Each time changes are made to the kext code we will update the pre-built
version that you can make use of. This doesn't prevent you from making changes
to the non-kext parts of Santa and distributing those. If you make changes to
the kext and make a pull request, we can merge them in and distribute a new
version of the pre-signed kext.
2) Apply for your own [kext signing
certificate](https://developer.apple.com/contact/kext/). Apple will only grant
this for broad distribution within an organization, they won't issue them just
for testing purposes.
# Contributing
Patches to this project are very much welcome. Please see the
[CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
file.
[CONTRIBUTING](https://santa.dev/development/contributing) doc.
# Disclaimer
This is **not** an official Google product.

View File

@@ -87,6 +87,10 @@ objc_library(
cc_library(
name = "SNTKernelCommon",
hdrs = ["SNTKernelCommon.h"],
defines = [
"TARGET_OS_OSX",
"TARGET_OS_MAC",
],
)
cc_library(
@@ -96,7 +100,11 @@ cc_library(
"-mkernel",
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
],
defines = ["KERNEL"],
defines = [
"KERNEL",
"TARGET_OS_OSX",
"TARGET_OS_MAC",
],
)
objc_library(
@@ -123,7 +131,11 @@ cc_library(
"-mkernel",
"-I__BAZEL_XCODE_SDKROOT__/System/Library/Frameworks/Kernel.framework/Headers",
],
defines = ["KERNEL"],
defines = [
"KERNEL",
"TARGET_OS_OSX",
"TARGET_OS_MAC",
],
deps = [":SNTLoggingKernel"],
)
@@ -201,6 +213,7 @@ objc_library(
name = "SNTMetricSet",
srcs = ["SNTMetricSet.m"],
hdrs = ["SNTMetricSet.h"],
deps = [":SNTCommonEnums"],
)
objc_library(
@@ -242,9 +255,9 @@ santa_unit_test(
name = "SNTFileInfoTest",
srcs = ["SNTFileInfoTest.m"],
resources = [
"testdata/32bitplist",
"testdata/bad_pagezero",
"testdata/missing_pagezero",
"testdata/32bitplist",
],
structured_resources = glob([
"testdata/BundleExample.app/**",

View File

@@ -32,6 +32,7 @@
@property NSString *certSHA256;
@property NSString *certCommonName;
@property NSArray<MOLCertificate *> *certChain;
@property NSString *teamID;
@property NSString *quarantineURL;

View File

@@ -24,6 +24,7 @@ typedef NS_ENUM(NSInteger, SNTRuleType) {
SNTRuleTypeBinary = 1,
SNTRuleTypeCertificate = 2,
SNTRuleTypeTeamID = 3,
};
typedef NS_ENUM(NSInteger, SNTRuleState) {
@@ -55,6 +56,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateBlockBinary = 1 << 17,
SNTEventStateBlockCertificate = 1 << 18,
SNTEventStateBlockScope = 1 << 19,
SNTEventStateBlockTeamID = 1 << 20,
// Bits 24-31 store allow decision types
SNTEventStateAllowUnknown = 1 << 24,
@@ -64,6 +66,7 @@ typedef NS_ENUM(NSInteger, SNTEventState) {
SNTEventStateAllowCompiler = 1 << 28,
SNTEventStateAllowTransitive = 1 << 29,
SNTEventStateAllowPendingTransitive = 1 << 30,
SNTEventStateAllowTeamID = 1 << 31,
// Block and Allow masks
SNTEventStateBlock = 0xFF << 16,
@@ -94,7 +97,7 @@ typedef NS_ENUM(NSInteger, SNTEventLogType) {
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
SNTMetricFormatTypeUnknown,
SNTMetricFormatTypeRawJSON,
SNTMetricFormatTypeJSON,
SNTMetricFormatTypeMonarchJSON,
};
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";

View File

@@ -185,6 +185,12 @@
#pragma mark - GUI Settings
///
/// The text to display when opening Santa.app.
/// If unset, the default text will be displayed.
///
@property(readonly, nonatomic) NSString *aboutText;
///
/// The URL to open when the user clicks "More Info..." when opening Santa.app.
/// If unset, the button will not be displayed.
@@ -386,6 +392,15 @@
///
@property(readonly, nonatomic) NSURL *metricURL;
///
/// Extra Metric Labels to add to the metrics payloads.
///
@property(readonly, nonatomic) NSDictionary *extraMetricLabels;
///
/// Duration in seconds of how often the metrics should be exported.
///
@property(readonly, nonatomic) NSUInteger metricExportInterval;
///
/// Retrieve an initialized singleton configurator object using the default file path.

View File

@@ -59,6 +59,7 @@ static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
static NSString *const kAboutText = @"AboutText";
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
static NSString *const kEventDetailURLKey = @"EventDetailURL";
static NSString *const kEventDetailTextKey = @"EventDetailText";
@@ -104,6 +105,8 @@ static NSString *const kBlockedPathRegexKeyDeprecated = @"BlacklistRegex";
// TODO(markowsky): move these to sync server only.
static NSString *const kMetricFormat = @"MetricFormat";
static NSString *const kMetricURL = @"MetricURL";
static NSString *const kMetricExportInterval = @"MetricExportInterval";
static NSString *const kMetricExtraLabels = @"MetricExtraLabels";
// The keys managed by a sync server.
static NSString *const kFullSyncLastSuccess = @"FullSyncLastSuccess";
@@ -119,6 +122,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
Class string = [NSString class];
Class data = [NSData class];
Class array = [NSArray class];
Class dictionary = [NSDictionary class];
_syncServerKeyTypes = @{
kClientModeKey : number,
kEnableTransitiveRulesKey : number,
@@ -143,6 +147,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kBlockedPathRegexKeyDeprecated : re,
kEnablePageZeroProtectionKey : number,
kEnableBadSignatureProtectionKey : number,
kAboutText : string,
kMoreInfoURLKey : string,
kEventDetailURLKey : string,
kEventDetailTextKey : string,
@@ -177,6 +182,8 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
kFCMAPIKey : string,
kMetricFormat : string,
kMetricURL : string,
kMetricExportInterval : number,
kMetricExtraLabels : dictionary,
};
_defaults = [NSUserDefaults standardUserDefaults];
[_defaults addSuiteNamed:@"com.google.santa"];
@@ -256,6 +263,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingAboutText {
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingMoreInfoURL {
return [self configStateSet];
}
@@ -384,6 +395,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [self configStateSet];
}
+ (NSSet *)keyPathsForValuesAffectingEnableBadSignatureProtection {
return [self configStateSet];
}
#pragma mark Public Interface
- (SNTClientMode)clientMode {
@@ -488,6 +503,10 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return number ? [number boolValue] : NO;
}
- (NSString *)aboutText {
return self.configState[kAboutText];
}
- (NSURL *)moreInfoURL {
return [NSURL URLWithString:self.configState[kMoreInfoURLKey]];
}
@@ -621,7 +640,7 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
- (BOOL)enableSysxCache {
NSNumber *number = self.configState[kEnableSysxCache];
return number ? [number boolValue] : NO;
return number ? [number boolValue] : YES;
}
- (BOOL)enableForkAndExitLogging {
@@ -671,12 +690,13 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
- (SNTMetricFormatType)metricFormat {
NSString *normalized = [self.configState[kMetricFormat] lowercaseString];
normalized = [normalized stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
if ([normalized isEqualToString:@"rawjson"]) {
return SNTMetricFormatTypeRawJSON;
} else if ([normalized isEqualToString:@"json"]) {
return SNTMetricFormatTypeJSON;
} else if ([normalized isEqualToString:@"monarchjson"]) {
return SNTMetricFormatTypeMonarchJSON;
} else {
return SNTMetricFormatTypeUnknown;
}
@@ -686,6 +706,20 @@ static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
return [NSURL URLWithString:self.configState[kMetricURL]];
}
// Returns a default value of 30 (for 30 seconds).
- (NSUInteger)metricExportInterval {
NSNumber *configuredInterval = self.configState[kMetricExportInterval];
if (configuredInterval == nil) {
return 30;
}
return [configuredInterval unsignedIntegerValue];
}
- (NSDictionary *)extraMetricLabels {
return self.configState[kMetricExtraLabels];
}
#pragma mark Private
///

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#import <Foundation/Foundation.h>
#import "SNTCommonEnums.h"
/**
* Provides an abstraction for various metric systems that will be exported to
@@ -48,6 +49,8 @@ typedef NS_ENUM(NSInteger, SNTMetricType) {
SNTMetricTypeCounter = 9,
};
NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType);
@interface SNTMetric : NSObject
- (NSDictionary *)export;
@end
@@ -100,8 +103,21 @@ typedef NS_ENUM(NSInteger, SNTMetricType) {
fieldNames:(NSArray<NSString *> *)fieldNames
helpText:(NSString *)text;
/**
* Returns a shared global instance with default root labels and metrics registered.
*/
+ (instancetype)sharedInstance;
/**
* Add a root label to the MetricSet.
*/
- (void)addRootLabel:(NSString *)label value:(NSString *)value;
/**
* Remove a root label from the MetricSet.
*/
- (void)removeRootLabel:(NSString *)labelName;
/**
* Returns a int64 gauge metric with the given Streamz name and help text,
* registered with this MetricSet.
@@ -170,4 +186,12 @@ typedef NS_ENUM(NSInteger, SNTMetricType) {
- (NSDictionary *)export;
@end
// Returns a human readble string from an SNTMetricFormat type
NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format);
/** Normalizes dates in an exported dictionary to be ISO8601 timestamp strings in
* UTC time.
*/
NSDictionary *SNTMetricConvertDatesToISO8601Strings(NSDictionary *metrics);
NS_ASSUME_NONNULL_END

View File

@@ -13,6 +13,24 @@
/// limitations under the License.
#import "SNTMetricSet.h"
#import "SNTCommonEnums.h"
NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
NSString *typeStr;
switch (metricType) {
case SNTMetricTypeConstantBool: typeStr = @"SNTMetricTypeConstantBool"; break;
case SNTMetricTypeConstantString: typeStr = @"SNTMetricTypeConstantString"; break;
case SNTMetricTypeConstantInt64: typeStr = @"SNTMetricTypeConstantInt64"; break;
case SNTMetricTypeConstantDouble: typeStr = @"SNTMetricTypeConstantDouble"; break;
case SNTMetricTypeGaugeBool: typeStr = @"SNTMetricTypeGaugeBool"; break;
case SNTMetricTypeGaugeString: typeStr = @"SNTMetricTypeGaugeString"; break;
case SNTMetricTypeGaugeInt64: typeStr = @"SNTMetricTypeGaugeInt64"; break;
case SNTMetricTypeGaugeDouble: typeStr = @"SNTMetricTypeGaugeDouble"; break;
case SNTMetricTypeCounter: typeStr = @"SNTMetricTypeCounter"; break;
default: typeStr = @"SNTMetricTypeUnknown"; break;
}
return [NSString stringWithFormat:@"%@ %ld", typeStr, metricType];
}
/**
* SNTMetricValue encapsulates the value of a metric along with the creation
@@ -257,6 +275,7 @@
NSMutableDictionary *metricDict = [NSMutableDictionary dictionaryWithCapacity:_fieldNames.count];
metricDict[@"type"] = [NSNumber numberWithInt:(int)_type];
metricDict[@"fields"] = [[NSMutableDictionary alloc] init];
metricDict[@"description"] = [_help copy];
if (_fieldNames.count == 0) {
metricDict[@"fields"][@""] = @[ [self encodeMetricValueForFieldValues:@[]] ];
@@ -431,6 +450,17 @@
NSMutableArray<void (^)(void)> *_callbacks;
}
+ (instancetype)sharedInstance {
static SNTMetricSet *sharedMetrics;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMetrics = [[SNTMetricSet alloc] init];
});
return sharedMetrics;
}
- (instancetype)init {
self = [super init];
if (self) {
@@ -461,6 +491,12 @@
}
}
- (void)removeRootLabel:(NSString *)label {
@synchronized(self) {
[_rootLabels removeObjectForKey:label];
}
}
- (SNTMetric *)registerMetric:(nonnull SNTMetric *)metric {
@synchronized(self) {
SNTMetric *oldMetric = _metrics[[metric name]];
@@ -581,19 +617,61 @@
}
@synchronized(self) {
// Walk root labels
NSMutableDictionary *exportDict = [[NSMutableDictionary alloc] init];
exportDict[@"root_labels"] = [NSDictionary dictionaryWithDictionary:_rootLabels];
exportDict[@"root_labels"] = [_rootLabels copy];
exportDict[@"metrics"] = [[NSMutableDictionary alloc] init];
// sort the metrics so we always get the same output.
for (id metricName in _metrics) {
SNTMetric *metric = [_metrics objectForKey:metricName];
exportDict[@"metrics"][metricName] = [metric export];
// TODO(markowsky) Sort the metrics so we always get the same output.
for (NSString *metricName in _metrics) {
exportDict[@"metrics"][metricName] = [_metrics[metricName] export];
}
exported = [NSDictionary dictionaryWithDictionary:exportDict];
}
return exported;
}
// Returns a human readble string from an SNTMetricFormat type
NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format) {
switch (format) {
case SNTMetricFormatTypeRawJSON: return @"rawjson";
case SNTMetricFormatTypeMonarchJSON: return @"monarchjson";
default: return @"Unknown Metric 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;
}
for (NSString *metricName in mutableMetrics[@"metrics"]) {
NSMutableDictionary *metric = mutableMetrics[@"metrics"][metricName];
for (NSString *field in metric[@"fields"]) {
NSMutableArray<NSMutableDictionary *> *values = metric[@"fields"][field];
[values enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
values[index][@"created"] = [formatter stringFromDate:values[index][@"created"]];
values[index][@"last_updated"] = [formatter stringFromDate:values[index][@"last_updated"]];
}];
}
}
return mutableMetrics;
}
@end

View File

@@ -20,6 +20,9 @@
@interface SNTMetricSetTest : XCTestCase
@end
@interface SNTMetricSetHelperFunctionsTest : XCTestCase
@end
// Stub out NSDate's date method
@implementation NSDate (custom)
@@ -60,6 +63,7 @@
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
@"description" : @"Count of exec events broken out by rule type.",
@"fields" : @{
@"rule_type" : @[ @{
@"value" : @"certificate",
@@ -96,6 +100,7 @@
[b set:true forFieldValues:@[]];
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeBool],
@"description" : @"Is the daemon connected.",
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@@ -150,6 +155,7 @@
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"description" : @"Count of rules broken out by rule type.",
@"fields" : @{
@"rule_type" : @[ @{
@"value" : @"binary",
@@ -201,6 +207,7 @@
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeDouble],
@"description" : @"CPU time consumed by this process.",
@"fields" : @{
@"mode" : @[
@{
@@ -244,6 +251,7 @@
NSDictionary *expected = @{
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeString],
@"description" : @"String description of the mode.",
@"fields" : @{
@"" : @[ @{
@"value" : @"",
@@ -265,8 +273,18 @@
NSDictionary *expected = @{@"root_labels" : @{@"hostname" : @"localhost"}, @"metrics" : @{}};
NSDictionary *output = [metricSet export];
XCTAssertEqualObjects(output, expected);
XCTAssertEqualObjects(expected, [metricSet export]);
// ensure that adding a rootLabel with the same name overwrites.
expected = @{@"root_labels" : @{@"hostname" : @"localhost2"}, @"metrics" : @{}};
[metricSet addRootLabel:@"hostname" value:@"localhost2"];
XCTAssertEqualObjects(expected, [metricSet export],
@"failed to overwrite rootLabel with second call to addRootLabel");
// ensure that removing a rootLabelWorks
expected = @{@"root_labels" : @{}, @"metrics" : @{}};
[metricSet removeRootLabel:@"hostname"];
}
- (void)testDoubleRegisteringIncompatibleMetricsFails {
@@ -313,6 +331,7 @@
NSDictionary *expected = @{
@"/tautology" : @{
@"description" : @"The first rule of tautology club is the first rule",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
@"fields" : @{
@"" : @[ @{
@@ -337,6 +356,7 @@
NSDictionary *expected = @{
@"/build/label" : @{
@"description" : @"Build label for the binary",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
@"fields" : @{
@"" : @[ @{
@@ -360,6 +380,7 @@
NSDictionary *expected = @{
@"/deep/thought/answer" : @{
@"description" : @"Life, the universe, and everything",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
@"fields" : @{
@"" : @[ @{
@@ -386,10 +407,10 @@
[metricSet addConstantBooleanWithName:@"/santa/using_endpoint_security_framework"
helpText:@"Is santad using the endpoint security framework."
value:TRUE];
[metricSet addConstantIntegerWithName:@"/proc/birth_timestamp"
helpText:@"Start time of this LogDumper instance, in microseconds "
@"since epoch"
value:(long long)(0x12345668910)];
[metricSet
addConstantIntegerWithName:@"/proc/birth_timestamp"
helpText:@"Start time of this santad instance, in microseconds since epoch"
value:(long long)(0x12345668910)];
// Add Metrics
SNTMetricCounter *c = [metricSet counterWithName:@"/santa/events"
fieldNames:@[ @"rule_type" ]
@@ -414,7 +435,7 @@
SNTMetricInt64Gauge *residentMemoryGauge =
[metricSet int64GaugeWithName:@"/proc/memory/resident_size"
fieldNames:@[]
helpText:@"The resident set siz of this process."];
helpText:@"The resident set size of this process."];
[metricSet registerCallback:^(void) {
[virtualMemoryGauge set:987654321 forFieldValues:@[]];
@@ -425,6 +446,7 @@
@"root_labels" : @{@"hostname" : @"testHost", @"username" : @"testUser"},
@"metrics" : @{
@"/build/label" : @{
@"description" : @"Software version running.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
@"fields" : @{
@"" : @[ @{
@@ -436,6 +458,7 @@
}
},
@"/santa/events" : @{
@"description" : @"Count of events on the host",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
@"fields" : @{
@"rule_type" : @[
@@ -455,6 +478,7 @@
},
},
@"/santa/rules" : @{
@"description" : @"Number of rules.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"rule_type" : @[
@@ -474,6 +498,7 @@
},
},
@"/santa/using_endpoint_security_framework" : @{
@"description" : @"Is santad using the endpoint security framework.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
@"fields" : @{
@"" : @[ @{
@@ -485,6 +510,7 @@
}
},
@"/proc/birth_timestamp" : @{
@"description" : @"Start time of this santad instance, in microseconds since epoch",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
@"fields" : @{
@"" : @[ @{
@@ -496,6 +522,7 @@
},
},
@"/proc/memory/virtual_size" : @{
@"description" : @"The virtual memory size of this process.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"" : @[ @{
@@ -507,6 +534,7 @@
}
},
@"/proc/memory/resident_size" : @{
@"description" : @"The resident set size of this process.",
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
@"fields" : @{
@"" : @[ @{
@@ -524,3 +552,56 @@
}
@end
@implementation SNTMetricSetHelperFunctionsTest
- (void)testMakeMetricString {
NSArray<NSDictionary *> *tests = @[
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeUnknown],
@"expected" : @"SNTMetricTypeUnknown 0"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantBool],
@"expected" : @"SNTMetricTypeConstantBool 1"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantString],
@"expected" : @"SNTMetricTypeConstantString 2"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantInt64],
@"expected" : @"SNTMetricTypeConstantInt64 3"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantDouble],
@"expected" : @"SNTMetricTypeConstantDouble 4"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeBool],
@"expected" : @"SNTMetricTypeGaugeBool 5"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeString],
@"expected" : @"SNTMetricTypeGaugeString 6"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeInt64],
@"expected" : @"SNTMetricTypeGaugeInt64 7"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeDouble],
@"expected" : @"SNTMetricTypeGaugeDouble 8"
},
@{
@"input" : [NSNumber numberWithInt:SNTMetricTypeCounter],
@"expected" : @"SNTMetricTypeCounter 9"
}
];
for (NSDictionary *test in tests) {
NSString *output = SNTMetricMakeStringFromMetricType([test[@"input"] integerValue]);
XCTAssertEqualObjects(test[@"expected"], output, @"expected %@ got %@", test[@"expected"],
output);
}
}
@end

View File

@@ -45,26 +45,29 @@
[UUIDs addObject:[NSUUID UUID].UUIDString];
}
__block BOOL stop = NO;
// Create a bunch of background noise.
dispatch_async(dispatch_get_global_queue(0, 0), ^{
dispatch_apply(UINT64_MAX, dispatch_get_global_queue(0, 0), ^(size_t i) {
t->HasPrefix([UUIDs[i % count] UTF8String]);
});
for (uint64_t i = 0; i < UINT64_MAX; ++i) {
dispatch_async(dispatch_get_global_queue(0, 0), ^{
t->HasPrefix([UUIDs[i % count] UTF8String]);
});
if (stop) return;
}
});
// Fill up the tree.
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
if (t->AddPrefix([UUIDs[i] UTF8String]) != kIOReturnSuccess) {
XCTFail();
}
XCTAssertEqual(t->AddPrefix([UUIDs[i] UTF8String]), kIOReturnSuccess);
});
// Make sure every leaf byte is found.
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
if (!t->HasPrefix([UUIDs[i] UTF8String])) {
XCTFail();
}
XCTAssertTrue(t->HasPrefix([UUIDs[i] UTF8String]));
});
stop = YES;
}
@end

View File

@@ -24,7 +24,7 @@
///
/// The hash of the object this rule is for
///
@property(copy) NSString *shasum;
@property(copy) NSString *identifier;
///
/// The state of this rule
@@ -50,7 +50,7 @@
///
/// Designated initializer.
///
- (instancetype)initWithShasum:(NSString *)shasum
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
@@ -59,7 +59,7 @@
///
/// Initialize with a default timestamp: current time if rule state is transitive, 0 otherwise.
///
- (instancetype)initWithShasum:(NSString *)shasum
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg;

View File

@@ -20,14 +20,14 @@
@implementation SNTRule
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp {
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg
timestamp:(NSUInteger)timestamp {
self = [super init];
if (self) {
_shasum = shasum;
_identifier = identifier;
_state = state;
_type = type;
_customMsg = customMsg;
@@ -36,11 +36,11 @@
return self;
}
- (instancetype)initWithShasum:(NSString *)shasum
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg {
self = [self initWithShasum:shasum state:state type:type customMsg:customMsg timestamp:0];
- (instancetype)initWithIdentifier:(NSString *)identifier
state:(SNTRuleState)state
type:(SNTRuleType)type
customMsg:(NSString *)customMsg {
self = [self initWithIdentifier:identifier state:state type:type customMsg:customMsg timestamp:0];
// Initialize timestamp to current time if rule is transitive.
if (self && state == SNTRuleStateAllowTransitive) {
[self resetTimestamp];
@@ -61,7 +61,7 @@
}
- (void)encodeWithCoder:(NSCoder *)coder {
ENCODE(self.shasum, @"shasum");
ENCODE(self.identifier, @"identifier");
ENCODE(@(self.state), @"state");
ENCODE(@(self.type), @"type");
ENCODE(self.customMsg, @"custommsg");
@@ -71,7 +71,7 @@
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super init];
if (self) {
_shasum = DECODE(NSString, @"shasum");
_identifier = DECODE(NSString, @"identifier");
_state = [DECODE(NSNumber, @"state") intValue];
_type = [DECODE(NSNumber, @"type") intValue];
_customMsg = DECODE(NSString, @"custommsg");
@@ -88,22 +88,22 @@
if (other == self) return YES;
if (![other isKindOfClass:[SNTRule class]]) return NO;
SNTRule *o = other;
return ([self.shasum isEqual:o.shasum] && self.state == o.state && self.type == o.type);
return ([self.identifier isEqual:o.identifier] && self.state == o.state && self.type == o.type);
}
- (NSUInteger)hash {
NSUInteger prime = 31;
NSUInteger result = 1;
result = prime * result + [self.shasum hash];
result = prime * result + [self.identifier hash];
result = prime * result + self.state;
result = prime * result + self.type;
return result;
}
- (NSString *)description {
return
[NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld, Timestamp: %lu",
self.shasum, self.state, self.type, (unsigned long)self.timestamp];
return [NSString
stringWithFormat:@"SNTRule: Identifier: %@, State: %ld, Type: %ld, Timestamp: %lu",
self.identifier, self.state, self.type, (unsigned long)self.timestamp];
}
#pragma mark Last-access Timestamp

View File

@@ -34,6 +34,7 @@
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTRule *))reply;
///

View File

@@ -14,13 +14,13 @@
#import "Source/common/SNTXPCMetricServiceInterface.h"
@implementation SNTXPCMetricServiceInterface
+ (NSXPCInterface *)metricServiceInterface {
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTMetricServiceXPC)];
[r setClasses:[NSSet setWithObjects:[NSDictionary class], nil]
[r setClasses:[NSSet setWithObjects:[NSDictionary class], [NSArray class], [NSNumber class],
[NSString class], [NSDate class], nil]
forSelector:@selector(exportForMonitoring:)
argumentIndex:0
ofReply:NO];

View File

@@ -39,7 +39,7 @@
/// Database ops
///
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
int64_t transitive))reply;
int64_t transitive, int64_t teamID))reply;
- (void)databaseEventCount:(void (^)(int64_t count))reply;
///
@@ -57,6 +57,7 @@
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTEventState))reply;
///
@@ -71,6 +72,11 @@
- (void)enableBundles:(void (^)(BOOL))reply;
- (void)enableTransitiveRules:(void (^)(BOOL))reply;
///
/// Metrics ops
///
- (void)metrics:(void (^)(NSDictionary *))reply;
///
/// GUI Ops
///

View File

@@ -48,19 +48,20 @@ macos_application(
additional_contents = {
"//Source/santactl": "MacOS",
"//Source/santabundleservice": "MacOS",
"//Source/santametricservice": "MacOS",
"//Source/santad:com.google.santa.daemon": "Library/SystemExtensions",
},
app_icons = glob(["Resources/Images.xcassets/**"]),
bundle_id = "com.google.santa",
bundle_name = "Santa",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
entitlements = "Santa.app.entitlements",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
entitlements = "Santa.app.entitlements",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",

View File

@@ -7,6 +7,7 @@
<objects>
<customObject id="-2" userLabel="File's Owner" customClass="SNTAboutWindowController">
<connections>
<outlet property="aboutTextField" destination="uh6-q0-RzL" id="oGn-hV-wwc"/>
<outlet property="moreInfoButton" destination="SRu-Kf-vu5" id="Vj2-9Q-05d"/>
<outlet property="window" destination="F0z-JX-Cv5" id="gIp-Ho-8D9"/>
</connections>

View File

@@ -16,6 +16,7 @@
@interface SNTAboutWindowController : NSWindowController
@property IBOutlet NSTextField *aboutTextField;
@property IBOutlet NSButton *moreInfoButton;
- (IBAction)openMoreInfoURL:(id)sender;

View File

@@ -24,7 +24,12 @@
- (void)loadWindow {
[super loadWindow];
if (![[SNTConfigurator configurator] moreInfoURL]) {
SNTConfigurator *config = [SNTConfigurator configurator];
NSString *aboutText = [config aboutText];
if (aboutText) {
[self.aboutTextField setStringValue:aboutText];
}
if (![config moreInfoURL]) {
[self.moreInfoButton removeFromSuperview];
}
}

View File

@@ -2,7 +2,15 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>EQHXZ8M8AV.com.google.santa</string>
<key>com.apple.developer.system-extension.install</key>
<true/>
<key>com.apple.developer.team-identifier</key>
<string>EQHXZ8M8AV</string>
<key>keychain-access-groups</key>
<array>
<string>EQHXZ8M8AV.com.google.santa</string>
</array>
</dict>
</plist>

View File

@@ -32,10 +32,10 @@ cc_library(
"SANTA_VERSION=" + SANTA_VERSION,
],
deps = [
"//Source/common:SantaCache",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLoggingKernel",
"//Source/common:SNTPrefixTreeKernel",
"//Source/common:SantaCache",
],
alwayslink = 1,
)

View File

@@ -3,6 +3,8 @@ load("//:helper.bzl", "santa_unit_test")
licenses(["notice"])
package(default_visibility = ["//:santa_package_group"])
objc_library(
name = "santactl_lib",
srcs = [
@@ -15,6 +17,8 @@ objc_library(
"Commands/SNTCommandRule.m",
"Commands/SNTCommandStatus.m",
"Commands/SNTCommandVersion.m",
"Commands/SNTCommandMetrics.h",
"Commands/SNTCommandMetrics.m",
"Commands/sync/NSData+Zlib.h",
"Commands/sync/NSData+Zlib.m",
"Commands/sync/SNTCommandSync.m",
@@ -54,6 +58,7 @@ objc_library(
"//Source/common:SNTFileInfo",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTStrengthify",
@@ -82,15 +87,18 @@ objc_library(
macos_command_line_application(
name = "santactl",
bundle_id = "com.google.santa.ctl",
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
minimum_os_version = "10.9",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
}),
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":santactl_lib"],
)
@@ -138,11 +146,16 @@ santa_unit_test(
"Commands/sync/SNTCommandSyncStage.m",
"Commands/sync/SNTCommandSyncState.h",
"Commands/sync/SNTCommandSyncState.m",
"Commands/sync/SNTCommandSyncTest.m",
"SNTCommand.h",
"SNTCommand.m",
"SNTCommandController.h",
"SNTCommandController.m",
],
resources = glob([
"Commands/sync/testdata/*.json",
"Commands/sync/testdata/*.plist",
]),
sdk_dylibs = ["libz"],
deps = [
":FCM_lib",
@@ -160,3 +173,25 @@ santa_unit_test(
"@OCMock",
],
)
santa_unit_test(
name = "SNTCommandMetricsTest",
srcs = ["Commands/SNTCommandMetricsTest.m"],
structured_resources = glob(["Commands/testdata/*"]),
visibility = ["//:santa_package_group"],
deps = [
":santactl_lib",
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
"@OCMock",
],
)
test_suite(
name = "unit_tests",
tests = [
":SNTCommandFileInfoTest",
":SNTCommandMetricsTest",
":SNTCommandSyncTest",
],
visibility = ["//:santa_package_group"],
)

View File

@@ -41,6 +41,7 @@ static NSString *const kCodeSigned = @"Code-signed";
static NSString *const kRule = @"Rule";
static NSString *const kSigningChain = @"Signing Chain";
static NSString *const kUniversalSigningChain = @"Universal Signing Chain";
static NSString *const kTeamID = @"Team ID";
// signing chain keys
static NSString *const kCommonName = @"Common Name";
@@ -109,6 +110,7 @@ typedef id (^SNTAttributeBlock)(SNTCommandFileInfo *, SNTFileInfo *);
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadURL;
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadTimestamp;
@property(readonly, copy, nonatomic) SNTAttributeBlock downloadAgent;
@property(readonly, copy, nonatomic) SNTAttributeBlock teamID;
@property(readonly, copy, nonatomic) SNTAttributeBlock type;
@property(readonly, copy, nonatomic) SNTAttributeBlock pageZero;
@property(readonly, copy, nonatomic) SNTAttributeBlock codeSigned;
@@ -183,7 +185,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
+ (NSArray<NSString *> *)fileInfoKeys {
return @[
kPath, kSHA256, kSHA1, kBundleName, kBundleVersion, kBundleVersionStr, kDownloadReferrerURL,
kDownloadURL, kDownloadTimestamp, kDownloadAgent, kType, kPageZero, kCodeSigned, kRule,
kDownloadURL, kDownloadTimestamp, kDownloadAgent, kTeamID, kType, kPageZero, kCodeSigned, kRule,
kSigningChain, kUniversalSigningChain
];
}
@@ -215,7 +217,8 @@ REGISTER_COMMAND_NAME(@"fileinfo")
kCodeSigned : self.codeSigned,
kRule : self.rule,
kSigningChain : self.signingChain,
kUniversalSigningChain : self.universalSigningChain
kUniversalSigningChain : self.universalSigningChain,
kTeamID : self.teamID,
};
_printQueue = dispatch_queue_create("com.google.santactl.print_queue", DISPATCH_QUEUE_SERIAL);
@@ -355,13 +358,15 @@ REGISTER_COMMAND_NAME(@"fileinfo")
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSError *err;
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:&err];
[[cmd.daemonConn remoteObjectProxy] decisionForFilePath:fileInfo.path
fileSHA256:fileInfo.SHA256
certificateSHA256:err ? nil : csc.leafCertificate.SHA256
reply:^(SNTEventState s) {
state = s;
dispatch_semaphore_signal(sema);
}];
[[cmd.daemonConn remoteObjectProxy]
decisionForFilePath:fileInfo.path
fileSHA256:fileInfo.SHA256
certificateSHA256:err ? nil : csc.leafCertificate.SHA256
teamID:[csc.signingInformation valueForKey:@"teamid"]
reply:^(SNTEventState s) {
state = s;
dispatch_semaphore_signal(sema);
}];
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
cmd.daemonUnavailable = YES;
return kCommunicationErrorMsg;
@@ -375,10 +380,13 @@ REGISTER_COMMAND_NAME(@"fileinfo")
case SNTEventStateBlockBinary: [output appendString:@" (Binary)"]; break;
case SNTEventStateAllowCertificate:
case SNTEventStateBlockCertificate: [output appendString:@" (Certificate)"]; break;
case SNTEventStateAllowTeamID:
case SNTEventStateBlockTeamID: [output appendString:@" (TeamID)"]; break;
case SNTEventStateAllowScope:
case SNTEventStateBlockScope: [output appendString:@" (Scope)"]; break;
case SNTEventStateAllowCompiler: [output appendString:@" (Compiler)"]; break;
case SNTEventStateAllowTransitive: [output appendString:@" (Transitive)"]; break;
default: output = @"None".mutableCopy; break;
}
if (cmd.prettyOutput) {
@@ -458,6 +466,13 @@ REGISTER_COMMAND_NAME(@"fileinfo")
};
}
- (SNTAttributeBlock)teamID {
return ^id(SNTCommandFileInfo *cmd, SNTFileInfo *fileInfo) {
MOLCodesignChecker *csc = [fileInfo codesignCheckerWithError:NULL];
return [csc.signingInformation valueForKey:@"teamid"];
};
}
#pragma mark -
// Entry point for the command.

View File

@@ -0,0 +1,22 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@interface SNTCommandMetrics : SNTCommand <SNTCommandProtocol>
- (void)prettyPrintMetrics:(NSDictionary *)metircs asJSON:(BOOL)exportJSON;
@end

View File

@@ -0,0 +1,145 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/SNTCommandMetrics.h"
#import "Source/santactl/SNTCommand.h"
#import "Source/santactl/SNTCommandController.h"
@implementation SNTCommandMetrics
REGISTER_COMMAND_NAME(@"metrics")
+ (BOOL)requiresRoot {
return NO;
}
+ (BOOL)requiresDaemonConn {
return YES;
}
+ (NSString *)shortHelpText {
return @"Show Santa metric information.";
}
+ (NSString *)longHelpText {
return (@"Provides metrics about Santa's operation while it's running.\n"
@" Use --json to output in JSON format");
}
- (void)prettyPrintRootLabels:(NSDictionary *)rootLabels {
for (NSString *label in rootLabels) {
const char *labelStr = [label cStringUsingEncoding:NSUTF8StringEncoding];
const char *valueStr = [rootLabels[label] cStringUsingEncoding:NSUTF8StringEncoding];
printf(" %-25s | %s\n", labelStr, valueStr);
}
}
- (void)prettyPrintMetricValues:(NSDictionary *)metrics {
for (NSString *metricName in metrics) {
NSDictionary *metric = metrics[metricName];
const char *metricNameStr = [metricName UTF8String];
const char *description = [metric[@"description"] UTF8String];
NSString *metricType = SNTMetricMakeStringFromMetricType([metric[@"type"] integerValue]);
const char *metricTypeStr = [metricType UTF8String];
printf(" %-25s | %s\n", "Metric Name", metricNameStr);
printf(" %-25s | %s\n", "Description", description);
printf(" %-25s | %s\n", "Type", metricTypeStr);
for (NSString *fieldName in metric[@"fields"]) {
for (NSDictionary *field in metric[@"fields"][fieldName]) {
const char *fieldNameStr = [fieldName cStringUsingEncoding:NSUTF8StringEncoding];
const char *fieldValueStr = [field[@"value"] cStringUsingEncoding:NSUTF8StringEncoding];
const char *createdStr = [field[@"created"] UTF8String];
const char *lastUpdatedStr = [field[@"last_updated"] UTF8String];
const char *data = [[NSString stringWithFormat:@"%@", field[@"data"]] UTF8String];
if (strlen(fieldNameStr) > 0) {
printf(" %-25s | %s=%s\n", "Field", fieldNameStr, fieldValueStr);
}
printf(" %-25s | %s\n", "Created", createdStr);
printf(" %-25s | %s\n", "Last Updated", lastUpdatedStr);
printf(" %-25s | %s\n", "Data", data);
}
}
printf("\n");
}
}
- (void)prettyPrintMetrics:(NSDictionary *)metrics asJSON:(BOOL)exportJSON {
BOOL exportMetrics = [[SNTConfigurator configurator] exportMetrics];
NSURL *metricsURLStr = [[SNTConfigurator configurator] metricURL];
SNTMetricFormatType metricFormat = [[SNTConfigurator configurator] metricFormat];
NSUInteger metricExportInterval = [[SNTConfigurator configurator] metricExportInterval];
NSDictionary *normalizedMetrics = SNTMetricConvertDatesToISO8601Strings(metrics);
if (exportJSON) {
// Format
NSData *metricData = [NSJSONSerialization dataWithJSONObject:normalizedMetrics
options:NSJSONWritingPrettyPrinted
error:nil];
NSString *metricStr = [[NSString alloc] initWithData:metricData encoding:NSUTF8StringEncoding];
printf("%s\n", [metricStr UTF8String]);
return;
}
if (!exportMetrics) {
printf("Metrics not configured\n");
return;
}
printf(">>> Metrics Info\n");
printf(" %-25s | %s\n", "Metrics Server", [metricsURLStr.absoluteString UTF8String]);
printf(" %-25s | %s\n", "Metrics Format",
[SNTMetricStringFromMetricFormatType(metricFormat) UTF8String]);
printf(" %-25s | %lu\n", "Export Interval (seconds)", metricExportInterval);
printf("\n");
printf(">>> Root Labels\n");
[self prettyPrintRootLabels:normalizedMetrics[@"root_labels"]];
printf("\n");
printf(">>> Metrics \n");
[self prettyPrintMetricValues:normalizedMetrics[@"metrics"]];
}
- (void)runWithArguments:(NSArray *)arguments {
__block NSDictionary *metrics;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] metrics:^(NSDictionary *exportedMetrics) {
metrics = exportedMetrics;
dispatch_group_leave(group);
}];
// Wait a maximum of 5s for metrics collected from daemon to arrive.
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
fprintf(stderr, "Failed to retrieve metrics from daemon\n\n");
}
[self prettyPrintMetrics:metrics asJSON:[arguments containsObject:@"--json"]];
exit(0);
}
@end

View File

@@ -0,0 +1,134 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/santactl/Commands/SNTCommandMetrics.h"
#import "Source/santametricservice/Formats/SNTMetricFormatTestHelper.h"
@interface SNTCommandMetricsTest : XCTestCase
@property NSString *tempDir;
@property id mockConfigurator;
@end
@implementation SNTCommandMetricsTest
- (void)setUp {
// create a temp dir
char template[] = "/tmp/sntcommandmetrictest.XXXXXXX";
char *tempPath = mkdtemp(template);
if (tempPath == NULL) {
NSLog(@"Unable to make temp directory");
exit(1);
}
self.tempDir =
[[NSFileManager defaultManager] stringWithFileSystemRepresentation:tempPath
length:strlen(tempPath)];
// mock the SNTConfigurator
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
OCMStub([self.mockConfigurator exportMetrics]).andReturn(YES);
OCMStub([self.mockConfigurator metricFormat]).andReturn(SNTMetricFormatTypeMonarchJSON);
OCMStub([self.mockConfigurator metricURL])
.andReturn([NSURL URLWithString:@"http://localhost:2444/submit"]);
OCMStub([self.mockConfigurator metricExportInterval]).andReturn((NSUInteger)30);
}
- (void)tearDown {
// delete the temp dir
NSError *err;
[[NSFileManager defaultManager] removeItemAtPath:self.tempDir error:&err];
if (err != nil) {
NSLog(@"unable to remove %@, error: %@", self.tempDir, err);
}
dup2(1, STDOUT_FILENO);
}
- (void)testPrettyPrintingJSON {
NSError *err;
NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath];
path = [path stringByAppendingPathComponent:@"Commands/testdata/metrics-prettyprint.json"];
NSString *goldenFileContents = [[NSString alloc]
initWithData:[NSData dataWithContentsOfFile:path options:NSDataReadingUncached error:&err]
encoding:NSUTF8StringEncoding];
XCTAssertNil(err, @"failed to read golden file %@ for testPrettyPrintingJSON", path);
SNTCommandMetrics *metricsCmd = [[SNTCommandMetrics alloc] init];
NSString *outputPath = [NSString pathWithComponents:@[ self.tempDir, @"test.data" ]];
// redirect stdout
int fd = open([outputPath UTF8String], O_TRUNC | O_WRONLY | O_CREAT, 0600);
int saved_stdout = dup(fileno(stdout));
dup2(fd, fileno(stdout));
[metricsCmd prettyPrintMetrics:[SNTMetricFormatTestHelper createValidMetricsDictionary]
asJSON:YES];
// restore stdout
fflush(stdout);
dup2(saved_stdout, fileno(stdout));
// open test file assert equal with golden file
NSString *commandOutput =
[[NSString alloc] initWithData:[NSData dataWithContentsOfFile:outputPath]
encoding:NSUTF8StringEncoding];
XCTAssertEqualObjects(goldenFileContents, commandOutput,
@"Metrics command command did not produce expected output");
}
- (void)testPrettyPrinting {
NSError *err;
NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath];
path = [path stringByAppendingPathComponent:@"Commands/testdata/metrics-prettyprint.txt"];
NSString *goldenFileContents = [[NSString alloc]
initWithData:[NSData dataWithContentsOfFile:path options:NSDataReadingUncached error:&err]
encoding:NSUTF8StringEncoding];
XCTAssertNil(err, @"failed to read golden file %@ for testPrettyPrinting", path);
SNTCommandMetrics *metricsCmd = [[SNTCommandMetrics alloc] init];
NSString *outputPath = [NSString pathWithComponents:@[ self.tempDir, @"test.data" ]];
// redirect stdout
int fd = open([outputPath UTF8String], O_TRUNC | O_WRONLY | O_CREAT, 0600);
int saved_stdout = dup(fileno(stdout));
dup2(fd, fileno(stdout));
[metricsCmd prettyPrintMetrics:[SNTMetricFormatTestHelper createValidMetricsDictionary]
asJSON:NO];
// restore stdout
fflush(stdout);
dup2(saved_stdout, fileno(stdout));
// open test file assert equal with golden file
NSString *commandOutput =
[[NSString alloc] initWithData:[NSData dataWithContentsOfFile:outputPath]
encoding:NSUTF8StringEncoding];
XCTAssertEqualObjects(goldenFileContents, commandOutput,
@"Metrics command command did not produce expected output");
}
@end

View File

@@ -60,9 +60,11 @@ REGISTER_COMMAND_NAME(@"rule")
@" Will add the hash of the file currently at that path.\n"
@" Does not work with --check. Use the fileinfo verb to check.\n"
@" the rule state of a file.\n"
@" --sha256 {sha256}: hash to add/remove/check\n"
@" --identifier {sha256|teamID}: identifier to add/remove/check\n"
@" --sha256 {sha256}: hash to add/remove/check [deprecated]\n"
@"\n"
@" Optionally:\n"
@" --teamid: add or check a team ID rule instead of binary\n"
@" --certificate: add or check a certificate sha256 rule instead of binary\n"
#ifdef DEBUG
@" --force: allow manual changes even when SyncBaseUrl is set\n"
@@ -111,17 +113,24 @@ REGISTER_COMMAND_NAME(@"rule")
check = YES;
} else if ([arg caseInsensitiveCompare:@"--certificate"] == NSOrderedSame) {
newRule.type = SNTRuleTypeCertificate;
} else if ([arg caseInsensitiveCompare:@"--teamid"] == NSOrderedSame) {
newRule.type = SNTRuleTypeTeamID;
} else if ([arg caseInsensitiveCompare:@"--path"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--path requires an argument"];
}
path = arguments[i];
} else if ([arg caseInsensitiveCompare:@"--identifier"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--identifier requires an argument"];
}
newRule.identifier = arguments[i];
} else if ([arg caseInsensitiveCompare:@"--sha256"] == NSOrderedSame) {
if (++i > arguments.count - 1) {
[self printErrorUsageAndExit:@"--sha256 requires an argument"];
}
newRule.shasum = arguments[i];
if (newRule.shasum.length != 64) {
newRule.identifier = arguments[i];
if (newRule.identifier.length != 64) {
[self printErrorUsageAndExit:@"--sha256 requires a valid SHA-256 as the argument"];
}
} else if ([arg caseInsensitiveCompare:@"--message"] == NSOrderedSame) {
@@ -139,7 +148,7 @@ REGISTER_COMMAND_NAME(@"rule")
}
if (check) {
if (!newRule.shasum) return [self printErrorUsageAndExit:@"--check requires --sha256"];
if (!newRule.identifier) return [self printErrorUsageAndExit:@"--check requires --sha256"];
return [self printStateOfRule:newRule daemonConnection:self.daemonConn];
}
@@ -150,16 +159,17 @@ REGISTER_COMMAND_NAME(@"rule")
}
if (newRule.type == SNTRuleTypeBinary) {
newRule.shasum = fi.SHA256;
newRule.identifier = fi.SHA256;
} else if (newRule.type == SNTRuleTypeCertificate) {
MOLCodesignChecker *cs = [fi codesignCheckerWithError:NULL];
newRule.shasum = cs.leafCertificate.SHA256;
newRule.identifier = cs.leafCertificate.SHA256;
} else if (newRule.type == SNTRuleTypeTeamID) {
}
}
if (newRule.state == SNTRuleStateUnknown) {
[self printErrorUsageAndExit:@"No state specified"];
} else if (!newRule.shasum) {
} else if (!newRule.identifier) {
[self printErrorUsageAndExit:@"Either SHA-256 or path to file must be specified"];
}
@@ -174,9 +184,9 @@ REGISTER_COMMAND_NAME(@"rule")
exit(1);
} else {
if (newRule.state == SNTRuleStateRemove) {
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
printf("Removed rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
} else {
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
printf("Added rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
}
exit(0);
}
@@ -184,14 +194,16 @@ REGISTER_COMMAND_NAME(@"rule")
}
- (void)printStateOfRule:(SNTRule *)rule daemonConnection:(MOLXPCConnection *)daemonConn {
NSString *fileSHA256 = (rule.type == SNTRuleTypeBinary) ? rule.shasum : nil;
NSString *certificateSHA256 = (rule.type == SNTRuleTypeCertificate) ? rule.shasum : nil;
NSString *fileSHA256 = (rule.type == SNTRuleTypeBinary) ? rule.identifier : nil;
NSString *certificateSHA256 = (rule.type == SNTRuleTypeCertificate) ? rule.identifier : nil;
NSString *teamID = (rule.type == SNTRuleTypeTeamID) ? rule.identifier : nil;
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
__block NSMutableString *output;
[[daemonConn remoteObjectProxy] decisionForFilePath:nil
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256
teamID:teamID
reply:^(SNTEventState s) {
output = (SNTEventStateAllow & s)
? @"Allowed".mutableCopy
@@ -219,6 +231,10 @@ REGISTER_COMMAND_NAME(@"rule")
case SNTEventStateAllowTransitive:
[output appendString:@" (Transitive)"];
break;
case SNTEventStateAllowTeamID:
case SNTEventStateBlockTeamID:
[output appendString:@" (TeamID)"];
break;
default: output = @"None".mutableCopy; break;
}
if (isatty(STDOUT_FILENO)) {
@@ -244,6 +260,7 @@ REGISTER_COMMAND_NAME(@"rule")
[[daemonConn remoteObjectProxy]
databaseRuleForBinarySHA256:fileSHA256
certificateSHA256:certificateSHA256
teamID:teamID
reply:^(SNTRule *r) {
if (r.state == SNTRuleStateAllowTransitive) {
NSDate *date =

View File

@@ -94,17 +94,19 @@ REGISTER_COMMAND_NAME(@"status")
}
// Database counts
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1;
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1, teamIDRuleCount = -1;
__block int64_t compilerRuleCount = -1, transitiveRuleCount = -1;
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate,
int64_t compiler, int64_t transitive) {
binaryRuleCount = binary;
certRuleCount = certificate;
compilerRuleCount = compiler;
transitiveRuleCount = transitive;
dispatch_group_leave(group);
}];
[[self.daemonConn remoteObjectProxy]
databaseRuleCounts:^(int64_t binary, int64_t certificate, int64_t compiler, int64_t transitive,
int64_t teamID) {
binaryRuleCount = binary;
certRuleCount = certificate;
teamIDRuleCount = teamID;
compilerRuleCount = compiler;
transitiveRuleCount = transitive;
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseEventCount:^(int64_t count) {
eventCount = count;
@@ -172,6 +174,10 @@ REGISTER_COMMAND_NAME(@"status")
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
BOOL exportMetrics = [[SNTConfigurator configurator] exportMetrics];
NSURL *metricsURLStr = [[SNTConfigurator configurator] metricURL];
NSUInteger metricExportInterval = [[SNTConfigurator configurator] metricExportInterval];
if ([arguments containsObject:@"--json"]) {
NSMutableDictionary *stats = [@{
@"daemon" : @{
@@ -226,6 +232,7 @@ REGISTER_COMMAND_NAME(@"status")
printf(">>> Database Info\n");
printf(" %-25s | %lld\n", "Binary Rules", binaryRuleCount);
printf(" %-25s | %lld\n", "Certificate Rules", certRuleCount);
printf(" %-25s | %lld\n", "TeamID Rules", teamIDRuleCount);
printf(" %-25s | %lld\n", "Compiler Rules", compilerRuleCount);
printf(" %-25s | %lld\n", "Transitive Rules", transitiveRuleCount);
printf(" %-25s | %lld\n", "Events Pending Upload", eventCount);
@@ -241,6 +248,12 @@ REGISTER_COMMAND_NAME(@"status")
printf(" %-25s | %s\n", "Bundle Scanning", (enableBundles ? "Yes" : "No"));
printf(" %-25s | %s\n", "Transitive Rules", (enableTransitiveRules ? "Yes" : "No"));
}
if (exportMetrics) {
printf(">>> Metrics Info\n");
printf(" %-25s | %s\n", "Metrics Server", [[metricsURLStr absoluteString] UTF8String]);
printf(" %-25s | %lu\n", "Export Interval (seconds)", metricExportInterval);
}
}
exit(0);

View File

@@ -37,6 +37,7 @@ extern NSString *const kBinaryRuleCount;
extern NSString *const kCertificateRuleCount;
extern NSString *const kCompilerRuleCount;
extern NSString *const kTransitiveRuleCount;
extern NSString *const kTeamIDRuleCount;
extern NSString *const kFullSyncInterval;
extern NSString *const kFCMToken;
extern NSString *const kFCMFullSyncInterval;

View File

@@ -37,6 +37,7 @@ NSString *const kBinaryRuleCount = @"binary_rule_count";
NSString *const kCertificateRuleCount = @"certificate_rule_count";
NSString *const kCompilerRuleCount = @"compiler_rule_count";
NSString *const kTransitiveRuleCount = @"transitive_rule_count";
NSString *const kTeamIDRuleCount = @"teamid_rule_count";
NSString *const kFullSyncInterval = @"full_sync_interval";
NSString *const kFCMToken = @"fcm_token";
NSString *const kFCMFullSyncInterval = @"fcm_full_sync_interval";

View File

@@ -43,14 +43,16 @@
dispatch_group_t group = dispatch_group_create();
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate,
int64_t compiler, int64_t transitive) {
requestDict[kBinaryRuleCount] = @(binary);
requestDict[kCertificateRuleCount] = @(certificate);
requestDict[kCompilerRuleCount] = @(compiler);
requestDict[kTransitiveRuleCount] = @(transitive);
dispatch_group_leave(group);
}];
[[self.daemonConn remoteObjectProxy]
databaseRuleCounts:^(int64_t binary, int64_t certificate, int64_t compiler, int64_t transitive,
int64_t teamID) {
requestDict[kBinaryRuleCount] = @(binary);
requestDict[kCertificateRuleCount] = @(certificate);
requestDict[kCompilerRuleCount] = @(compiler);
requestDict[kTransitiveRuleCount] = @(transitive);
requestDict[kTeamIDRuleCount] = @(teamID);
dispatch_group_leave(group);
}];
dispatch_group_enter(group);
[[self.daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {

View File

@@ -136,8 +136,7 @@
if (![dict isKindOfClass:[NSDictionary class]]) return nil;
SNTRule *newRule = [[SNTRule alloc] init];
newRule.shasum = dict[kRuleSHA256];
if (newRule.shasum.length != 64) return nil;
newRule.identifier = dict[kRuleSHA256];
NSString *policyString = dict[kRulePolicy];
if ([policyString isEqual:kRulePolicyAllowlist] ||
@@ -178,7 +177,7 @@
// it is simply the binary hash.
NSString *primaryHash = dict[kFileBundleHash];
if (primaryHash.length != 64) {
primaryHash = newRule.shasum;
primaryHash = newRule.identifier;
}
// As we read in rules, we update the "remaining count" information stored in

View File

@@ -17,17 +17,17 @@
#import <MOLXPCConnection/MOLXPCConnection.h>
#import <OCMock/OCMock.h>
#import "SNTCommandSyncConstants.h"
#import "SNTCommandSyncEventUpload.h"
#import "SNTCommandSyncPostflight.h"
#import "SNTCommandSyncPreflight.h"
#import "SNTCommandSyncRuleDownload.h"
#import "SNTCommandSyncStage.h"
#import "SNTCommandSyncState.h"
#import "SNTCommonEnums.h"
#import "SNTRule.h"
#import "SNTStoredEvent.h"
#import "SNTXPCControlInterface.h"
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTRule.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncConstants.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncEventUpload.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPostflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncPreflight.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncStage.h"
#import "Source/santactl/Commands/sync/SNTCommandSyncState.h"
// Prevent Zlib compression during testing
@implementation NSData (Zlib)
@@ -136,6 +136,7 @@
*/
- (NSData *)dataFromFixture:(NSString *)file {
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:file ofType:nil];
XCTAssertNotNil(path, @"failed to load testdata: %@", file);
return [NSData dataWithContentsOfFile:path];
}
@@ -200,11 +201,12 @@
- (void)testPreflightDatabaseCounts {
SNTCommandSyncPreflight *sut = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
int64_t bin = 5, cert = 8, compiler = 2, transitive = 19;
int64_t bin = 5, cert = 8, compiler = 2, transitive = 19, teamID = 3;
OCMStub([self.daemonConnRop
databaseRuleCounts:([OCMArg invokeBlockWithArgs:OCMOCK_VALUE(bin), OCMOCK_VALUE(cert),
OCMOCK_VALUE(compiler),
OCMOCK_VALUE(transitive), nil])]);
OCMOCK_VALUE(transitive), OCMOCK_VALUE(teamID),
nil])]);
[self stubRequestBody:nil
response:nil
@@ -215,6 +217,7 @@
XCTAssertEqualObjects(requestDict[kCertificateRuleCount], @(8));
XCTAssertEqualObjects(requestDict[kCompilerRuleCount], @(2));
XCTAssertEqualObjects(requestDict[kTransitiveRuleCount], @(19));
XCTAssertEqualObjects(requestDict[kTeamIDRuleCount], @(3));
return YES;
}];
@@ -406,20 +409,20 @@
NSArray *rules = @[
[[SNTRule alloc]
initWithShasum:@"ee382e199f7eda58863a93a7854b930ade35798bc6856ee8e6ab6ce9277f0eab"
state:SNTRuleStateBlock
type:SNTRuleTypeBinary
customMsg:@""],
initWithIdentifier:@"ee382e199f7eda58863a93a7854b930ade35798bc6856ee8e6ab6ce9277f0eab"
state:SNTRuleStateBlock
type:SNTRuleTypeBinary
customMsg:@""],
[[SNTRule alloc]
initWithShasum:@"46f8c706d0533a54554af5fc163eea704f10c08b30f8a5db12bfdc04fb382fc3"
state:SNTRuleStateAllow
type:SNTRuleTypeCertificate
customMsg:@""],
initWithIdentifier:@"46f8c706d0533a54554af5fc163eea704f10c08b30f8a5db12bfdc04fb382fc3"
state:SNTRuleStateAllow
type:SNTRuleTypeCertificate
customMsg:@""],
[[SNTRule alloc]
initWithShasum:@"7846698e47ef41be80b83fb9e2b98fa6dc46c9188b068bff323c302955a00142"
state:SNTRuleStateBlock
type:SNTRuleTypeCertificate
customMsg:@"Hi There"],
initWithIdentifier:@"7846698e47ef41be80b83fb9e2b98fa6dc46c9188b068bff323c302955a00142"
state:SNTRuleStateBlock
type:SNTRuleTypeCertificate
customMsg:@"Hi There"],
];
OCMVerify([self.daemonConnRop databaseRuleAddRules:rules cleanSlate:NO reply:OCMOCK_ANY]);

View File

@@ -0,0 +1,118 @@
{
"metrics" : {
"\/santa\/rules" : {
"type" : 7,
"description" : "Number of rules",
"fields" : {
"rule_type" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "binary",
"data" : 1
},
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "certificate",
"data" : 3
}
]
}
},
"\/proc\/memory\/resident_size" : {
"type" : 7,
"description" : "The resident set size of this process",
"fields" : {
"" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "",
"data" : 123456789
}
]
}
},
"\/santa\/events" : {
"type" : 9,
"description" : "Count of process exec events on the host",
"fields" : {
"rule_type" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "binary",
"data" : 1
},
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "certificate",
"data" : 2
}
]
}
},
"\/santa\/using_endpoint_security_framework" : {
"type" : 1,
"description" : "Is santad using the endpoint security framework",
"fields" : {
"" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "",
"data" : true
}
]
}
},
"\/proc\/birth_timestamp" : {
"type" : 3,
"description" : "Start time of this santad instance, in microseconds since epoch",
"fields" : {
"" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "",
"data" : 1250999830800
}
]
}
},
"\/proc\/memory\/virtual_size" : {
"type" : 7,
"description" : "The virtual memory size of this process",
"fields" : {
"" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "",
"data" : 987654321
}
]
}
},
"\/build\/label" : {
"type" : 2,
"description" : "Software version running",
"fields" : {
"" : [
{
"created" : "2021-09-16T21:07:34.826Z",
"last_updated" : "2021-09-16T21:07:34.826Z",
"value" : "",
"data" : "20210809.0.1"
}
]
}
}
},
"root_labels" : {
"hostname" : "testHost",
"username" : "testUser"
}
}

View File

@@ -0,0 +1,69 @@
>>> Metrics Info
Metrics Server | http://localhost:2444/submit
Metrics Format | monarchjson
Export Interval (seconds) | 30
>>> Root Labels
hostname | testHost
username | testUser
>>> Metrics
Metric Name | /santa/rules
Description | Number of rules
Type | SNTMetricTypeGaugeInt64 7
Field | rule_type=binary
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 1
Field | rule_type=certificate
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 3
Metric Name | /proc/memory/resident_size
Description | The resident set size of this process
Type | SNTMetricTypeGaugeInt64 7
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 123456789
Metric Name | /santa/events
Description | Count of process exec events on the host
Type | SNTMetricTypeCounter 9
Field | rule_type=binary
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 1
Field | rule_type=certificate
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 2
Metric Name | /santa/using_endpoint_security_framework
Description | Is santad using the endpoint security framework
Type | SNTMetricTypeConstantBool 1
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 1
Metric Name | /proc/birth_timestamp
Description | Start time of this santad instance, in microseconds since epoch
Type | SNTMetricTypeConstantInt64 3
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 1250999830800
Metric Name | /proc/memory/virtual_size
Description | The virtual memory size of this process
Type | SNTMetricTypeGaugeInt64 7
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 987654321
Metric Name | /build/label
Description | Software version running
Type | SNTMetricTypeConstantString 2
Created | 2021-09-16T21:07:34.826Z
Last Updated | 2021-09-16T21:07:34.826Z
Data | 20210809.0.1

View File

@@ -12,12 +12,12 @@ objc_library(
"DataLayer/SNTEventTable.m",
"DataLayer/SNTRuleTable.h",
"DataLayer/SNTRuleTable.m",
"EventProviders/SNTCachingEndpointSecurityManager.h",
"EventProviders/SNTCachingEndpointSecurityManager.mm",
"EventProviders/SNTDriverManager.h",
"EventProviders/SNTDriverManager.m",
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTCachingEndpointSecurityManager.h",
"EventProviders/SNTCachingEndpointSecurityManager.mm",
"EventProviders/SNTEventProvider.h",
"Logs/SNTEventLog.h",
"Logs/SNTEventLog.m",
@@ -52,7 +52,6 @@ objc_library(
"IOKit",
],
deps = [
"//Source/common:SantaCache",
"//Source/common:SNTBlockMessage",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
@@ -61,25 +60,41 @@ objc_library(
"//Source/common:SNTFileInfo",
"//Source/common:SNTKernelCommon",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTPrefixTree",
"//Source/common:SNTRule",
"//Source/common:SNTStoredEvent",
"//Source/common:SNTXPCControlInterface",
"//Source/common:SNTXPCMetricServiceInterface",
"//Source/common:SNTXPCNotifierInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SantaCache",
"//Source/santad:SNTApplicationCoreMetrics",
"@FMDB",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
)
objc_library(
name = "SNTApplicationCoreMetrics",
srcs = ["SNTApplicationCoreMetrics.m"],
hdrs = ["SNTApplicationCoreMetrics.h"],
deps = [
"//Source/common:SNTCommonEnums",
"//Source/common:SNTConfigurator",
"//Source/common:SNTMetricSet",
"//Source/common:SNTSystemInfo",
],
)
objc_library(
name = "EndpointSecurityTestLib",
testonly = 1,
srcs = [
"EventProviders/EndpointSecurityTestUtil.h",
"EventProviders/EndpointSecurityTestUtil.mm",
],
testonly = 1,
sdk_dylibs = [
"EndpointSecurity",
"bsm",
@@ -90,14 +105,14 @@ macos_bundle(
name = "com.google.santa.daemon",
bundle_extension = "systemextension",
bundle_id = "com.google.santa.daemon",
infoplists = ["Info.plist"],
linkopts = ["-execute"],
minimum_os_version = "10.9",
codesignopts = [
"--timestamp",
"--force",
"--options library,kill,runtime",
],
infoplists = ["Info.plist"],
linkopts = ["-execute"],
minimum_os_version = "10.9",
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Daemon_Dev.provisionprofile",
@@ -110,37 +125,14 @@ macos_bundle(
santa_unit_test(
name = "SNTExecutionControllerTest",
srcs = [
"DataLayer/SNTDatabaseTable.h",
"DataLayer/SNTDatabaseTable.m",
"DataLayer/SNTEventTable.h",
"DataLayer/SNTEventTable.m",
"DataLayer/SNTRuleTable.h",
"DataLayer/SNTRuleTable.m",
"EventProviders/SNTDriverManager.h",
"EventProviders/SNTDriverManager.m",
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTEventProvider.h",
"Logs/SNTEventLog.h",
"Logs/SNTEventLog.m",
"SNTDatabaseController.h",
"SNTDatabaseController.m",
"SNTExecutionController.h",
"SNTExecutionController.m",
"SNTExecutionControllerTest.m",
"SNTNotificationQueue.h",
"SNTNotificationQueue.m",
"SNTPolicyProcessor.h",
"SNTPolicyProcessor.m",
"SNTSyncdQueue.h",
"SNTSyncdQueue.m",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
"//Source/common:SantaCache",
":santad_lib",
"//Source/common:SNTBlockMessage",
"//Source/common:SNTCachedDecision",
"//Source/common:SNTCommonEnums",
@@ -152,6 +144,7 @@ santa_unit_test(
"//Source/common:SNTRule",
"//Source/common:SNTXPCNotifierInterface",
"//Source/common:SNTXPCSyncdInterface",
"//Source/common:SantaCache",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
@@ -201,10 +194,14 @@ santa_unit_test(
santa_unit_test(
name = "SNTEndpointSecurityManagerTest",
srcs = [
"EventProviders/SNTEndpointSecurityManager.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTEndpointSecurityManagerTest.mm",
"EventProviders/SNTEventProvider.h",
"EventProviders/SNTEndpointSecurityManager.mm",
"EventProviders/SNTEndpointSecurityManager.h",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":EndpointSecurityTestLib",
@@ -212,31 +209,64 @@ santa_unit_test(
"//Source/common:SNTPrefixTree",
"//Source/common:SantaCache",
],
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
)
santa_unit_test(
name = "SNTApplicationTest",
srcs = [
"SNTApplicationTest.m",
],
data = [
"//Source/santad/testdata:binaryrules_testdata",
],
srcs = [
"SNTApplicationTest.m"
],
deps = [
":santad_lib",
":EndpointSecurityTestLib",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":EndpointSecurityTestLib",
":santad_lib",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
],
)
santa_unit_test(
name = "SNTApplicationBenchmark",
srcs = [
"SNTApplicationBenchmark.m",
],
data = [
"//Source/santad/testdata:binaryrules_testdata",
],
minimum_os_version = "10.15",
sdk_dylibs = [
"EndpointSecurity",
"bsm",
],
deps = [
":EndpointSecurityTestLib",
":santad_lib",
"@MOLCodesignChecker",
"@MOLXPCConnection",
"@OCMock",
],
)
santa_unit_test(
name = "SNTApplicationCoreMetricsTest",
srcs = [
"SNTApplicationCoreMetricsTest.m",
],
minimum_os_version = "10.15",
deps = [
":SNTApplicationCoreMetrics",
"//Source/common:SNTCommonEnums",
"//Source/common:SNTMetricSet",
"//Source/common:SNTSystemInfo",
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
"@OCMock",
],
)

View File

@@ -51,12 +51,18 @@
///
- (NSUInteger)certificateRuleCount;
///
/// @return Number of team ID rules in the database
///
- (NSUInteger)teamIDRuleCount;
///
/// @return Rule for binary or certificate with given SHA-256. The binary rule will be returned
/// if it exists. If not, the certificate rule will be returned if it exists.
///
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256;
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID;
///
/// Add an array of rules to the database. The rules will be added within a transaction and the

View File

@@ -39,11 +39,25 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
@implementation SNTRuleTable
// ES on Monterey now has a default mute set of paths that are automatically applied to each ES
// client. This mute set contains most (not all) AUTH event types for some paths that were deemed
// system critical.
// Retain this list for < 12.0 versions of ES, but we should be able to rely on the paths muted by
// default (visible with es_muted_paths_events any time after connecting a new client and before
// modifying any of the mute state).
+ (NSArray *)criticalSystemBinaryPaths {
return @[
@"/usr/libexec/trustd",
@"/usr/sbin/securityd",
@"/usr/libexec/xpcproxy",
@"/usr/libexec/amfid",
@"/usr/libexec/opendirectoryd",
@"/usr/libexec/runningboardd",
@"/usr/libexec/syspolicyd",
@"/usr/libexec/watchdogd",
@"/usr/libexec/cfprefsd",
@"/usr/sbin/securityd",
@"/System/Library/PrivateFrameworks/TCC.framework/Versions/A/Resources/tccd",
@"/System/Library/PrivateFrameworks/SkyLight.framework/Versions/A/Resources/WindowServer",
@"/usr/sbin/ocspd",
@"/usr/lib/dyld",
@"/Applications/Santa.app/Contents/MacOS/Santa",
@@ -120,6 +134,11 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
[db executeUpdate:@"ALTER TABLE 'rules' ADD 'timestamp' INTEGER"];
newVersion = 3;
}
if (version < 4) {
// Rename `shasum` column to `identifier`.
[db executeUpdate:@"ALTER TABLE 'rules' RENAME COLUMN 'shasum' TO 'identifier'"];
newVersion = 4;
}
// Save signing info for launchd and santad. Used to ensure they are always allowed.
self.santadCSInfo = [[MOLCodesignChecker alloc] initWithSelf];
@@ -175,25 +194,36 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
return count;
}
- (NSUInteger)teamIDRuleCount {
__block NSUInteger count = 0;
[self inDatabase:^(FMDatabase *db) {
count = [db longForQuery:@"SELECT COUNT(*) FROM rules WHERE type=3"];
}];
return count;
}
- (SNTRule *)ruleFromResultSet:(FMResultSet *)rs {
return [[SNTRule alloc] initWithShasum:[rs stringForColumn:@"shasum"]
state:[rs intForColumn:@"state"]
type:[rs intForColumn:@"type"]
customMsg:[rs stringForColumn:@"custommsg"]
timestamp:[rs intForColumn:@"timestamp"]];
return [[SNTRule alloc] initWithIdentifier:[rs stringForColumn:@"identifier"]
state:[rs intForColumn:@"state"]
type:[rs intForColumn:@"type"]
customMsg:[rs stringForColumn:@"custommsg"]
timestamp:[rs intForColumn:@"timestamp"]];
}
- (SNTRule *)ruleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256 {
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID {
__block SNTRule *rule;
// NOTE: This code is written with the intention that the binary rule is searched for first
// as Santa is designed to go with the most-specific rule possible. As such the query
// should have "ORDER BY type DESC" before the LIMIT, to ensure that is the case. However,
// in all tested versions of SQLite that ORDER BY clause is unnecessary: the query is
// performed 'as written' by doing 2 separate lookups in the index and the second is skipped
// if the first returns a result. That behavior can be checked here:
// http://sqlfiddle.com/#!5/09c8a/2
// as Santa is designed to go with the most-specific rule possible.
//
// The intended order of precedence is Binaries > Certificates > Team IDs.
//
// As such the query should have "ORDER BY type DESC" before the LIMIT, to ensure that is the
// case. However, in all tested versions of SQLite that ORDER BY clause is unnecessary: the query
// is performed 'as written' by doing separate lookups in the index and the later lookups are if
// the first returns a result. That behavior can be checked here: http://sqlfiddle.com/#!5/cdc42/1
//
// Adding the ORDER BY clause slows down this query, particularly in a database where
// the number of binary rules outweighs the number of certificate rules because:
@@ -204,9 +234,9 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
//
[self inDatabase:^(FMDatabase *db) {
FMResultSet *rs =
[db executeQuery:
@"SELECT * FROM rules WHERE (shasum=? and type=1) OR (shasum=? AND type=2) LIMIT 1",
binarySHA256, certificateSHA256];
[db executeQuery:@"SELECT * FROM rules WHERE (identifier=? and type=1) OR "
@"(identifier=? AND type=2) OR (identifier=? AND type=3) LIMIT 1",
binarySHA256, certificateSHA256, teamID];
if ([rs next]) {
rule = [self ruleFromResultSet:rs];
}
@@ -216,11 +246,11 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
// Allow binaries signed by the "Software Signing" cert used to sign launchd
// if no existing rule has matched.
if (!rule && [certificateSHA256 isEqual:self.launchdCSInfo.leafCertificate.SHA256]) {
rule = [[SNTRule alloc] initWithShasum:certificateSHA256
state:SNTRuleStateAllow
type:SNTRuleTypeCertificate
customMsg:nil
timestamp:0];
rule = [[SNTRule alloc] initWithIdentifier:certificateSHA256
state:SNTRuleStateAllow
type:SNTRuleTypeCertificate
customMsg:nil
timestamp:0];
}
return rule;
@@ -244,7 +274,7 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
}
for (SNTRule *rule in rules) {
if (![rule isKindOfClass:[SNTRule class]] || rule.shasum.length == 0 ||
if (![rule isKindOfClass:[SNTRule class]] || rule.identifier.length == 0 ||
rule.state == SNTRuleStateUnknown || rule.type == SNTRuleTypeUnknown) {
[self fillError:error code:SNTRuleTableErrorInvalidRule message:rule.description];
*rollback = failed = YES;
@@ -252,7 +282,7 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
}
if (rule.state == SNTRuleStateRemove) {
if (![db executeUpdate:@"DELETE FROM rules WHERE shasum=? AND type=?", rule.shasum,
if (![db executeUpdate:@"DELETE FROM rules WHERE identifier=? AND type=?", rule.identifier,
@(rule.type)]) {
[self fillError:error code:SNTRuleTableErrorRemoveFailed message:[db lastErrorMessage]];
*rollback = failed = YES;
@@ -260,9 +290,9 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
}
} else {
if (![db executeUpdate:@"INSERT OR REPLACE INTO rules "
@"(shasum, state, type, custommsg, timestamp) "
@"(identifier, state, type, custommsg, timestamp) "
@"VALUES (?, ?, ?, ?, ?);",
rule.shasum, @(rule.state), @(rule.type), rule.customMsg,
rule.identifier, @(rule.state), @(rule.type), rule.customMsg,
@(rule.timestamp)]) {
[self fillError:error
code:SNTRuleTableErrorInsertOrReplaceFailed
@@ -284,7 +314,7 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
}
// If still here, then all rules in the array are allowlist rules. So now we look for allowlist
// rules where there is a previously existing allowlist compiler rule for the same shasum.
// rules where there is a previously existing allowlist compiler rule for the same identifier.
// If so we find such a rule, then cache should be flushed.
__block BOOL flushDecisionCache = NO;
[self inTransaction:^(FMDatabase *db, BOOL *rollback) {
@@ -293,8 +323,8 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
if (rule.type == SNTRuleTypeCertificate) continue;
if ([db longForQuery:
@"SELECT COUNT(*) FROM rules WHERE shasum=? AND type=? AND state=? LIMIT 1",
rule.shasum, @(SNTRuleTypeBinary), @(SNTRuleStateAllowCompiler)] > 0) {
@"SELECT COUNT(*) FROM rules WHERE identifier=? AND type=? AND state=? LIMIT 1",
rule.identifier, @(SNTRuleTypeBinary), @(SNTRuleStateAllowCompiler)] > 0) {
flushDecisionCache = YES;
break;
}
@@ -309,9 +339,9 @@ static const NSUInteger kTransitiveRuleExpirationSeconds = 6 * 30 * 24 * 3600;
if (!rule) return;
[rule resetTimestamp];
[self inDatabase:^(FMDatabase *db) {
if (![db executeUpdate:@"UPDATE rules SET timestamp=? WHERE shasum=? AND type=?",
@(rule.timestamp), rule.shasum, @(rule.type)]) {
LOGE(@"Could not update timestamp for rule with sha256=%@", rule.shasum);
if (![db executeUpdate:@"UPDATE rules SET timestamp=? WHERE identifier=? AND type=?",
@(rule.timestamp), rule.identifier, @(rule.type)]) {
LOGE(@"Could not update timestamp for rule with sha256=%@", rule.identifier);
}
}];
}

View File

@@ -34,9 +34,18 @@
self.sut = [[SNTRuleTable alloc] initWithDatabaseQueue:self.dbq];
}
- (SNTRule *)_exampleTeamIDRule {
SNTRule *r = [[SNTRule alloc] init];
r.identifier = @"teamID";
r.state = SNTRuleStateBlock;
r.type = SNTRuleTypeTeamID;
r.customMsg = @"A teamID rule";
return r;
}
- (SNTRule *)_exampleBinaryRule {
SNTRule *r = [[SNTRule alloc] init];
r.shasum = @"a";
r.identifier = @"a";
r.state = SNTRuleStateBlock;
r.type = SNTRuleTypeBinary;
r.customMsg = @"A rule";
@@ -45,7 +54,7 @@
- (SNTRule *)_exampleCertRule {
SNTRule *r = [[SNTRule alloc] init];
r.shasum = @"b";
r.identifier = @"b";
r.state = SNTRuleStateAllow;
r.type = SNTRuleTypeCertificate;
return r;
@@ -103,7 +112,7 @@
- (void)testAddInvalidRule {
SNTRule *r = [[SNTRule alloc] init];
r.shasum = @"a";
r.identifier = @"a";
r.type = SNTRuleTypeCertificate;
NSError *error;
@@ -116,12 +125,12 @@
cleanSlate:NO
error:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"a");
XCTAssertEqualObjects(r.identifier, @"a");
XCTAssertEqual(r.type, SNTRuleTypeBinary);
r = [self.sut ruleForBinarySHA256:@"b" certificateSHA256:nil];
r = [self.sut ruleForBinarySHA256:@"b" certificateSHA256:nil teamID:nil];
XCTAssertNil(r);
}
@@ -130,26 +139,52 @@
cleanSlate:NO
error:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"b"];
SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"b" teamID:nil];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"b");
XCTAssertEqualObjects(r.identifier, @"b");
XCTAssertEqual(r.type, SNTRuleTypeCertificate);
r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"a"];
r = [self.sut ruleForBinarySHA256:nil certificateSHA256:@"a" teamID:nil];
XCTAssertNil(r);
}
- (void)testFetchTeamIDRule {
[self.sut addRules:@[ [self _exampleBinaryRule], [self _exampleTeamIDRule] ]
cleanSlate:NO
error:nil];
SNTRule *r = [self.sut ruleForBinarySHA256:nil certificateSHA256:nil teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"teamID");
XCTAssertEqual(r.type, SNTRuleTypeTeamID);
XCTAssertEqual([self.sut teamIDRuleCount], 1);
r = [self.sut ruleForBinarySHA256:nil certificateSHA256:nil teamID:@"nonexistentTeamID"];
XCTAssertNil(r);
}
- (void)testFetchRuleOrdering {
[self.sut addRules:@[ [self _exampleCertRule], [self _exampleBinaryRule] ]
cleanSlate:NO
error:nil];
[self.sut
addRules:@[ [self _exampleCertRule], [self _exampleBinaryRule], [self _exampleTeamIDRule] ]
cleanSlate:NO
error:nil];
// This test verifies that the implicit rule ordering we've been abusing is still working.
// See the comment in SNTRuleTable#ruleForBinarySHA256:certificateSHA256:
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:@"b"];
// See the comment in SNTRuleTable#ruleForBinarySHA256:certificateSHA256:teamID
SNTRule *r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:@"b" teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.shasum, @"a");
XCTAssertEqualObjects(r.identifier, @"a");
XCTAssertEqual(r.type, SNTRuleTypeBinary, @"Implicit rule ordering failed");
r = [self.sut ruleForBinarySHA256:@"a" certificateSHA256:@"unknowncert" teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"a");
XCTAssertEqual(r.type, SNTRuleTypeBinary, @"Implicit rule ordering failed");
r = [self.sut ruleForBinarySHA256:@"unknown" certificateSHA256:@"b" teamID:@"teamID"];
XCTAssertNotNil(r);
XCTAssertEqualObjects(r.identifier, @"b");
XCTAssertEqual(r.type, SNTRuleTypeCertificate, @"Implicit rule ordering failed");
}
- (void)testBadDatabase {

View File

@@ -14,15 +14,20 @@
#import <Foundation/Foundation.h>
#import "Source/common/SNTKernelCommon.h"
#include "Source/common/SNTKernelCommon.h"
@class SNTCachedDecision;
@class SNTStoredEvent;
///
/// Logs execution and file write events to syslog
/// Abstract interface for logging execution and file write events to syslog
///
@interface SNTEventLog : NSObject
// Getter for a singleton SNTEventLog object.
// Determines which type of SNTEventLog to use based on [SNTConfigurator eventLogType].
+ (instancetype)logger;
// Methods implemented by a concrete subclass.
- (void)logDiskAppeared:(NSDictionary *)diskProperties;
- (void)logDiskDisappeared:(NSDictionary *)diskProperties;
@@ -34,7 +39,7 @@
- (void)logExit:(santa_message_t)message;
- (void)writeLog:(NSString *)log;
// Methods for storing, retrieving, and removing cached decisions.
// Methods for storing, retrieving, and removing cached decisions.
- (void)cacheDecision:(SNTCachedDecision *)cd;
- (SNTCachedDecision *)cachedDecisionForMessage:(santa_message_t)message;
- (void)forgetCachedDecisionForVnodeId:(santa_vnode_id_t)vnodeId;

View File

@@ -11,7 +11,6 @@
/// 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/santad/Logs/SNTEventLog.h"
#include <dlfcn.h>
@@ -25,6 +24,9 @@
#import "Source/santad/DataLayer/SNTRuleTable.h"
#import "Source/santad/SNTDatabaseController.h"
#import "Source/santad/Logs/SNTFileEventLog.h"
#import "Source/santad/Logs/SNTSyslogEventLog.h"
@interface SNTEventLog ()
@property NSMutableDictionary<NSNumber *, SNTCachedDecision *> *detailStore;
@property dispatch_queue_t detailStoreQueue;
@@ -120,10 +122,10 @@
if (cd.decision != SNTEventStateAllowTransitive) return;
NSDate *lastUpdate = [self.timestampResetMap objectForKey:cd.sha256];
if (!lastUpdate || -[lastUpdate timeIntervalSinceNow] > 3600) {
SNTRule *rule = [[SNTRule alloc] initWithShasum:cd.sha256
state:SNTRuleStateAllowTransitive
type:SNTRuleTypeBinary
customMsg:nil];
SNTRule *rule = [[SNTRule alloc] initWithIdentifier:cd.sha256
state:SNTRuleStateAllowTransitive
type:SNTRuleTypeBinary
customMsg:nil];
[[SNTDatabaseController ruleTable] resetTimestampForRule:rule];
[self.timestampResetMap setObject:[NSDate date] forKey:cd.sha256];
}
@@ -418,4 +420,22 @@
return [origURL path]; // this will be nil if there was an error
}
+ (instancetype)logger {
static SNTEventLog *logger;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
switch ([[SNTConfigurator configurator] eventLogType]) {
case SNTEventLogTypeSyslog: {
logger = [[SNTSyslogEventLog alloc] init];
break;
}
case SNTEventLogTypeFilelog: {
logger = [[SNTFileEventLog alloc] init];
break;
}
default: logger = nil;
}
});
return logger;
}
@end

View File

@@ -113,6 +113,11 @@
r = @"SCOPE";
logArgs = YES;
break;
case SNTEventStateAllowTeamID:
d = @"ALLOW";
r = @"TEAMID";
logArgs = YES;
break;
case SNTEventStateAllowUnknown:
d = @"ALLOW";
r = @"UNKNOWN";
@@ -130,6 +135,10 @@
d = @"DENY";
r = @"SCOPE";
break;
case SNTEventStateBlockTeamID:
d = @"DENY";
r = @"TEAMID";
break;
case SNTEventStateBlockUnknown:
d = @"DENY";
r = @"UNKNOWN";

View File

@@ -13,6 +13,7 @@
/// limitations under the License.
#import "Source/santad/SNTApplication.h"
#import "Source/santad/SNTApplicationCoreMetrics.h"
#import <DiskArbitration/DiskArbitration.h>
#import <MOLXPCConnection/MOLXPCConnection.h>
@@ -21,7 +22,9 @@
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTDropRootPrivs.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTXPCControlInterface.h"
#import "Source/common/SNTXPCMetricServiceInterface.h"
#import "Source/common/SNTXPCNotifierInterface.h"
#import "Source/common/SNTXPCUnprivilegedControlInterface.h"
#import "Source/santad/DataLayer/SNTEventTable.h"
@@ -30,8 +33,7 @@
#import "Source/santad/EventProviders/SNTDriverManager.h"
#import "Source/santad/EventProviders/SNTEndpointSecurityManager.h"
#import "Source/santad/EventProviders/SNTEventProvider.h"
#import "Source/santad/Logs/SNTFileEventLog.h"
#import "Source/santad/Logs/SNTSyslogEventLog.h"
#import "Source/santad/Logs/SNTEventLog.h"
#import "Source/santad/SNTCompilerController.h"
#import "Source/santad/SNTDaemonControlController.h"
#import "Source/santad/SNTDatabaseController.h"
@@ -42,12 +44,13 @@
@interface SNTApplication ()
@property DASessionRef diskArbSession;
@property id<SNTEventProvider> eventProvider;
@property SNTEventLog *eventLog;
@property SNTExecutionController *execController;
@property SNTCompilerController *compilerController;
@property MOLXPCConnection *controlConnection;
@property SNTNotificationQueue *notQueue;
@property pid_t syncdPID;
@property MOLXPCConnection *metricsConnection;
@property dispatch_source_t metricsTimer;
@end
@implementation SNTApplication
@@ -89,11 +92,6 @@
return nil;
}
switch ([configurator eventLogType]) {
case SNTEventLogTypeSyslog: _eventLog = [[SNTSyslogEventLog alloc] init]; break;
case SNTEventLogTypeFilelog: _eventLog = [[SNTFileEventLog alloc] init]; break;
}
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
// The filter is reset when santad disconnects from the driver.
// Add the default filters.
@@ -129,6 +127,15 @@
forKeyPath:NSStringFromSelector(@selector(blockedPathRegex))
options:bits
context:NULL];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(exportMetrics))
options:bits
context:NULL];
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(metricExportInterval))
options:bits
context:NULL];
if (![configurator enableSystemExtension]) {
[configurator addObserver:self
forKeyPath:NSStringFromSelector(@selector(enableSystemExtension))
@@ -140,8 +147,7 @@
SNTDaemonControlController *dc =
[[SNTDaemonControlController alloc] initWithEventProvider:_eventProvider
notificationQueue:self.notQueue
syncdQueue:syncdQueue
eventLog:_eventLog];
syncdQueue:syncdQueue];
_controlConnection =
[[MOLXPCConnection alloc] initServerWithName:[SNTXPCControlInterface serviceID]];
@@ -152,20 +158,22 @@
[_controlConnection resume];
// Initialize the transitive whitelisting controller object.
_compilerController = [[SNTCompilerController alloc] initWithEventProvider:_eventProvider
eventLog:_eventLog];
_compilerController = [[SNTCompilerController alloc] initWithEventProvider:_eventProvider];
// Initialize the binary checker object
_execController = [[SNTExecutionController alloc] initWithEventProvider:_eventProvider
ruleTable:ruleTable
eventTable:eventTable
notifierQueue:self.notQueue
syncdQueue:syncdQueue
eventLog:_eventLog];
syncdQueue:syncdQueue];
// Start up santactl as a daemon if a sync server exists.
[self startSyncd];
if (!_execController) return nil;
if ([configurator exportMetrics]) {
[self startMetricsPoll];
}
}
return self;
@@ -216,16 +224,16 @@
NSString *path = @(message.path);
if (!path) break;
if ([re numberOfMatchesInString:path options:0 range:NSMakeRange(0, path.length)]) {
[self->_eventLog logFileModification:message];
[[SNTEventLog logger] logFileModification:message];
}
break;
}
case ACTION_NOTIFY_EXEC: {
[self->_eventLog logAllowedExecution:message];
[[SNTEventLog logger] logAllowedExecution:message];
break;
}
case ACTION_NOTIFY_FORK: [self->_eventLog logFork:message]; break;
case ACTION_NOTIFY_EXIT: [self->_eventLog logExit:message]; break;
case ACTION_NOTIFY_FORK: [[SNTEventLog logger] logFork:message]; break;
case ACTION_NOTIFY_EXIT: [[SNTEventLog logger] logExit:message]; break;
default: LOGE(@"Received log request without a valid action: %d", message.action); break;
}
}];
@@ -247,19 +255,17 @@
}
void diskAppearedCallback(DADiskRef disk, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
[app.eventLog logDiskAppeared:props];
[[SNTEventLog logger] logDiskAppeared:props];
}
void diskDescriptionChangedCallback(DADiskRef disk, CFArrayRef keys, void *context) {
SNTApplication *app = (__bridge SNTApplication *)context;
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
if (props[@"DAVolumePath"]) [app.eventLog logDiskAppeared:props];
if (props[@"DAVolumePath"]) [[SNTEventLog logger] logDiskAppeared:props];
}
void diskDisappearedCallback(DADiskRef disk, void *context) {
@@ -267,10 +273,67 @@ void diskDisappearedCallback(DADiskRef disk, void *context) {
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
if (![props[@"DAVolumeMountable"] boolValue]) return;
[app.eventLog logDiskDisappeared:props];
[[SNTEventLog logger] logDiskDisappeared:props];
[app.eventProvider flushCacheNonRootOnly:YES];
}
// Taken from Apple's Concurrency Programming Guide.
dispatch_source_t createDispatchTimer(uint64_t interval, uint64_t leeway, dispatch_queue_t queue,
dispatch_block_t block) {
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
if (timer) {
dispatch_source_set_timer(timer, dispatch_walltime(NULL, 0), interval, leeway);
dispatch_source_set_event_handler(timer, block);
dispatch_resume(timer);
}
return timer;
}
/*
* Create a SNTMetricSet instance and start reporting essential metrics immediately to the metric
* service.
*/
- (void)startMetricsPoll {
NSUInteger interval = [[SNTConfigurator configurator] metricExportInterval];
LOGI(@"starting to export metrics every %ld seconds", interval);
void (^exportMetricsBlock)(void) = ^{
[[self.metricsConnection remoteObjectProxy]
exportForMonitoring:[[SNTMetricSet sharedInstance] export]];
};
static dispatch_once_t registerMetrics;
dispatch_once(&registerMetrics, ^{
_metricsConnection = [SNTXPCMetricServiceInterface configuredConnection];
[_metricsConnection resume];
LOGD(@"registering core metrics");
SNTRegisterCoreMetrics();
exportMetricsBlock();
});
dispatch_source_t timer = createDispatchTimer(interval * NSEC_PER_SEC, 1ull * NSEC_PER_SEC,
dispatch_get_main_queue(), exportMetricsBlock);
if (!timer) {
LOGE(@"failed to created timer for exporting metrics");
return;
}
_metricsTimer = timer;
}
- (void)stopMetricsPoll {
if (!_metricsTimer) {
LOGE(@"stopMetricsPoll called while _metricsTimer is nil");
return;
}
dispatch_source_cancel(_metricsTimer);
}
- (void)startSyncd {
if (![[SNTConfigurator configurator] syncBaseURL]) return;
[self stopSyncd];
@@ -331,6 +394,27 @@ void diskDisappearedCallback(DADiskRef disk, void *context) {
LOGI(@"The penultimate exit.");
exit(0);
}
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(exportMetrics))]) {
BOOL new = [ change[newKey] boolValue ];
BOOL old = [change[oldKey] boolValue];
if (old == NO && new == YES) {
LOGI(@"metricsExport changed NO -> YES, starting to export metrics");
[self startMetricsPoll];
} else if (old == YES && new == NO) {
LOGI(@"metricsExport changed YES -> NO, stopping export of metrics");
[self stopMetricsPoll];
}
} else if ([keyPath isEqualToString:NSStringFromSelector(@selector(metricExportInterval))]) {
// clang-format off
NSUInteger new = [ change[newKey] unsignedIntegerValue ];
NSUInteger old = [ change[oldKey] unsignedIntegerValue ];
// clang-format on
LOGI(@"MetricExportInterval changed from %ld to %ld restarting export", old, new);
[self stopMetricsPoll];
[self startMetricsPoll];
}
}

View File

@@ -0,0 +1,152 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 <EndpointSecurity/EndpointSecurity.h>
#import <Foundation/Foundation.h>
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
#import "Source/common/SNTConfigurator.h"
#import "Source/santad/SNTApplication.h"
#import "Source/santad/SNTDatabaseController.h"
#include "Source/santad/EventProviders/EndpointSecurityTestUtil.h"
@interface SNTApplicationBenchmark : XCTestCase
@property id mockSNTDatabaseController;
@property id mockConfigurator;
@end
@implementation SNTApplicationBenchmark : XCTestCase
- (void)setUp {
[super setUp];
fclose(stdout);
self.mockSNTDatabaseController = OCMClassMock([SNTDatabaseController class]);
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
OCMStub([self.mockConfigurator enableSystemExtension]).andReturn(true);
OCMStub([self.mockConfigurator enableSysxCache]).andReturn(false);
}
+ (NSArray<XCTPerformanceMetric> *)defaultPerformanceMetrics {
return @[
XCTPerformanceMetric_WallClockTime,
// Metrics visible and controllable from the XCode UI but without a symbol exposed for them:
@"com.apple.XCTPerformanceMetric_RunTime",
@"com.apple.XCTPerformanceMetric_UserTime",
@"com.apple.XCTPerformanceMetric_SystemTime",
@"com.apple.XCTPerformanceMetric_HighWaterMarkForHeapAllocations",
@"com.apple.XCTPerformanceMetric_PersistentHeapAllocations",
@"com.apple.XCTPerformanceMetric_PersistentHeapAllocationsNodes",
@"com.apple.XCTPerformanceMetric_PersistentVMAllocations",
@"com.apple.XCTPerformanceMetric_TotalHeapAllocationsKilobytes",
@"com.apple.XCTPerformanceMetric_TransientHeapAllocationsKilobytes",
@"com.apple.XCTPerformanceMetric_TransientHeapAllocationsNodes",
@"com.apple.XCTPerformanceMetric_TransientVMAllocationsKilobytes",
@"com.apple.XCTPerformanceMetric_HighWaterMarkForVMAllocations",
];
}
- (void)tearDown {
[self.mockSNTDatabaseController stopMocking];
[self.mockConfigurator stopMocking];
[super tearDown];
}
- (void)executeAndMeasure:(NSString *)binaryName testPath:(NSString *)testPath {
MockEndpointSecurity *mockES = [MockEndpointSecurity mockEndpointSecurity];
[mockES reset];
OCMStub([self.mockSNTDatabaseController databasePath]).andReturn(testPath);
SNTApplication *app = [[SNTApplication alloc] init];
[app start];
// es events will start flowing in as soon as es_subscribe is called, regardless
// of whether we're ready or not for it.
XCTestExpectation *santaInit =
[self expectationWithDescription:@"Wait for Santa to subscribe to EndpointSecurity"];
dispatch_async(dispatch_get_global_queue(QOS_CLASS_BACKGROUND, 0), ^{
while ([mockES.subscriptions[ES_EVENT_TYPE_AUTH_EXEC] isEqualTo:@NO])
;
[santaInit fulfill];
});
// Ugly hack to deflake the test and allow listenForDecisionRequests to install the correct
// decision callback.
[self waitForExpectations:@[ santaInit ] timeout:2.0];
// MeasureMetrics actually runs all of the individual events asynchronously at once.
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
void (^executeBinary)(void) = ^void(void) {
NSString *binaryPath = [NSString pathWithComponents:@[ testPath, binaryName ]];
struct stat fileStat;
lstat(binaryPath.UTF8String, &fileStat);
ESMessage *msg = [[ESMessage alloc] initWithBlock:^(ESMessage *m) {
m.binaryPath = binaryPath;
m.executable->stat = fileStat;
m.message->action_type = ES_ACTION_TYPE_AUTH;
m.message->event_type = ES_EVENT_TYPE_AUTH_EXEC;
m.message->event = (es_events_t){.exec = {.target = m.process}};
}];
__block BOOL complete = NO;
[mockES registerResponseCallback:^(ESResponse *r) {
complete = YES;
}];
[self startMeasuring];
[mockES triggerHandler:msg.message];
while (!complete)
;
[self stopMeasuring];
dispatch_semaphore_signal(sem);
};
[self measureMetrics:[SNTApplicationBenchmark defaultPerformanceMetrics]
automaticallyStartMeasuring:false
forBlock:executeBinary];
int sampleSize = 10;
for (size_t i = 0; i < sampleSize; i++) {
dispatch_semaphore_wait(sem, dispatch_time(DISPATCH_TIME_NOW, 20 * NSEC_PER_SEC));
}
}
// Microbenchmarking analysis of binary execution
- (void)testMeasureExecutionDeny {
NSString *testPath = @"santa/Source/santad/testdata/binaryrules";
NSString *fullTestPath = [NSString pathWithComponents:@[
[[[NSProcessInfo processInfo] environment] objectForKey:@"TEST_SRCDIR"], testPath
]];
[self executeAndMeasure:@"badbinary" testPath:fullTestPath];
}
- (void)testMeasureExecutionAllow {
NSString *testPath = @"santa/Source/santad/testdata/binaryrules";
NSString *fullTestPath = [NSString pathWithComponents:@[
[[[NSProcessInfo processInfo] environment] objectForKey:@"TEST_SRCDIR"], testPath
]];
[self executeAndMeasure:@"goodbinary" testPath:fullTestPath];
}
@end

View File

@@ -0,0 +1,17 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// 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
///
/// http://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/SNTMetricSet.h"
void SNTRegisterCoreMetrics();

View File

@@ -0,0 +1,142 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// 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
///
/// http://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/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTSystemInfo.h"
#import <Foundation/Foundation.h>
#include <mach/mach.h>
#include <sys/resource.h>
/**
* Register the mode metric checking the config before reporting the status.
*/
static void RegisterModeMetric(SNTMetricSet *metricSet) {
SNTMetricStringGauge *mode = [metricSet stringGaugeWithName:@"/santa/mode"
fieldNames:@[]
helpText:@"Santa's operating mode"];
// create a callback that gets the current mode
[metricSet registerCallback:^{
SNTConfigurator *config = [SNTConfigurator configurator];
switch (config.clientMode) {
case SNTClientModeLockdown: [mode set:@"lockdown" forFieldValues:@[]]; break;
case SNTClientModeMonitor: [mode set:@"monitor" forFieldValues:@[]]; break;
default:
// Should never be reached.
[mode set:@"unknown" forFieldValues:@[]];
break;
}
}];
}
/**
* Register metrics for measuring memory usage.
*/
static void RegisterMemoryAndCPUMetrics(SNTMetricSet *metricSet) {
SNTMetricInt64Gauge *vsize =
[metricSet int64GaugeWithName:@"/proc/memory/virtual_size"
fieldNames:@[]
helpText:@"The virtual memory size of this process"];
SNTMetricInt64Gauge *rsize =
[metricSet int64GaugeWithName:@"/proc/memory/resident_size"
fieldNames:@[]
helpText:@"The resident set size of this process"];
SNTMetricDoubleGauge *cpuUsage =
[metricSet doubleGaugeWithName:@"/proc/cpu_usage"
fieldNames:@[ @"mode" ] // "user" or "system"
helpText:@"CPU time consumed by this process, in seconds"];
[metricSet registerCallback:^(void) {
struct mach_task_basic_info info;
mach_msg_type_number_t size = MACH_TASK_BASIC_INFO_COUNT;
kern_return_t ret =
task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&info, &size);
if (ret != KERN_SUCCESS) {
return;
}
[vsize set:info.virtual_size forFieldValues:@[]];
[rsize set:info.resident_size forFieldValues:@[]];
// convert times to seconds
double user_time = info.user_time.seconds + (info.user_time.microseconds / 1e6);
double system_time = info.system_time.seconds + (info.system_time.microseconds / 1e6);
[cpuUsage set:user_time forFieldValues:@[ @"user" ]];
[cpuUsage set:system_time forFieldValues:@[ @"system" ]];
}];
}
static void RegisterHostnameAndUsernameLabels(SNTMetricSet *metricSet) {
NSString *hostname = [NSProcessInfo processInfo].hostName;
[metricSet addRootLabel:@"host_name" value:hostname];
[metricSet addRootLabel:@"username" value:NSUserName()];
[metricSet addRootLabel:@"job_name" value:@"santad"];
[metricSet addRootLabel:@"service_name" value:@"santa"];
// get extra root labels from configuration
SNTConfigurator *config = [SNTConfigurator configurator];
NSDictionary *extraLabels = [config extraMetricLabels];
if (extraLabels.count == 0) return;
for (NSString *key in extraLabels) {
// remove the root label if the value is empty.
if ([@"" isEqualToString:(NSString *)extraLabels[key]]) {
[metricSet removeRootLabel:key];
continue;
}
// Set or override the value.
[metricSet addRootLabel:key value:(NSString *)extraLabels[key]];
}
}
static void RegisterCommonSantaMetrics(SNTMetricSet *metricSet) {
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
// register the version
[metricSet addConstantStringWithName:@"/build/label"
helpText:@"Version of the binary"
value:version];
// register start time
[metricSet
addConstantIntegerWithName:@"/proc/birth_timestamp"
helpText:@"Start time of Santad, in microseconds since epoch"
value:(long long)([[NSDate date] timeIntervalSince1970] * 1000000)];
// Register OS version
[metricSet addConstantStringWithName:@"/proc/os/version"
helpText:@"Short operating System version"
value:[SNTSystemInfo osVersion]];
RegisterModeMetric(metricSet);
// TODO(markowsky) Register CSR status
// TODO(markowsky) Register system extension status
}
void SNTRegisterCoreMetrics() {
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
RegisterHostnameAndUsernameLabels(metricSet);
RegisterMemoryAndCPUMetrics(metricSet);
RegisterCommonSantaMetrics(metricSet);
}

View File

@@ -0,0 +1,269 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// 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
///
/// http://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/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTSystemInfo.h"
#import "Source/santad/SNTApplicationCoreMetrics.h"
#import "Source/santametricservice/Formats/SNTMetricFormatTestHelper.h"
#import <OCMock/OCMock.h>
#import <XCTest/XCTest.h>
@interface SNTApplicationCoreMetricsTest : XCTestCase
@property id mockConfigurator;
@property NSDictionary *extraMetricLabels;
@end
@implementation SNTApplicationCoreMetricsTest
- (void)setUp {
self.extraMetricLabels = @{@"service_name" : @"santa", @"corp_site" : @"roam"};
self.mockConfigurator = OCMClassMock([SNTConfigurator class]);
OCMStub([self.mockConfigurator configurator]).andReturn(self.mockConfigurator);
}
- (void)tearDown {
[self.mockConfigurator stopMocking];
}
- (NSDictionary *)fixUpDatesAndDataValuesOf:(NSDictionary *)exportedMetrics {
NSMutableDictionary *mutableMetrics =
[[SNTMetricFormatTestHelper convertDatesToFixedDateWithExportDict:exportedMetrics] mutableCopy];
// Ensure that we got data of the correct type but do not check the values as they'll change per
// call. Instead we stub out the values for testing.
NSMutableDictionary *metric = mutableMetrics[@"metrics"][@"/proc/birth_timestamp"];
XCTAssertTrue([metric[@"fields"][@""][0][@"data"] isKindOfClass:[NSNumber class]],
@"/proc/birth_timestamp data is not a number");
metric[@"fields"][@""][0][@"data"] = @1634148013203157;
mutableMetrics[@"metrics"][@"/proc/birth_timestamp"] = metric;
// Fix up CPU usage
metric = mutableMetrics[@"metrics"][@"/proc/cpu_usage"];
XCTAssertTrue([metric[@"fields"][@"mode"][0][@"data"] isKindOfClass:[@0.63002 class]],
@"/proc/cpu_usage has non-floating point data");
metric[@"fields"][@"mode"][0][@"data"] = @0.63002;
XCTAssertTrue([metric[@"fields"][@"mode"][1][@"data"] isKindOfClass:[@0.29522 class]],
@"/proc/cpu_usage has non-floating point data");
metric[@"fields"][@"mode"][1][@"data"] = @0.29522;
metric = mutableMetrics[@"metrics"][@"/proc/cpu_usage"];
// Fix up Memory (resident size)
metric = mutableMetrics[@"metrics"][@"/proc/memory/resident_size"];
XCTAssertTrue([metric[@"fields"][@""][0][@"data"] isKindOfClass:[@22097920 class]]);
metric[@"fields"][@""][0][@"data"] = @22097920;
mutableMetrics[@"metrics"][@"/proc/memory/resident_size"] = metric;
// Fix up Memory (virtual size)
metric = mutableMetrics[@"metrics"][@"/proc/memory/virtual_size"];
XCTAssertTrue([metric[@"fields"][@""][0][@"data"] isKindOfClass:[@35634683904 class]]);
metric[@"fields"][@""][0][@"data"] = @35634683904;
mutableMetrics[@"metrics"][@"/proc/memory/virtual_size"] = metric;
return mutableMetrics;
}
- (void)testRegisteringCoreMetrics {
OCMStub([self.mockConfigurator extraMetricLabels]).andReturn(self.extraMetricLabels);
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
SNTRegisterCoreMetrics();
NSString *version = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
NSString *shortOSVersion = [SNTSystemInfo osVersion];
NSISO8601DateFormatter *formatter = [[NSISO8601DateFormatter alloc] init];
if (@available(macOS 10.13, *)) {
formatter.formatOptions =
NSISO8601DateFormatWithFractionalSeconds | NSISO8601DateFormatWithInternetDateTime;
}
NSDate *fixedDate = [formatter dateFromString:@"2021-09-16T21:07:34.826Z"];
NSString *hostname = [NSProcessInfo processInfo].hostName;
NSDictionary *expected = @{
@"metrics" : @{
@"/build/label" : @{
@"description" : @"Version of the binary",
@"type" : @2,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : version,
@"last_updated" : fixedDate,
@"value" : @""
} ]
},
},
@"/proc/birth_timestamp" : @{
@"description" : @"Start time of Santad, in microseconds since epoch",
@"type" : @3,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : @1634148013203157,
@"last_updated" : fixedDate,
@"value" : @""
} ],
},
},
@"/proc/cpu_usage" : @{
@"description" : @"CPU time consumed by this process, in seconds",
@"type" : @8,
@"fields" : @{
@"mode" : @[
@{
@"created" : fixedDate,
@"data" : @0.63002,
@"last_updated" : fixedDate,
@"value" : @"user"
},
@{
@"created" : fixedDate,
@"data" : @0.29522,
@"last_updated" : fixedDate,
@"value" : @"system"
}
],
},
},
@"/proc/memory/resident_size" : @{
@"description" : @"The resident set size of this process",
@"type" : @7,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : @22097920,
@"last_updated" : fixedDate,
@"value" : @""
} ],
},
},
@"/proc/memory/virtual_size" : @{
@"description" : @"The virtual memory size of this process",
@"type" : @7,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : @35634683904,
@"last_updated" : fixedDate,
@"value" : @""
} ],
},
},
@"/proc/os/version" : @{
@"description" : @"Short operating System version",
@"type" : @2,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : shortOSVersion,
@"last_updated" : fixedDate,
@"value" : @""
} ],
},
},
@"/santa/mode" : @{
@"description" : @"Santa's operating mode",
@"type" : @6,
@"fields" : @{
@"" : @[ @{
@"created" : fixedDate,
@"data" : @"lockdown",
@"last_updated" : fixedDate,
@"value" : @""
} ],
},
},
},
@"root_labels" : @{
@"host_name" : hostname,
@"job_name" : @"santad",
@"service_name" : @"santa",
@"corp_site" : @"roam",
@"username" : [NSProcessInfo processInfo].userName
},
};
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
NSDictionary *exportedMetrics = [self fixUpDatesAndDataValuesOf:[metricSet export]];
XCTAssertNotNil(exportedMetrics);
XCTAssertEqualObjects(expected[@"root_labels"], exportedMetrics[@"root_labels"],
@"root_labels are different");
for (NSString *metricName in expected[@"metrics"]) {
NSDictionary *other = exportedMetrics[@"metrics"][metricName];
XCTAssertNotNil(other, @"exported Metrics Missing %@", metricName);
XCTAssertEqualObjects(expected[@"metrics"][metricName], other,
@"%@ does not match expected values", metricName);
}
}
// Test that setting a new value for an existing rootLabel overwrites the labels value.
//
// Becareful modifiying this test as the singleton makes this order dependent.
//
- (void)testRootLabelReplacement {
self.extraMetricLabels = @{@"host_name" : @"santa-host"};
OCMStub([self.mockConfigurator extraMetricLabels]).andReturn(self.extraMetricLabels);
SNTRegisterCoreMetrics();
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
NSDictionary *output = [metricSet export];
NSDictionary *expectedRootLabels = @{
@"host_name" : @"santa-host",
@"job_name" : @"santad",
@"service_name" : @"santa",
@"corp_site" : @"roam",
@"username" : [NSProcessInfo processInfo].userName
};
XCTAssertEqualObjects(expectedRootLabels, output[@"root_labels"],
@"failed to update host_name root label");
}
// Test that setting a rootLabel key to "" removes it from the root labels.
//
// Becareful modifiying this test as the singleton makes this order dependent.
//
- (void)testRootLabelRemoval {
self.extraMetricLabels = @{@"host_name" : @""};
OCMStub([self.mockConfigurator extraMetricLabels]).andReturn(self.extraMetricLabels);
SNTRegisterCoreMetrics();
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
NSDictionary *output = [metricSet export];
NSDictionary *expectedRootLabels = @{
@"job_name" : @"santad",
@"service_name" : @"santa",
@"corp_site" : @"roam",
@"username" : [NSProcessInfo processInfo].userName,
};
XCTAssertEqualObjects(expectedRootLabels, output[@"root_labels"],
@"failed to remove only host_name root label");
}
@end

View File

@@ -93,31 +93,16 @@
testPath, binaryName);
}
- (void)testBinaryRules {
- (void)testRules {
NSString *testPath = @"santa/Source/santad/testdata/binaryrules";
NSDictionary *testCases = @{
@"badbinary" : [NSNumber numberWithInt:ES_AUTH_RESULT_DENY],
@"goodbinary" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
@"noop" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
};
NSString *fullTestPath = [NSString pathWithComponents:@[
[[[NSProcessInfo processInfo] environment] objectForKey:@"TEST_SRCDIR"], testPath
]];
for (NSString *binary in testCases) {
[self checkBinaryExecution:binary
testPath:fullTestPath
wantResult:[testCases[binary] intValue]];
}
}
- (void)testCertRules {
NSString *testPath = @"santa/Source/santad/testdata/binaryrules";
NSDictionary *testCases = @{
@"banned_teamid" : [NSNumber numberWithInt:ES_AUTH_RESULT_DENY],
@"banned_teamid_allowed_binary" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
@"badcert" : [NSNumber numberWithInt:ES_AUTH_RESULT_DENY],
@"goodcert" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
@"noop" : [NSNumber numberWithInt:ES_AUTH_RESULT_ALLOW],
};
NSString *fullTestPath = [NSString pathWithComponents:@[
[[[NSProcessInfo processInfo] environment] objectForKey:@"TEST_SRCDIR"], testPath

View File

@@ -23,8 +23,7 @@
// Designated initializer takes a SNTEventLog instance so that we can
// call saveDecisionDetails: to create a fake cached decision for transitive
// rule creation requests that are still pending.
- (instancetype)initWithEventProvider:(SNTDriverManager *)driverManager
eventLog:(SNTEventLog *)eventLog;
- (instancetype)initWithEventProvider:(SNTDriverManager *)driverManager;
// Whenever an executable file is closed or renamed whitelist the resulting file.
// We assume that we have already determined that the writing process was a compiler.

View File

@@ -27,17 +27,14 @@
@interface SNTCompilerController ()
@property id<SNTEventProvider> eventProvider;
@property SNTEventLog *eventLog;
@end
@implementation SNTCompilerController
- (instancetype)initWithEventProvider:(id<SNTEventProvider>)eventProvider
eventLog:(SNTEventLog *)eventLog {
- (instancetype)initWithEventProvider:(id<SNTEventProvider>)eventProvider {
self = [super init];
if (self) {
_eventProvider = eventProvider;
_eventLog = eventLog;
}
return self;
}
@@ -50,11 +47,11 @@
cd.decision = SNTEventStateAllowPendingTransitive;
cd.vnodeId = message.vnode_id;
cd.sha256 = @"pending";
[self.eventLog cacheDecision:cd];
[[SNTEventLog logger] cacheDecision:cd];
}
- (void)removeFakeDecision:(santa_message_t)message {
[self.eventLog forgetCachedDecisionForVnodeId:message.vnode_id];
[[SNTEventLog logger] forgetCachedDecisionForVnodeId:message.vnode_id];
}
// Assume that this method is called only when we already know that the writing process is a
@@ -71,20 +68,20 @@
// Check if there is an existing (non-transitive) rule for this file. We leave existing rules
// alone, so that a allowlist or blocklist rule can't be overwritten by a transitive one.
SNTRuleTable *ruleTable = [SNTDatabaseController ruleTable];
SNTRule *prevRule = [ruleTable ruleForBinarySHA256:fi.SHA256 certificateSHA256:nil];
SNTRule *prevRule = [ruleTable ruleForBinarySHA256:fi.SHA256 certificateSHA256:nil teamID:nil];
if (!prevRule || prevRule.state == SNTRuleStateAllowTransitive) {
// Construct a new transitive allowlist rule for the executable.
SNTRule *rule = [[SNTRule alloc] initWithShasum:fi.SHA256
state:SNTRuleStateAllowTransitive
type:SNTRuleTypeBinary
customMsg:@""];
SNTRule *rule = [[SNTRule alloc] initWithIdentifier:fi.SHA256
state:SNTRuleStateAllowTransitive
type:SNTRuleTypeBinary
customMsg:@""];
// Add the new rule to the rules database.
NSError *err;
if (![ruleTable addRules:@[ rule ] cleanSlate:NO error:&err]) {
LOGE(@"unable to add new transitive rule to database: %@", err.localizedDescription);
} else {
[self.eventLog
[[SNTEventLog logger]
writeLog:[NSString
stringWithFormat:@"action=ALLOWLIST|pid=%d|pidversion=%d|path=%s|sha256=%@",
message.pid, message.pidversion, target, fi.SHA256]];

View File

@@ -28,6 +28,5 @@
- (instancetype)initWithEventProvider:(SNTDriverManager *)driverManager
notificationQueue:(SNTNotificationQueue *)notQueue
syncdQueue:(SNTSyncdQueue *)syncdQueue
eventLog:(SNTEventLog *)eventLog;
syncdQueue:(SNTSyncdQueue *)syncdQueue;
@end

View File

@@ -20,6 +20,7 @@
#import "Source/common/SNTCommonEnums.h"
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTMetricSet.h"
#import "Source/common/SNTRule.h"
#import "Source/common/SNTStoredEvent.h"
#import "Source/common/SNTStrengthify.h"
@@ -44,7 +45,6 @@ double watchdogRAMPeak = 0;
@interface SNTDaemonControlController ()
@property NSString *_syncXsrfToken;
@property SNTPolicyProcessor *policyProcessor;
@property SNTEventLog *eventLog;
@property id<SNTEventProvider> eventProvider;
@property SNTNotificationQueue *notQueue;
@property SNTSyncdQueue *syncdQueue;
@@ -54,8 +54,7 @@ double watchdogRAMPeak = 0;
- (instancetype)initWithEventProvider:(id<SNTEventProvider>)eventProvider
notificationQueue:(SNTNotificationQueue *)notQueue
syncdQueue:(SNTSyncdQueue *)syncdQueue
eventLog:(SNTEventLog *)eventLog {
syncdQueue:(SNTSyncdQueue *)syncdQueue {
self = [super init];
if (self) {
_policyProcessor =
@@ -63,7 +62,6 @@ double watchdogRAMPeak = 0;
_eventProvider = eventProvider;
_notQueue = notQueue;
_syncdQueue = syncdQueue;
_eventLog = eventLog;
}
return self;
}
@@ -94,10 +92,10 @@ double watchdogRAMPeak = 0;
#pragma mark Database ops
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate, int64_t compiler,
int64_t transitive))reply {
int64_t transitive, int64_t teamID))reply {
SNTRuleTable *rdb = [SNTDatabaseController ruleTable];
reply([rdb binaryRuleCount], [rdb certificateRuleCount], [rdb compilerRuleCount],
[rdb transitiveRuleCount]);
[rdb transitiveRuleCount], [rdb teamIDRuleCount]);
}
- (void)databaseRuleAddRules:(NSArray *)rules
@@ -139,9 +137,11 @@ double watchdogRAMPeak = 0;
- (void)databaseRuleForBinarySHA256:(NSString *)binarySHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTRule *))reply {
reply([[SNTDatabaseController ruleTable] ruleForBinarySHA256:binarySHA256
certificateSHA256:certificateSHA256]);
certificateSHA256:certificateSHA256
teamID:teamID]);
}
#pragma mark Decision Ops
@@ -149,10 +149,12 @@ double watchdogRAMPeak = 0;
- (void)decisionForFilePath:(NSString *)filePath
fileSHA256:(NSString *)fileSHA256
certificateSHA256:(NSString *)certificateSHA256
teamID:(NSString *)teamID
reply:(void (^)(SNTEventState))reply {
reply([self.policyProcessor decisionForFilePath:filePath
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256]
certificateSHA256:certificateSHA256
teamID:teamID]
.decision);
}
@@ -241,6 +243,18 @@ double watchdogRAMPeak = 0;
reply();
}
#pragma mark Metrics Ops
- (void)metrics:(void (^)(NSDictionary *))reply {
// If metrics are not enabled send nil back
if (![[SNTConfigurator configurator] exportMetrics]) {
reply(nil);
}
SNTMetricSet *metricSet = [SNTMetricSet sharedInstance];
reply([metricSet export]);
}
#pragma mark GUI Ops
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener {
@@ -301,7 +315,7 @@ double watchdogRAMPeak = 0;
[eventTable addStoredEvent:event];
// Log all of the generated bundle events.
[self.eventLog logBundleHashingEvents:events];
[[SNTEventLog logger] logBundleHashingEvents:events];
WEAKIFY(self);

View File

@@ -40,8 +40,7 @@
ruleTable:(SNTRuleTable *)ruleTable
eventTable:(SNTEventTable *)eventTable
notifierQueue:(SNTNotificationQueue *)notifierQueue
syncdQueue:(SNTSyncdQueue *)syncdQueue
eventLog:(SNTEventLog *)eventLog;
syncdQueue:(SNTSyncdQueue *)syncdQueue;
///
/// Handles the logic of deciding whether to allow the binary to run or not, sends the response to

View File

@@ -46,7 +46,6 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
@interface SNTExecutionController ()
@property id<SNTEventProvider> eventProvider;
@property SNTEventLog *eventLog;
@property SNTEventTable *eventTable;
@property SNTNotificationQueue *notifierQueue;
@property SNTPolicyProcessor *policyProcessor;
@@ -64,8 +63,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
ruleTable:(SNTRuleTable *)ruleTable
eventTable:(SNTEventTable *)eventTable
notifierQueue:(SNTNotificationQueue *)notifierQueue
syncdQueue:(SNTSyncdQueue *)syncdQueue
eventLog:(SNTEventLog *)eventLog {
syncdQueue:(SNTSyncdQueue *)syncdQueue {
self = [super init];
if (self) {
_eventProvider = eventProvider;
@@ -73,7 +71,6 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
_eventTable = eventTable;
_notifierQueue = notifierQueue;
_syncdQueue = syncdQueue;
_eventLog = eventLog;
_policyProcessor = [[SNTPolicyProcessor alloc] initWithRuleTable:_ruleTable];
_eventQueue = dispatch_queue_create("com.google.santad.event_upload", DISPATCH_QUEUE_SERIAL);
@@ -94,6 +91,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
[self.eventProvider postAction:ACTION_RESPOND_ALLOW forMessage:message];
return;
}
NSError *fileInfoError;
SNTFileInfo *binInfo = [[SNTFileInfo alloc] initWithPath:@(message.path) error:&fileInfoError];
if (unlikely(!binInfo)) {
@@ -127,7 +125,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
// ACTION_NOTIFY_EXEC message related to the transitive rule is received.
NSString *ttyPath;
if (action == ACTION_RESPOND_ALLOW) {
[_eventLog cacheDecision:cd];
[[SNTEventLog logger] cacheDecision:cd];
} else {
ttyPath = [self ttyPathForPID:message.ppid];
}
@@ -144,7 +142,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
// Log to database if necessary.
if (cd.decision != SNTEventStateAllowBinary && cd.decision != SNTEventStateAllowCompiler &&
cd.decision != SNTEventStateAllowTransitive && cd.decision != SNTEventStateAllowCertificate &&
cd.decision != SNTEventStateAllowScope) {
cd.decision != SNTEventStateAllowTeamID && cd.decision != SNTEventStateAllowScope) {
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
se.occurrenceDate = [[NSDate alloc] init];
se.fileSHA256 = cd.sha256;
@@ -187,7 +185,7 @@ static size_t kLargeBinarySize = 30 * 1024 * 1024;
// If binary was blocked, do the needful
if (action != ACTION_RESPOND_ALLOW && action != ACTION_RESPOND_ALLOW_COMPILER) {
[_eventLog logDeniedExecution:cd withMessage:message];
[[SNTEventLog logger] logDeniedExecution:cd withMessage:message];
if ([[SNTConfigurator configurator] enableBundles] && binInfo.bundle) {
// If the binary is part of a bundle, find and hash all the related binaries in the bundle.

View File

@@ -69,8 +69,7 @@
ruleTable:self.mockRuleDatabase
eventTable:self.mockEventDatabase
notifierQueue:nil
syncdQueue:nil
eventLog:nil];
syncdQueue:nil];
}
/// Return a pre-configured santa_message_ t for testing with.
@@ -87,16 +86,6 @@
return (santa_vnode_id_t){.fsid = 1234, .fileid = 5678};
}
- (void)tearDown {
[self.mockFileInfo stopMocking];
[self.mockCodesignChecker stopMocking];
[self.mockDriverManager stopMocking];
[self.mockRuleDatabase stopMocking];
[self.mockEventDatabase stopMocking];
[super tearDown];
}
- (void)testBinaryAllowRule {
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
@@ -104,7 +93,8 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllow;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
@@ -118,7 +108,8 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateBlock;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
@@ -135,7 +126,8 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllow;
rule.type = SNTRuleTypeCertificate;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a"]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a" teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
@@ -152,11 +144,15 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateBlock;
rule.type = SNTRuleTypeCertificate;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a"]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:nil certificateSHA256:@"a" teamID:nil])
.andReturn(rule);
OCMExpect([self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
}
- (void)testBinaryAllowCompilerRule {
@@ -167,7 +163,8 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowCompiler;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
@@ -183,7 +180,8 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowCompiler;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
@@ -198,7 +196,8 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowTransitive;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
[self.sut validateBinaryWithMessage:[self getMessage]];
@@ -214,11 +213,15 @@
SNTRule *rule = [[SNTRule alloc] init];
rule.state = SNTRuleStateAllowTransitive;
rule.type = SNTRuleTypeBinary;
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil]).andReturn(rule);
OCMStub([self.mockRuleDatabase ruleForBinarySHA256:@"a" certificateSHA256:nil teamID:nil])
.andReturn(rule);
OCMExpect([self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
}
- (void)testDefaultDecision {
@@ -226,17 +229,19 @@
OCMStub([self.mockFileInfo SHA256]).andReturn(@"a");
OCMExpect([self.mockConfigurator clientMode]).andReturn(SNTClientModeMonitor);
OCMExpect([self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
OCMExpect([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
}
- (void)testOutOfScope {
OCMStub([self.mockFileInfo isMachO]).andReturn(NO);
OCMStub([self.mockConfigurator clientMode]).andReturn(SNTClientModeLockdown);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_ALLOW forMessage:[self getMessage]]);
@@ -250,9 +255,10 @@
- (void)testPageZero {
OCMStub([self.mockFileInfo isMachO]).andReturn(YES);
OCMStub([self.mockFileInfo isMissingPageZero]).andReturn(YES);
OCMExpect([self.mockEventDatabase addStoredEvent:OCMOCK_ANY]);
[self.sut validateBinaryWithMessage:[self getMessage]];
OCMVerify([self.mockDriverManager postAction:ACTION_RESPOND_DENY forMessage:[self getMessage]]);
OCMVerifyAllWithDelay(self.mockEventDatabase, 1);
}
@end

View File

@@ -44,7 +44,8 @@
///
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
fileSHA256:(nullable NSString *)fileSHA256
certificateSHA256:(nullable NSString *)certificateSHA256;
certificateSHA256:(nullable NSString *)certificateSHA256
teamID:(nullable NSString *)teamID;
/// Convenience initializer with nil hashes for both the file and certificate.
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo;
@@ -57,6 +58,7 @@
///
- (nonnull SNTCachedDecision *)decisionForFilePath:(nonnull NSString *)filePath
fileSHA256:(nullable NSString *)fileSHA256
certificateSHA256:(nullable NSString *)certificateSHA256;
certificateSHA256:(nullable NSString *)certificateSHA256
teamID:(nullable NSString *)teamID;
@end

View File

@@ -40,9 +40,11 @@
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo
fileSHA256:(nullable NSString *)fileSHA256
certificateSHA256:(nullable NSString *)certificateSHA256 {
certificateSHA256:(nullable NSString *)certificateSHA256
teamID:(nullable NSString *)teamID {
SNTCachedDecision *cd = [[SNTCachedDecision alloc] init];
cd.sha256 = fileSHA256 ?: fileInfo.SHA256;
cd.teamID = teamID;
// If the binary is a critical system binary, don't check its signature.
// The binary was validated at startup when the rule table was initialized.
@@ -64,11 +66,15 @@
cd.certSHA256 = csInfo.leafCertificate.SHA256;
cd.certCommonName = csInfo.leafCertificate.commonName;
cd.certChain = csInfo.certificates;
cd.teamID = teamID ?: [csInfo.signingInformation valueForKey:@"teamid"];
teamID = cd.teamID;
}
}
cd.quarantineURL = fileInfo.quarantineDataURL;
SNTRule *rule = [self.ruleTable ruleForBinarySHA256:cd.sha256 certificateSHA256:cd.certSHA256];
SNTRule *rule = [self.ruleTable ruleForBinarySHA256:cd.sha256
certificateSHA256:cd.certSHA256
teamID:teamID];
if (rule) {
switch (rule.type) {
case SNTRuleTypeBinary:
@@ -115,6 +121,20 @@
default: break;
}
break;
case SNTRuleTypeTeamID:
switch (rule.state) {
case SNTRuleStateAllow: cd.decision = SNTEventStateAllowTeamID; return cd;
case SNTRuleStateSilentBlock:
cd.silentBlock = YES;
// intentional fallthrough
case SNTRuleStateBlock:
cd.customMsg = rule.customMsg;
cd.decision = SNTEventStateBlockTeamID;
return cd;
default: break;
}
break;
default: break;
}
}
@@ -149,19 +169,22 @@
}
- (nonnull SNTCachedDecision *)decisionForFileInfo:(nonnull SNTFileInfo *)fileInfo {
return [self decisionForFileInfo:fileInfo fileSHA256:nil certificateSHA256:nil];
return [self decisionForFileInfo:fileInfo fileSHA256:nil certificateSHA256:nil teamID:nil];
}
// Used by `$ santactl fileinfo`.
- (nonnull SNTCachedDecision *)decisionForFilePath:(nonnull NSString *)filePath
fileSHA256:(nullable NSString *)fileSHA256
certificateSHA256:(nullable NSString *)certificateSHA256 {
certificateSHA256:(nullable NSString *)certificateSHA256
teamID:(nullable NSString *)teamID {
SNTFileInfo *fileInfo;
NSError *error;
fileInfo = [[SNTFileInfo alloc] initWithPath:filePath error:&error];
if (!fileInfo) LOGW(@"Failed to read file %@: %@", filePath, error.localizedDescription);
return [self decisionForFileInfo:fileInfo
fileSHA256:fileSHA256
certificateSHA256:certificateSHA256];
certificateSHA256:certificateSHA256
teamID:teamID];
}
///

View File

@@ -2,11 +2,15 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.application-identifier</key>
<string>$(TeamIdentifierPrefix)com.google.santa.daemon</string>
<key>com.apple.developer.team-identifier</key>
<string>EQHXZ8M8AV</string>
<key>com.apple.application-identifier</key>
<string>EQHXZ8M8AV.com.google.santa.daemon</string>
<key>com.apple.developer.endpoint-security.client</key>
<true/>
<key>com.apple.developer.team-identifier</key>
<string>EQHXZ8M8AV</string>
<key>keychain-access-groups</key>
<array>
<string>EQHXZ8M8AV.com.google.santa.daemon</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,5 @@
#include <stdio.h>
int main(int argc, char* argv[]) {
printf("This binary should fail to execute because of its banned team ID!");
return 0;
}

Binary file not shown.

View File

@@ -0,0 +1,8 @@
#include <stdio.h>
int main(int argc, char* argv[]) {
printf(
"Hello world, this binary's execution is okay even though its teamID "
"isn't");
return 0;
}

Binary file not shown.

Binary file not shown.

View File

@@ -14,11 +14,14 @@ objc_library(
],
deps = [
"//Source/common:SNTConfigurator",
"//Source/common:SNTDropRootPrivs",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
"//Source/common:SNTXPCMetricServiceInterface",
"//Source/santametricservice/Formats:SNTMetricMonarchJSONFormat",
"//Source/santametricservice/Formats:SNTMetricRawJSONFormat",
"//Source/santametricservice/Writers:SNTMetricFileWriter",
"//Source/santametricservice/Writers:SNTMetricHTTPWriter",
"@MOLCodesignChecker",
"@MOLXPCConnection",
],
@@ -27,6 +30,7 @@ objc_library(
santa_unit_test(
name = "SNTMetricServiceTest",
srcs = ["SNTMetricServiceTest.m"],
structured_resources = ["//Source/santametricservice/Formats:testdata"],
deps = [
":SNTMetricServiceLib",
"//Source/santametricservice/Formats:SNTMetricFormatTestHelper",
@@ -38,8 +42,8 @@ test_suite(
name = "unit_tests",
tests = [
":SNTMetricServiceTest",
"//Source/santametricservice/Formats:SNTMetricRawJSONFormatTest",
"//Source/santametricservice/Writers:SNTMetricFileWriterTest",
"//Source/santametricservice/Formats:format_tests",
"//Source/santametricservice/Writers:writer_tests",
],
)
@@ -51,9 +55,15 @@ macos_command_line_application(
"--force",
"--options library,kill,runtime",
],
provisioning_profile = select({
"//:ci_build": None,
"//conditions:default": "Santa_Dev.provisionprofile",
}),
infoplists = ["Info.plist"],
minimum_os_version = "10.15",
version = "//:version",
visibility = ["//:santa_package_group"],
deps = [":SNTMetricServiceLib"],
deps = [
":SNTMetricServiceLib",
],
)

View File

@@ -33,6 +33,20 @@ objc_library(
],
)
objc_library(
name = "SNTMetricMonarchJSONFormat",
srcs = [
"SNTMetricFormat.h",
"SNTMetricMonarchJSONFormat.h",
"SNTMetricMonarchJSONFormat.m",
],
deps = [
":SNTMetricFormat",
"//Source/common:SNTLogging",
"//Source/common:SNTMetricSet",
],
)
santa_unit_test(
name = "SNTMetricRawJSONFormatTest",
srcs = [
@@ -45,9 +59,27 @@ santa_unit_test(
],
)
santa_unit_test(
name = "SNTMetricMonarchJSONFormatTest",
srcs = [
"SNTMetricMonarchJSONFormatTest.m",
],
structured_resources = [":testdata"],
deps = [
":SNTMetricFormatTestHelper",
":SNTMetricMonarchJSONFormat",
],
)
filegroup(
name = "testdata",
srcs = glob(["testdata/**"]),
)
test_suite(
name = "format_tests",
tests = [
":SNTMetricMonarchJSONFormatTest",
":SNTMetricRawJSONFormatTest",
],
)

View File

@@ -15,5 +15,6 @@
#import <Foundation/Foundation.h>
@interface SNTMetricFormatTestHelper : NSObject
+ (NSDictionary *)convertDatesToFixedDateWithExportDict:(NSDictionary *)exportDict;
+ (NSDictionary *)createValidMetricsDictionary;
@end

View File

@@ -19,8 +19,21 @@
@implementation SNTMetricFormatTestHelper
+ (NSDictionary *)convertDatesToFixedDateWithExportDict:(NSMutableDictionary *)exportDict {
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
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;
}
NSDate *fixedDate = [formatter dateFromString:@"2021-09-16T21:07:34.826Z"];
for (NSString *metricName in exportDict[@"metrics"]) {
@@ -45,26 +58,26 @@
// Add constants
[metricSet addConstantStringWithName:@"/build/label"
helpText:@"Software version running."
helpText:@"Software version running"
value:@"20210809.0.1"];
[metricSet addConstantBooleanWithName:@"/santa/using_endpoint_security_framework"
helpText:@"Is santad using the endpoint security framework."
helpText:@"Is santad using the endpoint security framework"
value:YES];
[metricSet addConstantIntegerWithName:@"/proc/birth_timestamp"
helpText:@"Start time of this LogDumper instance, in microseconds "
@"since epoch"
value:(long long)(0x12345668910)];
[metricSet
addConstantIntegerWithName:@"/proc/birth_timestamp"
helpText:@"Start time of this santad instance, in microseconds since epoch"
value:(long long)(0x12345668910)];
// Add Metrics
SNTMetricCounter *c = [metricSet counterWithName:@"/santa/events"
fieldNames:@[ @"rule_type" ]
helpText:@"Count of events on the host"];
helpText:@"Count of process exec events on the host"];
[c incrementForFieldValues:@[ @"binary" ]];
[c incrementBy:2 forFieldValues:@[ @"certificate" ]];
SNTMetricInt64Gauge *g = [metricSet int64GaugeWithName:@"/santa/rules"
fieldNames:@[ @"rule_type" ]
helpText:@"Number of rules."];
helpText:@"Number of rules"];
[g set:1 forFieldValues:@[ @"binary" ]];
[g set:3 forFieldValues:@[ @"certificate" ]];
@@ -73,12 +86,12 @@
SNTMetricInt64Gauge *virtualMemoryGauge =
[metricSet int64GaugeWithName:@"/proc/memory/virtual_size"
fieldNames:@[]
helpText:@"The virtual memory size of this process."];
helpText:@"The virtual memory size of this process"];
SNTMetricInt64Gauge *residentMemoryGauge =
[metricSet int64GaugeWithName:@"/proc/memory/resident_size"
fieldNames:@[]
helpText:@"The resident set siz of this process."];
helpText:@"The resident set size of this process"];
[metricSet registerCallback:^(void) {
[virtualMemoryGauge set:987654321 forFieldValues:@[]];

View File

@@ -0,0 +1,20 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 "Source/santametricservice/Formats/SNTMetricFormat.h"
@interface SNTMetricMonarchJSONFormat : NSObject <SNTMetricFormat>
@end

View File

@@ -0,0 +1,226 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// 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
///
/// http://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/santametricservice/Formats/SNTMetricMonarchJSONFormat.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTMetricSet.h"
const NSString *kMetricsCollection = @"metricsCollection";
const NSString *kMetricsDataSet = @"metricsDataSet";
const NSString *kMetricName = @"metricName";
const NSString *kStreamKind = @"streamKind";
const NSString *kValueType = @"valueType";
const NSString *kDescription = @"description";
const NSString *kData = @"data";
const NSString *kFieldDescriptor = @"fieldDescriptor";
const NSString *kBoolValue = @"boolValue";
const NSString *kBoolValueType = @"BOOL";
const NSString *kInt64Value = @"int64Value";
const NSString *kInt64ValueType = @"INT64";
const NSString *kStringValue = @"stringValue";
const NSString *kStringValueType = @"STRING";
const NSString *kName = @"name";
const NSString *kStartTimestamp = @"startTimestamp";
const NSString *kEndTimestamp = @"endTimestamp";
const NSString *kRootLabels = @"rootLabels";
const NSString *kKey = @"key";
@implementation SNTMetricMonarchJSONFormat {
NSISO8601DateFormatter *_dateFormatter;
}
- (instancetype)init {
self = [super init];
if (self) {
_dateFormatter = [[NSISO8601DateFormatter alloc] init];
_dateFormatter.formatOptions = NSISO8601DateFormatWithInternetDateTime;
if (@available(macOS 10.13, *)) {
_dateFormatter.formatOptions |= NSISO8601DateFormatWithFractionalSeconds;
}
}
return self;
}
- (void)encodeValueAndStreamKindFor:(NSString *)metricName
withMetric:(NSDictionary *)metric
into:(NSMutableDictionary *)monarchMetric {
if (!metric[@"type"]) {
LOGE(@"metric type not supposed to be nil for %@", metricName);
return;
}
NSNumber *type = metric[@"type"];
if (![type isKindOfClass:[NSNumber class]]) {
LOGE(@"%@ [@\"type\"] is not a number", metricName);
return;
}
switch ((SNTMetricType)[type intValue]) {
case SNTMetricTypeConstantBool: monarchMetric[kValueType] = kBoolValueType; break;
case SNTMetricTypeConstantString: monarchMetric[kValueType] = kStringValueType; break;
case SNTMetricTypeConstantInt64: monarchMetric[kValueType] = kInt64ValueType; break;
case SNTMetricTypeConstantDouble: monarchMetric[kValueType] = @"DOUBLE"; break;
case SNTMetricTypeGaugeBool:
monarchMetric[kStreamKind] = @"GAUGE";
monarchMetric[kValueType] = kBoolValueType;
break;
case SNTMetricTypeGaugeString:
monarchMetric[kStreamKind] = @"GAUGE";
monarchMetric[kValueType] = kStringValueType;
break;
case SNTMetricTypeGaugeInt64:
monarchMetric[kStreamKind] = @"GAUGE";
monarchMetric[kValueType] = kInt64ValueType;
break;
case SNTMetricTypeGaugeDouble:
monarchMetric[kStreamKind] = @"GAUGE";
monarchMetric[kValueType] = @"DOUBLE";
break;
case SNTMetricTypeCounter:
monarchMetric[kStreamKind] = @"CUMULATIVE";
monarchMetric[kValueType] = kInt64ValueType;
break;
default:
LOGE(@"encountered unknown SNTMetricType - %ld for %@", (SNTMetricType)metric[@"type"],
metricName);
break;
}
}
- (NSArray<NSDictionary *> *)encodeDataForMetric:(NSDictionary *)metric {
NSMutableArray<NSDictionary *> *monarchMetricData = [[NSMutableArray alloc] init];
for (NSString *fieldName in metric[@"fields"]) {
for (NSDictionary *entry in metric[@"fields"][fieldName]) {
NSMutableDictionary *monarchDataEntry = [[NSMutableDictionary alloc] init];
if (![fieldName isEqualToString:@""]) {
monarchDataEntry[@"field"] = @[ @{kName : fieldName, kStringValue : entry[@"value"]} ];
}
monarchDataEntry[kStartTimestamp] = [self->_dateFormatter stringFromDate:entry[@"created"]];
monarchDataEntry[kEndTimestamp] =
[self->_dateFormatter stringFromDate:entry[@"last_updated"]];
if (!metric[@"type"]) {
LOGE(@"metric type is nil");
continue;
}
NSNumber *type = metric[@"type"];
switch ((SNTMetricType)[type intValue]) {
case SNTMetricTypeConstantBool:
case SNTMetricTypeGaugeBool: monarchDataEntry[kBoolValue] = entry[@"data"]; break;
case SNTMetricTypeConstantInt64:
case SNTMetricTypeGaugeInt64:
case SNTMetricTypeCounter: monarchDataEntry[kInt64Value] = entry[@"data"]; break;
case SNTMetricTypeConstantDouble:
case SNTMetricTypeGaugeDouble: monarchDataEntry[@"doubleValue"] = entry[@"data"]; break;
case SNTMetricTypeConstantString:
case SNTMetricTypeGaugeString: monarchDataEntry[kStringValue] = entry[@"data"]; break;
default: LOGE(@"encountered unknown SNTMetricType %ld", [type longValue]); break;
}
[monarchMetricData addObject:monarchDataEntry];
}
}
return monarchMetricData;
}
/*
* Translates SNTMetricSet fields to monarch's expected format. In this implementation only string
* type fields are supported.
*/
- (NSArray<NSDictionary *> *)encodeFieldsFor:(NSDictionary *)metric {
NSMutableArray<NSDictionary *> *monarchFields = [[NSMutableArray alloc] init];
for (NSString *fieldName in metric[@"fields"]) {
if (![fieldName isEqualToString:@""]) {
[monarchFields addObject:@{kName : fieldName, @"fieldType" : kStringValueType}];
}
}
return monarchFields;
}
/**
* formatMetric translates the SNTMetricSet metric entries into those consumable by Monarch.
**/
- (NSDictionary *)formatMetric:(NSString *)name withMetric:(NSDictionary *)metric {
NSMutableDictionary *monarchMetric = [[NSMutableDictionary alloc] init];
monarchMetric[kMetricName] = name;
if (metric[kDescription]) {
monarchMetric[kDescription] = metric[kDescription];
}
NSArray<NSDictionary *> *fieldDescriptorEntries = [self encodeFieldsFor:metric];
if (fieldDescriptorEntries.count > 0) {
monarchMetric[kFieldDescriptor] = [self encodeFieldsFor:metric];
}
[self encodeValueAndStreamKindFor:name withMetric:metric into:monarchMetric];
monarchMetric[@"data"] = [self encodeDataForMetric:metric];
return monarchMetric;
}
/**
* Normalizes the metrics dictionary for exporting to JSON
**/
- (NSDictionary *)normalize:(NSDictionary *)metrics {
NSMutableArray<NSDictionary *> *monarchMetrics = [[NSMutableArray alloc] init];
for (NSString *metricName in metrics[@"metrics"]) {
[monarchMetrics addObject:[self formatMetric:metricName
withMetric:metrics[@"metrics"][metricName]]];
}
NSMutableArray<NSDictionary *> *rootLabels = [[NSMutableArray alloc] init];
for (NSString *keyName in metrics[@"root_labels"]) {
[rootLabels addObject:@{kKey : keyName, kStringValue : metrics[@"root_labels"][keyName]}];
}
return @{kMetricsCollection : @[ @{kMetricsDataSet : monarchMetrics, kRootLabels : rootLabels} ]};
}
/*
* Convert normalizes and converts the metrics dictionary to a JSON
* object consumable by parts of Google Monarch's tooling.
*
* @param metrics an NSDictionary exported by the SNTMetricSet
* @param error a pointer to an NSError to allow errors to bubble up.
*
* Returns an NSArray containing one entry of all metrics serialized to JSON or
* nil on error.
*/
- (NSArray<NSData *> *)convert:(NSDictionary *)metrics error:(NSError **)err {
NSDictionary *normalizedMetrics = [self normalize:metrics];
NSData *json = [NSJSONSerialization dataWithJSONObject:normalizedMetrics
options:NSJSONWritingPrettyPrinted
error:err];
if (json == nil || (err != nil && *err != nil)) {
return nil;
}
return @[ json ];
}
@end

View File

@@ -0,0 +1,50 @@
#import <XCTest/XCTest.h>
#import "Source/santametricservice/Formats/SNTMetricFormatTestHelper.h"
#import "Source/santametricservice/Formats/SNTMetricMonarchJSONFormat.h"
@interface SNTMetricMonarchJSONFormatTest : XCTestCase
@end
@implementation SNTMetricMonarchJSONFormatTest
- (void)testMetricsConversionToJSON {
NSDictionary *validMetricsDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
SNTMetricMonarchJSONFormat *formatter = [[SNTMetricMonarchJSONFormat alloc] init];
NSError *err = nil;
NSArray<NSData *> *output = [formatter convert:validMetricsDict error:&err];
XCTAssertEqual(1, output.count);
XCTAssertNotNil(output[0]);
XCTAssertNil(err);
NSDictionary *jsonDict = [NSJSONSerialization JSONObjectWithData:output[0]
options:NSJSONReadingAllowFragments
error:&err];
XCTAssertNotNil(jsonDict);
NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath];
path = [path stringByAppendingPathComponent:@"testdata/json/monarch.json"];
NSData *goldenFileData = [NSData dataWithContentsOfFile:path];
XCTAssertNotNil(goldenFileData, @"unable to open / read golden file");
NSDictionary *expectedJSONDict =
[NSJSONSerialization JSONObjectWithData:goldenFileData
options:NSJSONReadingAllowFragments
error:&err];
XCTAssertNotNil(expectedJSONDict);
XCTAssertEqualObjects(expectedJSONDict, jsonDict, @"generated JSON does not match golden file.");
}
- (void)testPassingANilOrNullErrorDoesNotCrash {
SNTMetricMonarchJSONFormat *formatter = [[SNTMetricMonarchJSONFormat alloc] init];
NSDictionary *validMetricsDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
NSArray<NSData *> *output = [formatter convert:validMetricsDict error:nil];
output = [formatter convert:validMetricsDict error:NULL];
}
@end

View File

@@ -23,7 +23,8 @@
self = [super init];
if (self) {
_dateFormatter = [[NSDateFormatter alloc] init];
[_dateFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
_dateFormatter.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
_dateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
}
return self;
}
@@ -81,7 +82,7 @@
if (![NSJSONSerialization isValidJSONObject:normalizedMetrics]) {
if (err != nil) {
*err = [[NSError alloc]
initWithDomain:@"SNTMetricRawJSONFileWriter"
initWithDomain:@"com.google.santa.metricservice.formats.rawjson"
code:EINVAL
userInfo:@{
NSLocalizedDescriptionKey : @"unable to convert metrics to JSON: invalid metrics"

View File

@@ -0,0 +1,152 @@
{
"metricsCollection" : [
{
"rootLabels" : [
{
"key" : "hostname",
"stringValue" : "testHost"
},
{
"key" : "username",
"stringValue" : "testUser"
}
],
"metricsDataSet" : [
{
"metricName" : "/santa/rules",
"description" : "Number of rules",
"valueType" : "INT64",
"fieldDescriptor" : [
{
"name" : "rule_type",
"fieldType" : "STRING"
}
],
"streamKind" : "GAUGE",
"data" : [
{
"int64Value" : 1,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"field" : [
{
"name" : "rule_type",
"stringValue" : "binary"
}
],
"startTimestamp" : "2021-09-16T21:07:34.826Z"
},
{
"int64Value" : 3,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"field" : [
{
"name" : "rule_type",
"stringValue" : "certificate"
}
],
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
]
},
{
"valueType" : "INT64",
"streamKind" : "GAUGE",
"data" : [
{
"int64Value" : 123456789,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
],
"metricName" : "/proc/memory/resident_size",
"description" : "The resident set size of this process"
},
{
"valueType" : "INT64",
"fieldDescriptor" : [
{
"name" : "rule_type",
"fieldType" : "STRING"
}
],
"streamKind" : "CUMULATIVE",
"data" : [
{
"int64Value" : 1,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"field" : [
{
"name" : "rule_type",
"stringValue" : "binary"
}
],
"startTimestamp" : "2021-09-16T21:07:34.826Z"
},
{
"int64Value" : 2,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"field" : [
{
"name" : "rule_type",
"stringValue" : "certificate"
}
],
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
],
"metricName" : "/santa/events",
"description" : "Count of process exec events on the host"
},
{
"metricName" : "/santa/using_endpoint_security_framework",
"description" : "Is santad using the endpoint security framework",
"valueType" : "BOOL",
"data" : [
{
"boolValue" : true,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
]
},
{
"metricName" : "/proc/birth_timestamp",
"description" : "Start time of this santad instance, in microseconds since epoch",
"valueType" : "INT64",
"data" : [
{
"int64Value" : 1250999830800,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
]
},
{
"valueType" : "INT64",
"streamKind" : "GAUGE",
"data" : [
{
"int64Value" : 987654321,
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
],
"metricName" : "/proc/memory/virtual_size",
"description" : "The virtual memory size of this process"
},
{
"metricName" : "/build/label",
"valueType" : "STRING",
"description" : "Software version running",
"data" : [
{
"stringValue" : "20210809.0.1",
"endTimestamp" : "2021-09-16T21:07:34.826Z",
"startTimestamp" : "2021-09-16T21:07:34.826Z"
}
]
}
]
}
]
}

View File

@@ -2,6 +2,7 @@
"metrics" : {
"/santa/rules" : {
"type" : 7,
"description" : "Number of rules",
"fields" : {
"rule_type" : [
{
@@ -21,6 +22,7 @@
},
"/santa/events" : {
"type" : 9,
"description" : "Count of process exec events on the host",
"fields" : {
"rule_type" : [
{
@@ -40,6 +42,7 @@
},
"/proc/memory/resident_size" : {
"type" : 7,
"description" : "The resident set size of this process",
"fields" : {
"" : [
{
@@ -53,6 +56,7 @@
},
"/build/label" : {
"type" : 2,
"description" : "Software version running",
"fields" : {
"" : [
{
@@ -66,6 +70,7 @@
},
"/proc/birth_timestamp" : {
"type" : 3,
"description" : "Start time of this santad instance, in microseconds since epoch",
"fields" : {
"" : [
{
@@ -79,6 +84,7 @@
},
"/proc/memory/virtual_size" : {
"type" : 7,
"description" : "The virtual memory size of this process",
"fields" : {
"" : [
{
@@ -92,6 +98,7 @@
},
"/santa/using_endpoint_security_framework" : {
"type" : 1,
"description" : "Is santad using the endpoint security framework",
"fields" : {
"" : [
{

View File

@@ -19,8 +19,10 @@
#import "Source/common/SNTLogging.h"
#import "SNTMetricService.h"
#import "Source/santametricservice/Formats/SNTMetricMonarchJSONFormat.h"
#import "Source/santametricservice/Formats/SNTMetricRawJSONFormat.h"
#import "Source/santametricservice/Writers/SNTMetricFileWriter.h"
#import "Source/santametricservice/Writers/SNTMetricHTTPWriter.h"
@interface SNTMetricService ()
@property MOLXPCConnection *notifierConnection;
@@ -31,6 +33,7 @@
@implementation SNTMetricService {
@private
SNTMetricRawJSONFormat *rawJSONFormatter;
SNTMetricMonarchJSONFormat *monarchJSONFormatter;
NSDictionary *metricWriters;
}
@@ -38,7 +41,12 @@
self = [super init];
if (self) {
rawJSONFormatter = [[SNTMetricRawJSONFormat alloc] init];
metricWriters = @{@"file" : [[SNTMetricFileWriter alloc] init]};
monarchJSONFormatter = [[SNTMetricMonarchJSONFormat alloc] init];
metricWriters = @{
@"file" : [[SNTMetricFileWriter alloc] init],
@"http" : [[SNTMetricHTTPWriter alloc] init],
};
_queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
}
@@ -70,6 +78,8 @@
error:(NSError **)err {
switch (format) {
case SNTMetricFormatTypeRawJSON: return [self->rawJSONFormatter convert:metrics error:err];
case SNTMetricFormatTypeMonarchJSON:
return [self->monarchJSONFormatter convert:metrics error:err];
default: return nil;
}
}
@@ -84,6 +94,7 @@
SNTConfigurator *config = [SNTConfigurator configurator];
if (![config exportMetrics]) {
LOGD(@"received metrics message while not configured to export metrics.");
return;
}

View File

@@ -5,6 +5,7 @@
#import "Source/common/SNTConfigurator.h"
#import "Source/common/SNTMetricSet.h"
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#import <OCMock/OCMock.h>
#import "Source/santametricservice/Formats/SNTMetricFormatTestHelper.h"
@@ -16,6 +17,9 @@ NSDictionary *validMetricsDict = nil;
@property id mockConfigurator;
@property NSString *tempDir;
@property NSURL *jsonURL;
@property id mockSession;
@property id mockSessionDataTask;
@property id mockMOLAuthenticatingURLSession;
@end
@implementation SNTMetricServiceTest
@@ -112,4 +116,98 @@ NSDictionary *validMetricsDict = nil;
XCTAssertEqualObjects(validMetricsDict, parsedJSONData, @"invalid JSON created");
}
- (void)testWritingJSONOverHTTP {
NSURL *url = [NSURL URLWithString:@"http://localhost:9444"];
OCMStub([self.mockConfigurator exportMetrics]).andReturn(YES);
OCMStub([self.mockConfigurator metricFormat]).andReturn(SNTMetricFormatTypeRawJSON);
OCMStub([self.mockConfigurator metricURL]).andReturn(url);
self.mockSession = OCMClassMock([NSURLSession class]);
self.mockSessionDataTask = OCMClassMock([NSURLSessionDataTask class]);
self.mockMOLAuthenticatingURLSession = OCMClassMock([MOLAuthenticatingURLSession class]);
OCMStub([self.mockMOLAuthenticatingURLSession alloc])
.andReturn(self.mockMOLAuthenticatingURLSession);
OCMStub([self.mockMOLAuthenticatingURLSession session]).andReturn(self.mockSession);
NSHTTPURLResponse *response =
[[NSHTTPURLResponse alloc] initWithURL:url
statusCode:200
HTTPVersion:@"HTTP/1.1"
headerFields:@{@"content-type" : @"application/json"}];
__unsafe_unretained __block void (^passedBlock)(NSData *, NSURLResponse *, NSError *);
XCTestExpectation *responseCallback =
[[XCTestExpectation alloc] initWithDescription:@"ensure writer passed JSON"];
// stub out session to call completion handler immediately.
OCMStub([(NSURLSessionDataTask *)self.mockSessionDataTask resume]).andDo(^(NSInvocation *inv) {
if (passedBlock) {
passedBlock(nil, response, nil);
}
[responseCallback fulfill];
});
// stub out NSURLSession to assign our completion handler and return our mock
OCMStub([self.mockSession dataTaskWithRequest:[OCMArg any] completionHandler:[OCMArg any]])
.andDo(^(NSInvocation *inv) {
[inv retainArguments];
[inv getArgument:&passedBlock atIndex:3];
})
.andReturn(self.mockSessionDataTask);
SNTMetricService *service = [[SNTMetricService alloc] init];
[service exportForMonitoring:[SNTMetricFormatTestHelper createValidMetricsDictionary]];
[self waitForExpectations:@[ responseCallback ] timeout:10.0];
}
- (void)testWritingMonarchJSONToAFile {
OCMStub([self.mockConfigurator exportMetrics]).andReturn(YES);
OCMStub([self.mockConfigurator metricFormat]).andReturn(SNTMetricFormatTypeMonarchJSON);
OCMStub([self.mockConfigurator metricURL]).andReturn(self.jsonURL);
SNTMetricService *ms = [[SNTMetricService alloc] init];
NSDictionary *validMetricsDict = [SNTMetricFormatTestHelper createValidMetricsDictionary];
[ms exportForMonitoring:validMetricsDict];
NSError *err;
// Ensure that this has written 1 file that is well formed.
NSArray *items = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:self.tempDir
error:&err];
XCTAssertNil(err);
XCTAssertEqual(1, items.count, @"failed to create JSON metrics file");
NSData *jsonData = [NSData dataWithContentsOfFile:self.jsonURL.path
options:NSDataReadingUncached
error:&err];
XCTAssertNil(err);
// Read expected result from a golden file.
NSString *path = [[NSBundle bundleForClass:[self class]] resourcePath];
path = [path stringByAppendingPathComponent:@"testdata/json/monarch.json"];
NSData *goldenFileData = [NSData dataWithContentsOfFile:path
options:NSDataReadingUncached
error:&err];
XCTAssertNil(err);
XCTAssertNotNil(goldenFileData, @"unable to open / read golden file");
NSDictionary *parsedJSONAsDict =
[NSJSONSerialization JSONObjectWithData:jsonData
options:NSJSONReadingMutableContainers
error:&err];
XCTAssertNil(err);
NSDictionary *expectedJSONAsDict =
[NSJSONSerialization JSONObjectWithData:goldenFileData
options:NSJSONReadingMutableContainers
error:&err];
XCTAssertNil(err);
XCTAssertEqualObjects(expectedJSONAsDict, parsedJSONAsDict, @"invalid JSON created");
}
@end

View File

@@ -14,7 +14,6 @@ objc_library(
srcs = [
"SNTMetricFileWriter.h",
"SNTMetricFileWriter.m",
"SNTMetricWriter.h",
],
deps = [
":SNTMetricWriter",
@@ -31,3 +30,35 @@ santa_unit_test(
":SNTMetricFileWriter",
],
)
objc_library(
name = "SNTMetricHTTPWriter",
srcs = [
"SNTMetricHTTPWriter.h",
"SNTMetricHTTPWriter.m",
],
deps = [
":SNTMetricWriter",
"//Source/common:SNTLogging",
"@MOLAuthenticatingURLSession",
],
)
santa_unit_test(
name = "SNTMetricHTTPWriterTest",
srcs = [
"SNTMetricHTTPWriterTest.m",
],
deps = [
":SNTMetricHTTPWriter",
"@OCMock",
],
)
test_suite(
name = "writer_tests",
tests = [
":SNTMetricFileWriterTest",
":SNTMetricHTTPWriterTest",
],
)

View File

@@ -0,0 +1,20 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// 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
///
/// http://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/santametricservice/Writers/SNTMetricWriter.h"
#import <Foundation/Foundation.h>
@interface SNTMetricHTTPWriter : NSObject <SNTMetricWriter>
@end

View File

@@ -0,0 +1,101 @@
/// Copyright 2021 Google Inc. All rights reserved.
///
/// 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
///
/// http://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 <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#include <dispatch/dispatch.h>
#import "Source/common/SNTLogging.h"
#import "Source/santametricservice/Writers/SNTMetricHTTPWriter.h"
@implementation SNTMetricHTTPWriter {
@private
MOLAuthenticatingURLSession *_authSession;
}
- (instancetype)init {
self = [super init];
if (self) {
_authSession = [[MOLAuthenticatingURLSession alloc] init];
}
return self;
}
/**
* Post serialzied metrics to the specified URL one object at a time.
**/
- (BOOL)write:(NSArray<NSData *> *)metrics toURL:(NSURL *)url error:(NSError **)error {
__block NSError *_blockError = nil;
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
request.HTTPMethod = @"POST";
[request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
_authSession.serverHostname = url.host;
NSURLSession *_session = _authSession.session;
dispatch_group_t requests = dispatch_group_create();
[metrics enumerateObjectsUsingBlock:^(id value, NSUInteger index, BOOL *stop) {
dispatch_group_enter(requests);
request.HTTPBody = (NSData *)value;
[[_session dataTaskWithRequest:request
completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response,
NSError *_Nullable err) {
if (err != nil) {
_blockError = err;
*stop = YES;
} else if (response == nil) {
*stop = YES;
} else if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
// Check HTTP error codes and create errors for any non-200.
if (httpResponse && httpResponse.statusCode != 200) {
_blockError = [[NSError alloc]
initWithDomain:@"com.google.santa.metricservice.writers.http"
code:httpResponse.statusCode
userInfo:@{
NSLocalizedDescriptionKey : [NSString
stringWithFormat:@"received http status code %ld from %@",
httpResponse.statusCode, url]
}];
*stop = YES;
}
}
dispatch_group_leave(requests);
}] resume];
// Wait up to 30 seconds for the request to complete.
if (dispatch_group_wait(requests, (int64_t)(30.0 * NSEC_PER_SEC)) != 0) {
NSString *errMsg =
[NSString stringWithFormat:@"HTTP request to %@ timed out after 30 seconds", url];
_blockError = [[NSError alloc] initWithDomain:@"com.google.santa.metricservice.writers.http"
code:ETIMEDOUT
userInfo:@{NSLocalizedDescriptionKey : errMsg}];
}
}];
if (_blockError != nil) {
// If the caller hasn't passed us an error then we ignore it.
if (error != nil) {
*error = [_blockError copy];
}
return NO;
}
return YES;
}
@end

View File

@@ -0,0 +1,184 @@
#import <Foundation/Foundation.h>
#import <XCTest/XCTest.h>
#import <MOLAuthenticatingURLSession/MOLAuthenticatingURLSession.h>
#import <OCMock/OCMock.h>
#import "Source/santametricservice/Writers/SNTMetricHTTPWriter.h"
@interface SNTMetricHTTPWriterTest : XCTestCase
@property id mockSession;
@property id mockSessionDataTask;
@property id mockMOLAuthenticatingURLSession;
@property NSMutableArray<NSDictionary *> *mockResponses;
@property SNTMetricHTTPWriter *httpWriter;
@end
@implementation SNTMetricHTTPWriterTest
- (void)setUp {
self.mockSession = OCMClassMock([NSURLSession class]);
self.mockSessionDataTask = OCMClassMock([NSURLSessionDataTask class]);
self.mockMOLAuthenticatingURLSession = OCMClassMock([MOLAuthenticatingURLSession class]);
OCMStub([self.mockMOLAuthenticatingURLSession alloc])
.andReturn(self.mockMOLAuthenticatingURLSession);
OCMStub([self.mockMOLAuthenticatingURLSession session]).andReturn(self.mockSession);
self.httpWriter = [[SNTMetricHTTPWriter alloc] init];
self.mockResponses = [[NSMutableArray alloc] init];
// This must be marked __unsafe_unretained because we're going to store into
// it using NSInvocation's getArgument:atIndex: method which takes a void*
// to populate. If we don't mark the variable __unsafe_unretained it will
// default to __strong and ARC will attempt to release the block when it goes
// out of scope, not knowing that it wasn't ours to release in the first place.
__unsafe_unretained __block void (^completionHandler)(NSData *, NSURLResponse *, NSError *);
void (^getCompletionHandler)(NSInvocation *) = ^(NSInvocation *invocation) {
[invocation getArgument:&completionHandler atIndex:3];
};
void (^callCompletionHandler)(NSInvocation *) = ^(NSInvocation *invocation) {
NSDictionary *responseValue = self.mockResponses[0];
if (responseValue != nil && completionHandler != nil) {
completionHandler(responseValue[@"data"], responseValue[@"response"],
responseValue[@"error"]);
[self.mockResponses removeObjectAtIndex:0];
} else {
XCTFail(@"mockResponses set to zero");
}
};
OCMStub([(NSURLSessionDataTask *)self.mockSessionDataTask resume]).andDo(callCompletionHandler);
OCMStub([self.mockSession dataTaskWithRequest:[OCMArg any] completionHandler:[OCMArg any]])
.andDo(getCompletionHandler)
.andReturn(self.mockSessionDataTask);
}
/// enqueues a mock HTTP response for testing.
- (void)createMockResponseWithURL:(NSURL *)url
withCode:(NSInteger)code
withData:(NSData *)data
withError:(NSError *)err {
NSHTTPURLResponse *response =
[[NSHTTPURLResponse alloc] initWithURL:url
statusCode:code
HTTPVersion:@"HTTP/1.1"
headerFields:@{@"content-type" : @"application/json"}];
NSMutableDictionary *responseValue = [[NSMutableDictionary alloc] init];
responseValue[@"data"] = data;
responseValue[@"response"] = response;
responseValue[@"error"] = err;
[self.mockResponses addObject:responseValue];
}
- (void)testValidPostOfData {
NSURL *url = [[NSURL alloc] initWithString:@"http://localhost:8444/submit"];
[self createMockResponseWithURL:url withCode:200 withData:nil withError:nil];
SNTMetricHTTPWriter *httpWriter = [[SNTMetricHTTPWriter alloc] init];
NSData *JSONdata = [@"{\"foo\": \"bar\"}\r\n" dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
BOOL result = [httpWriter write:@[ JSONdata ] toURL:url error:&err];
XCTAssertEqual(YES, result);
XCTAssertNil(err);
}
- (void)testEnsureHTTPErrorCodesResultInErrors {
NSURL *url = [NSURL URLWithString:@"http://localhost:10444"];
NSData *JSONdata = [@"{\"foo\": \"bar\"}\r\n" dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
for (NSInteger code = 400; code < 600; code += 100) {
[self createMockResponseWithURL:url withCode:code withData:nil withError:nil];
BOOL result = [self.httpWriter write:@[ JSONdata ] toURL:url error:&err];
XCTAssertEqual(NO, result, @"result of call to write did not fail as expected");
XCTAssertNotNil(err);
XCTAssertEqual(code, err.code);
XCTAssertEqual(@"com.google.santa.metricservice.writers.http", err.domain);
NSString *expectedErrMsg = [NSString
stringWithFormat:@"received http status code %ld from %@", code, url.absoluteString];
XCTAssertEqualObjects(expectedErrMsg, err.localizedDescription);
err = nil;
}
}
- (void)testEnsureErrorsFromTransportAreHandled {
NSURL *url = [NSURL URLWithString:@"http://localhost:9444"];
NSError *mockErr = [[NSError alloc] initWithDomain:@"com.google.santa.metricservice.writers.http"
code:505
userInfo:@{NSLocalizedDescriptionKey : @"test error"}];
NSError *err;
[self createMockResponseWithURL:url withCode:505 withData:nil withError:mockErr];
NSData *JSONdata = [@"{\"foo\": \"bar\"}\r\n" dataUsingEncoding:NSUTF8StringEncoding];
BOOL result = [self.httpWriter write:@[ JSONdata ] toURL:url error:&err];
XCTAssertEqual(NO, result, @"result of call to write did not fail as expected");
XCTAssertEqual(mockErr.code, err.code);
XCTAssertEqualObjects(mockErr.domain, err.domain);
XCTAssertEqualObjects(@"test error", err.userInfo[NSLocalizedDescriptionKey]);
}
- (void)testEnsureMutlipleEntriesWriteMultipleTimes {
NSURL *url = [NSURL URLWithString:@"http://localhost:9444"];
// Ensure that non-200 status codes codes do not crash
[self createMockResponseWithURL:url withCode:200 withData:nil withError:nil];
[self createMockResponseWithURL:url withCode:200 withData:nil withError:nil];
NSData *JSONdata = [@"{\"foo\": \"bar\"}\r\n" dataUsingEncoding:NSUTF8StringEncoding];
NSError *err;
BOOL result = [self.httpWriter write:@[ JSONdata, JSONdata ] toURL:url error:&err];
XCTAssertEqual(YES, result);
XCTAssertNil(err);
XCTAssertEqual(0, self.mockResponses.count, @"incorrect number of requests was made");
}
- (void)testEnsurePassingNilOrNullErrorDoesNotCrash {
NSURL *url = [NSURL URLWithString:@"http://localhost:9444"];
// Queue up two responses for nil and NULL.
[self createMockResponseWithURL:url withCode:400 withData:nil withError:nil];
[self createMockResponseWithURL:url withCode:400 withData:nil withError:nil];
NSData *JSONdata = [@"{\"foo\": \"bar\"}\r\n" dataUsingEncoding:NSUTF8StringEncoding];
BOOL result = [self.httpWriter write:@[ JSONdata ] toURL:url error:nil];
XCTAssertEqual(NO, result);
result = [self.httpWriter write:@[ JSONdata ] toURL:url error:NULL];
XCTAssertEqual(NO, result);
NSError *mockErr =
[[NSError alloc] initWithDomain:@"com.google.santa.metricservice.writers.http.test"
code:505
userInfo:@{NSLocalizedDescriptionKey : @"test error"}];
// Queue up two responses for nil and NULL.
[self createMockResponseWithURL:url withCode:500 withData:nil withError:mockErr];
[self createMockResponseWithURL:url withCode:500 withData:nil withError:mockErr];
result = [self.httpWriter write:@[ JSONdata ] toURL:url error:nil];
XCTAssertFalse(result);
result = [self.httpWriter write:@[ JSONdata ] toURL:url error:NULL];
XCTAssertFalse(result);
}
@end

View File

@@ -16,11 +16,17 @@
#import <MOLXPCConnection/MOLXPCConnection.h>
#import "Source/common/SNTDropRootPrivs.h"
#import "Source/common/SNTLogging.h"
#import "Source/common/SNTXPCMetricServiceInterface.h"
#import "Source/santametricservice/SNTMetricService.h"
int main(int argc, const char *argv[]) {
if (!DropRootPrivileges()) {
LOGE(@"unable to drop root privileges, exiting.");
exit(1);
};
@autoreleasepool {
NSDictionary *infoDict = [[NSBundle mainBundle] infoDictionary];
LOGI(@"Started, version %@", infoDict[@"CFBundleVersion"]);

4
Testing/benchmark.sh Executable file
View File

@@ -0,0 +1,4 @@
#!/bin/sh
# TODO: Pull benchmarks from previous commit to check for regression
bazel test //:benchmarks --define=SANTA_BUILD_TYPE=ci

View File

@@ -1,19 +1,41 @@
#!/bin/sh
set -e
GIT_ROOT=$(git rev-parse --show-toplevel)
TMP_DIR=$(mktemp -d)
KEYCHAIN="santa-dev-test.keychain"
SANTAD_PATH=Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon
SANTAD_ENTITLEMENTS="$GIT_ROOT/Source/santad/com.google.santa.daemon.systemextension.entitlements"
SANTA_BIN_PATH=Santa.app/Contents/MacOS
SIGNING_IDENTITY="localhost"
function cleanup() {
# Reset randomize_version if we used it
if [ -f "$TMP_DIR/version.bzl" ]; then
mv "$TMP_DIR/version.bzl" $VERSION_FILE
fi
rm -rf $TMP_DIR
rm -f $GIT_ROOT/bazel-bin/santa-*.tar.gz
}
trap cleanup EXIT
function randomize_version() {
VERSION_FILE="$GIT_ROOT/version.bzl"
# Create a random version ID for the generated Santa version.
# The system extension won't replace itself if the version string isn't different than the one
# presently installed.
cp $VERSION_FILE $TMP_DIR
RANDOM_VERSION="$RANDOM.$RANDOM"
echo "Setting version to $RANDOM_VERSION"
echo "SANTA_VERSION = \"$RANDOM_VERSION\"" > $VERSION_FILE
}
function build_custom_signed() {
SANTAD_PATH=Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon
SANTA_BIN_PATH=Santa.app/Contents/MacOS
KEYCHAIN="santa-dev-test.keychain"
SANTAD_ENTITLEMENTS="$GIT_ROOT/Source/santad/com.google.santa.daemon.systemextension.entitlements"
SIGNING_IDENTITY="localhost"
function main() {
bazel build --apple_generate_dsym -c opt --define=SANTA_BUILD_TYPE=ci --define=apple.propagate_embedded_extra_outputs=yes --macos_cpus=x86_64,arm64 //:release
echo "> Build complete, installing santa"
TMP_DIR=$(mktemp -d)
tar xvf $GIT_ROOT/bazel-bin/santa-*.tar.gz -C $TMP_DIR
CS_ARGS="--prefix=EQHXZ8M8AV -fs $SIGNING_IDENTITY --timestamp --options library,kill,runtime"
@@ -22,7 +44,27 @@ function main() {
done
codesign ${CS_ARGS} --keychain $KEYCHAIN --entitlements $SANTAD_ENTITLEMENTS $TMP_DIR/binaries/$SANTAD_PATH
}
function build_provisionprofile_signed() {
bazel build --apple_generate_dsym -c opt --define=SANTA_BUILD_TYPE=release --define=apple.propagate_embedded_extra_outputs=yes --macos_cpus=x86_64,arm64 //:release
tar xvf $GIT_ROOT/bazel-bin/santa-*.tar.gz -C $TMP_DIR
}
function build() {
SANTA_DAEMON_PROVPROFILE=$GIT_ROOT/Source/santad/Santa_Daemon_Dev.provisionprofile
SANTA_PROVPROFILE=$GIT_ROOT/Source/santa/Santa_Dev.provisionprofile
if [[ -f $SANTA_DAEMON_PROVPROFILE && -f $SANTA_PROVPROFILE ]]; then
echo "Using provisionprofiles in $SANTA_DAEMON_PROVPROFILE and $SANTA_PROVPROFILE"
build_provisionprofile_signed
else
echo "No provisionprofiles detected, creating self-signed certs"
build_custom_signed
fi
}
function install() {
echo "> Running install.sh"
(
cd $TMP_DIR
@@ -30,5 +72,18 @@ function main() {
)
}
function main() {
for i in "$@"; do
case $i in
--randomize_version)
randomize_version
;;
esac
done
build
install
}
main $@
exit $?

5
Testing/fix.sh Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/bash
GIT_ROOT=$(git rev-parse --show-toplevel)
find $GIT_ROOT \( -name "*.m" -o -name "*.h" -name "*.mm" \) -exec xcrun clang-format -i {} \+
buildifier --lint=fix -r $GIT_ROOT

View File

@@ -2,14 +2,40 @@
set -e
GIT_ROOT=$(git rev-parse --show-toplevel)
CNF_PATH=$GIT_ROOT/Testing/openssl.cnf
openssl genrsa -out ./santa.key 2048
openssl rsa -in ./santa.key -out ./santa.key
openssl req -new -key ./santa.key -out ./santa.csr -config $CNF_PATH
openssl x509 -req -days 10 -in ./santa.csr -signkey ./santa.key -out ./santa.crt -extfile $CNF_PATH -extensions codesign
openssl pkcs12 -export -out santa.p12 -inkey santa.key -in santa.crt -password pass:santa
KEYCHAIN="santa-dev-test.keychain"
security create-keychain -p santa $KEYCHAIN
security import ./santa.p12 -k $KEYCHAIN -A -P santa
security add-trusted-cert -d -r trustRoot -k $KEYCHAIN santa.crt
function init() {
openssl genrsa -out ./santa.key 2048
openssl rsa -in ./santa.key -out ./santa.key
openssl req -new -key ./santa.key -out ./santa.csr -config $CNF_PATH
openssl x509 -req -days 10 -in ./santa.csr -signkey ./santa.key -out ./santa.crt -extfile $CNF_PATH -extensions codesign
openssl pkcs12 -export -out santa.p12 -inkey santa.key -in santa.crt -password pass:santa
security create-keychain -p santa $KEYCHAIN
security import ./santa.p12 -k $KEYCHAIN -A -P santa
security add-trusted-cert -d -r trustRoot -k $KEYCHAIN santa.crt
}
function cleanup() {
security delete-keychain $KEYCHAIN
rm santa.key
rm santa.csr
rm santa.p12
}
function main() {
case $1 in
init)
init
;;
cleanup)
cleanup
;;
*)
echo "$0 [init|cleanup]"
;;
esac
}
main $@
exit $?

Some files were not shown because too many files have changed in this diff Show More