Daemon for osctrl, the fast and efficient osquery management.
osctrld is the daemon component for osctrl. It keeps osquery clients aligned with their osctrl environment by managing flags, enrollment secrets, server certificates, extensions, and local osquery restarts.
It is designed to run close to osquery as a small operational companion: it can perform one-shot enrollment and verification tasks, or run continuously as a daemon that periodically syncs configuration from osctrl.
osctrld can:
- β¨ Enroll and remove nodes using osctrl-provided scripts.
- π₯ Retrieve osquery flags and TLS certificates from osctrl.
- π Verify local flags, certificates, and secrets against the server.
- β‘ Run as a daemon with a configurable sync interval and jitter.
- π Restart osquery when managed files or extensions change.
- π§© Deploy osquery extensions from osctrl extension lists, when the osctrl server serves them.
- βοΈ Use YAML or JSON configuration files, CLI flags, or environment variables.
- π Emit human-readable or JSON structured logs.
- π₯οΈ Run on Linux, macOS, and Windows.
β οΈ Warningosctrld manages files and actions that affect osquery execution. Review configuration, file permissions, TLS settings, and extension sources before deploying it to production endpoints.
osquery is powerful, but keeping endpoint configuration current across many systems can become repetitive and error-prone. osctrld provides a narrow, auditable daemon that lets osctrl manage the local osquery client state without embedding that logic in deployment scripts.
The goal is to keep endpoint-side lifecycle work predictable: retrieve the desired state from osctrl, write it to the expected local paths, detect changes, and restart osquery only when needed.
Full documentation for the osctrl project is available at https://osctrl.net.
For this repository, the most useful starting points are:
- service/osctrld-sample.yaml for a YAML configuration example.
- service/linux/systemd.service for Linux service deployment.
- service/darwin/net.osctrl.daemon.plist for macOS launchd deployment.
- SECURITY.md for vulnerability reporting and operator security guidance.
.
βββ cmd/osctrld/ # osctrld CLI, daemon logic, and tests
βββ service/
β βββ darwin/ # macOS launchd service definition
β βββ linux/ # Linux systemd service definition
β βββ osctrld-sample.yaml # Sample YAML configuration
βββ tests/ # Test configuration fixtures
βββ .github/workflows/ # CI and release automation
βββ .goreleaser.yml # Multi-platform release configuration
βββ Makefile # Local build, test, and release helpers
βββ SECURITY.md # Security policy
osctrld is a Go CLI built around a small set of commands:
enrollandremoveretrieve osctrl-provided lifecycle scripts and print them to stdout for the operator to review and run.flagsandcertfetch managed osquery files and write them locally.verifychecks whether local osquery configuration matches osctrl.serviceruns the daemon loop and repeatedly syncs managed state.check-configvalidates a configuration file and exits.default-configprints a generated YAML configuration template.
The daemon mode performs a sync cycle on a configurable interval. During each cycle, osctrld retrieves flags, certificates, and extension lists from osctrl, writes changed content to disk, and restarts osquery through the operating system service manager when a managed artifact changes.
Configuration comes from CLI flags, environment variables, or the osctrld section of a YAML or JSON file. YAML is the default format for new deployments. Note that supplying a configuration file replaces the entire effective configuration, so a config file takes precedence over flags and environment variables for every field, not only the ones it sets. Runtime defaults are applied before commands run, and wrapped commands validate the effective configuration before doing work.
Download the latest release from the Releases page.
go build -o osctrld ./cmd/osctrld/You can also use the Makefile:
make buildExample YAML configuration:
osctrld:
# osctrl enrollment secret value used to authenticate requests
osctrlSecret: "thisisthesecret"
# Local path where the osquery enrollment secret file is written or verified
osquerySecretFile: "/path/to/osquery.secret"
osqueryFlagFile: "/path/to/osquery.flags"
osqueryCertFile: "/path/to/osquery.crt"
environment: "osctrl_environment_UUID"
baseurl: "https://osctrl.url"
insecure: false
verbose: false
force: true
logFormat: "text"
interval: 60
extensionsDir: "/path/to/extensions/"JSON configuration files are also supported. Use a .json extension and osctrld will detect the format automatically.
check-config, service, enroll, remove, flags, cert, and verify all validate the effective configuration before running.
| Field | Description | Default |
|---|---|---|
osctrlSecret |
osctrl enrollment secret value used to authenticate requests | Required |
osquerySecretFile |
Local path where the osquery enrollment secret file is written or verified | OS-dependent |
osqueryFlagFile |
Path to the osquery flags file | OS-dependent |
osqueryCertFile |
Path to the osquery TLS certificate file | OS-dependent |
enrollScript |
Path to the enroll script | OS-dependent |
removeScript |
Path to the remove script | OS-dependent |
osquery |
Path to the osquery installation directory | OS-dependent |
environment |
UUID of the osctrl environment. osctrl rejects environment names on the osctrld endpoints | Required |
baseurl |
Base URL of the osctrl server | Required |
insecure |
Ignore TLS certificate warnings | false |
verbose |
Enable debug logging | false |
force |
Overwrite existing managed files | false |
logFormat |
Log format: text or json |
text |
interval |
Sync interval in minutes for daemon mode | 60 |
extensionsDir |
Directory for osquery extension binaries (configuration file only, no flag) | OS-dependent |
NAME:
osctrld - Daemon for osctrl, the fast and efficient osquery management
COMMANDS:
enroll Enroll a new node in osctrl, using new secret and flag files
remove Remove enrolled node from osctrl, clearing secret and flag files
verify Verify flags, cert and secret for an enrolled node in osctrl
flags Retrieve flags for osquery from osctrl and write them locally
cert Retrieve server certificate for osquery from osctrl and write it locally
service Run as a daemon, periodically syncing flags and certificate
install Enroll this node natively: install osquery if needed, write secret, flags and certificate, and start the service
uninstall Remove this node from osctrl natively: stop the service and delete secret, flags and certificate. osquery itself is left installed
check-config Validate configuration and exit
default-config Print a default YAML configuration
Validate configuration before launching:
osctrld check-config --config /etc/osctrld/osctrld.yamlGenerate a fresh config template:
osctrld default-configRetrieve flags and certificates:
osctrld flags --config /etc/osctrld/osctrld.yaml
osctrld cert --config /etc/osctrld/osctrld.yamlEnroll a node:
osctrld enroll --config /etc/osctrld/osctrld.yamlVerify a node:
osctrld verify --config /etc/osctrld/osctrld.yamlRun in daemon mode:
osctrld service --config /etc/osctrld/osctrld.yaml --interval 60In daemon mode, osctrld will:
- Sync flags, certificates, and extensions from osctrl every
--intervalminutes with jitter. - Detect whether managed content changed on disk.
- Restart osquery through the OS service manager when changes are detected.
- Shut down gracefully on
SIGINTorSIGTERM.
Flag requests carry the host UUID (read with osqueryd -S), so osctrl can serve a
per-node flagfile, plus the outcome of the last flags change. osquery exits on an
unknown flag or bad value, so after a restart caused by new flags osctrld waits up to
60 seconds for osqueryd to stay running. If it does not, osctrld writes the
environment flags back, restarts osquery once, reports the flags as rejected and
does not apply that same content again until osctrl serves something different. A
check interrupted by a stop, crash or reboot is finished on the next sync. This state,
along with the last known host UUID (used when the lookup fails), is kept next to the
flagfile, in <osqueryFlagFile>.osctrld-state.json.
Extensions are synced after flags and certificates, with their own restart and check, so a bad extension never rolls back good flags (two restarts only when both changed). The extensions request carries the host UUID and platform/architecture, and the outcome of the last extensions change, which is sent once.
- Verified. Each extension is downloaded from the configured osctrl server with
POST <base>/<env>/osctrld-extensionand{secret, sha256}(never from a URL in the list) into a temp file inextensionsDir, and its size and SHA-256 are checked before it is renamed into place as<name>.ext(<name>.exeon Windows). - Root-owned. Files are owned by root at
0755(an Administrators/SYSTEM-only write ACL on Windows), andextensionsDiris kept root-owned and not group/world-writable. Symlinks are never written or removed through. - Autoload file. osctrld writes the file named by
--extensions_autoloadin the flagfile, or osquery's default (/etc/osquery/extensions.load,/private/var/osquery/extensions.load,C:\Program Files\osquery\extensions.load). It manages only its own lines; lines an operator added, and files an operator placed inextensionsDir, are kept, even when osctrl assigns no extensions. The file is checked every sync: if osctrld's lines are missing, or the flags moved it, the lines are written again (and taken out of the old file), and osquery is restarted and checked. Since the path comes from the flags, which environment admins can edit, osctrld only writes a.loadfile directly in osquery's base directory (osqueryin the configuration; the defaults above qualify), and never one that is a symlink, the flagfile, the secret or certificate file, one of osctrld's state files, insideextensionsDir, or in a directory not owned by root or writable by others. On a refused path it changes nothing that sync and reports the error for every served extension, once per refused path. An existing file keeps its mode, minus group/world write. - Removal. Extensions osctrld installed that are no longer assigned are removed,
with their autoload lines, and reported as
removed. A removal that fails (the file cannot be moved aside, the autoload file cannot be written, orosqueryddoes not stay up without it) leaves the extension installed and is reported askept, so osctrl can show that a revoked extension is still on the host. - Process check and rollback. After the restart, osctrld waits up to 60 seconds for
osqueryd, then up to 60 seconds more for a process running each new extension's installed path. Ifosqueryddoes not stay up, the previous extensions are restored and osquery is restarted. If only some extensions do not run, just those are removed and osquery is restarted once. Failed content is reported asrejectedand not installed again until osctrl serves a different SHA-256; meanwhile the last good version, if any, stays installed. A removal that keepsosqueryddown (for example while the flags still--extensions_requireit) is undone and not retried until osctrl serves that extension again; until then it is reportedkepton every sync, and once served again, as installed, it is reportedok. Statuses are sent on the next extensions request, only the newest per extension; they are dropped only when osctrl answers 400 or 413, and kept for the next request on any other error (for example 403 after a secret rotation). With--disable_extensionsin the flags, extensions are still installed and reportedokwith a note, without the process check. - Windows. A running executable cannot be overwritten or deleted, so a replaced or
removed extension is renamed aside (
<file>.prev) and deleted after osquery restarted.
This state, including a check interrupted by a stop, crash or reboot (finished on the
next sync, restarting osquery first if that had not happened yet), is kept in
<osqueryFlagFile>.osctrld-extensions-state.json.
osctrld install does everything the osctrl quick-add script does, without a shell:
it installs osquery when it is missing or out of date, writes the secret, flags and
certificate, and starts and enables the service. osctrld uninstall reverses the
configuration β it stops the service and deletes those three files, and deliberately
leaves osquery itself installed.
Both commands require root on Linux and macOS, and Administrator on Windows. osctrld
never calls sudo itself; run it under sudo.
The osquery package is verified before installation. The SHA-256 comes from the osctrl
server when it provides one, otherwise from --osquery-sha256. Without either, the
install is refused unless you pass --allow-unverified.
| Flag | Environment variable | Purpose |
|---|---|---|
--osquery-sha256 |
OSQUERY_SHA256 |
Expected SHA-256 of the osquery package |
--osquery-package |
OSQUERY_PACKAGE |
Override the package URL, for mirrors and air-gapped installs |
--allow-unverified |
OSCTRL_ALLOW_UNVERIFIED |
Install without verifying the package |
sudo osctrld --secret <secret> --environment <environment-uuid> --osctrl-url https://osctrl.example.com \
--osquery-sha256 <sha256-of-the-package> installcp osctrld /usr/bin/osctrld
cp service/linux/systemd.service /etc/systemd/system/osctrld.service
mkdir -p /etc/osctrld
install -m 0600 service/osctrld-sample.yaml /etc/osctrld/osctrld.yaml
systemctl daemon-reload
systemctl enable osctrld
systemctl start osctrldEdit /etc/osctrld/osctrld.yaml with the correct osctrl URL, environment, secret, and local osquery paths before starting the service in production. The deb, rpm and apk packages install the binary, unit and configuration to these same paths.
The service runs as root: it writes the osquery flagfile, certificate and extensions, and restarts osqueryd when they change. Keep the configuration file 0600, since it holds the osctrl secret.
cp osctrld /usr/local/bin/
mkdir -p /usr/local/etc/osctrld
install -m 0600 service/osctrld-sample.yaml /usr/local/etc/osctrld/osctrld.yaml
cp service/darwin/net.osctrl.daemon.plist /Library/LaunchDaemons/
launchctl load /Library/LaunchDaemons/net.osctrl.daemon.plistThe plist runs /usr/local/bin/osctrld with /usr/local/etc/osctrld/osctrld.yaml and logs to /var/log/osctrld.log; adjust the paths if you installed elsewhere (Homebrew on Apple silicon uses /opt/homebrew). Update the configuration file for your environment before loading the daemon.
Install Go, then run the test suite:
go test ./cmd/osctrld/ -vUseful Makefile targets:
make build
make test
make test_cover
make release-snapshotInstall the pre-commit hooks once per clone, so formatting, linting (including the GOOS=windows build) and the race-enabled tests run before every commit:
pre-commit installRun them across the whole tree with pre-commit run --all-files.
Run the same checks used by CI before opening a pull request:
go test ./cmd/osctrld/ -race -coverprofile=coverage.out -v
go tool cover -func=coverage.out--configuration FILE, -c FILE Configuration file for osctrld
--secret value, -s value osctrl enrollment secret
--environment value, -e value UUID of the osctrl environment
--secret-file FILE, -S FILE Local osquery enrollment secret file
--flagfile FILE, -F FILE Local osquery flags file
--certificate FILE, -C FILE Local osquery TLS certificate file
--osctrl-url value, -U value Base URL for the osctrl server
--osquery-path FILE, -o FILE Path to osquery installation
--insecure, -i Ignore TLS warnings
--verbose, -V Enable debug logging
--force, -f Overwrite existing managed files
--log-format value, -L value Log output format: text or json
--interval value, -I value Sync interval in minutes for service mode
All flags can also be set through environment variables, including OSCTRL_CONFIG, OSCTRL_SECRET, OSCTRL_ENV, OSQUERY_SECRET, OSQUERY_FLAGFILE, OSQUERY_CERTIFICATE, OSCTRL_URL, OSQUERY_PATH, OSCTRL_INSECURE, OSCTRL_VERBOSE, OSCTRL_FORCE, OSCTRL_LOG_FORMAT, and OSCTRL_INTERVAL.
Find us in the #osctrl channel in the official osquery Slack community (Request an auto-invite!).
osctrld is licensed under the MIT License.
If you believe you have found a security issue in osctrld, please do not open a public GitHub issue. Follow the process in SECURITY.md.
We β€οΈ contributions!
Contributions are welcome. Please open an issue or pull request with a clear description of the problem, proposed change, and validation performed.
