<#1096 osquery extensions delivery> Pull request o...
# osctrl
g
#1096 osquery extensions delivery Pull request opened by javuto osquery extensions delivery Companion to the osctrld change that installs extensions safely (verified, root-owned, with process checks, rollback and per-extension status). Builds on per-node flags overrides (#1093). Problem osquery extensions add tables osquery doesn't ship, such as the trailofbits/osquery-extensions collection, and command execution, the typical investigation tool. osctrl had no way to distribute them. osctrld already called
POST /{env}/osctrld-extensions
, but osctrl didn't serve it. What changes Library:
pkg/extensions
• Extensions: name (
^[A-Za-z0-9_-]{1,64}$
, unique ignoring case), description, version, and a command execution marker. • Files: one per platform/architecture: darwin {amd64, arm64, universal}, linux {amd64, arm64}, windows {amd64}. • Up to 64 MB each, with SHA-256 and size computed by osctrl on upload. • Stored in the database as 1 MiB chunks shared by content hash. The column maps to longblob/bytea/blob; no 64 MB buffers anywhere. • Truncated uploads are detected and stored nowhere. • Assignments: • Ordinary extensions per environment. • Command-execution extensions per host, with optional expiry. • This split is enforced at assignment time and again when files are served, whatever rows exist. • Effective set per host: the environment's extensions plus the host's unexpired command-execution assignments, picked by platform and architecture. Darwin falls back to `universal`; extensions without a file for the platform are skipped. • Per-extension status reported by osctrld:
ok
,
rejected
,
kept
(a removal failed) and
removed
. • Lingering: a host is marked lingering when a command-execution extension reports
kept
and the host has no unexpired assignment for it. • Removing the assignment keeps the
kept
record. • Deleting the extension, or turning its command-execution marker off, is refused while any host reports
kept
. • Deletion guards: an extension can't be deleted, or have its marker flipped, while it's assigned. osctrl-tls (under
osctrld.enabled
, authenticated with the environment secret) • List:
POST /{env}/osctrld-extensions
with
{secret, uuid?, platform, arch, status?}
returns
[{name, sha256, size, url}]
. • The UUID is used only when it's a node of that environment; the node row's UUID is used for the set and the status record. • Old osctrld clients, which send no platform/arch, get
[]
. • Download:
POST /{env}/osctrld-extension
with
{secret, sha256}
. • Streams the file with
Content-Length
. • Only serves files assigned in that environment, by content hash. • Route registration moved into
registerRoutes()
, with a test that registers every route together. The originally planned
/{env}/osctrld-extension/{sha256}
panicked at startup against
/{env}/{secret}/osquery-config
. osctrl-api • Library (super admins only; denials audited): •
GET|POST /api/v1/extensions
•
PATCH|DELETE /api/v1/extensions/{id}
•
PUT|DELETE /api/v1/extensions/{id}/files/{platform}/{arch}
• Upload: multipart field
file
, streamed with
MultipartReader
. • The body cap is 65 MiB; a file over 64 MB gets 413, an empty file 400. • A 10-minute read deadline applies. • The filename is sanitized, and the description and version are capped. • Environment (environment admins): •
GET|PUT|DELETE /api/v1/environments/extensions/{env}[/{id}]
. • Only ordinary extensions are offered and accepted; command-execution ones get 409. • Node: •
GET /api/v1/nodes/{env}/node/{uuid}/extensions
, for environment admins: the effective set, assignments, statuses and lingering rows. The command-execution library is included for super admins. •
PUT|DELETE /api/v1/nodes/{env}/node/{uuid}/extensions/{id}
, super admins only. • The body must be exactly
{"expires_at": <future RFC3339> | null}
. A missing or misspelled key is refused rather than silently granting "never expires". • A removal that removes nothing returns 404 and isn't audited. • Audit: every upload, file change, extension create/update/delete and assignment change, with the environment set where applicable. • Node deletion (API and CLI database mode) removes the node's assignments and statuses. • OpenAPI is regenerated. SPA • Admin › Extensions (super admins): • library table: command-execution badge, files with size and SHA-256, where assigned; • create and edit; • upload or replace per platform, with a client-side 64 MB check; • delete disabled while assigned. • Environment › Configuration › Extensions: assign or remove ordinary extensions. • Node › Configuration › Extensions: the effective set with its source, availability for the host's platform, and the last status as host-reported plain text. • Super admins get Allow command execution, behind a confirmation with the "I understand … I know what I'm doing" checkbox, with optional expiry and remove. • Node banner: • "Command execution is allowed … via {extension}", only while osctrld reports it running; • "… assigned … but is not running" otherwise; • the expired wording; • "Command execution via {extension} was removed, but osctrld could not remove it from this host: {error}" for lingering hosts, which takes precedence. Deployment and docs • nginx
/api/
client_max_body_size
raised from 64m to 65m in
deploy/nginx/frontend.conf
,
deploy/docker/conf/nginx/frontend-dev.conf
,
deploy/cicd/nginx/frontend.conf
and
deploy/cicd/nginx/frontend-tls.conf
. • ARCHITECTURE.md and README updated. Security and operational impact • Who can do what: • Only super admins can put a binary into the library. • Only super admins can allow command execution, and only on single hosts. • Environment admins can assign ordinary extensions to their own environment only. • All checks are server-side, with CSRF on mutations; denials and changes are audited. • Integrity: osctrld installs only files whose size and SHA-256 match osctrl's record, fetched from its own configured osctrl. • Downloads are scoped to the environment and content hash, not per host. Any osctrld holding the environment secret and a file's hash can fetch it. Using it still needs root on that host to load it, and each host's list is per host. • Revoked command execution stays visible: a command-execution extension a host failed to remove keeps showing on the node page until osctrld reports it removed. • Delivery: only hosts running osctrld in service mode get extensions, on its next sync (default 60 minutes). • Schema: new tables via AutoMigrate (
extensions
,
extension_files
,
extension_chunks
,
env_extensions
,
node_extensions
,
node_extension_statuses
) with explicit
idx_ext_*
index names. No changes to existing tables. • Deployments using `provision.sh`: the direct API vhost inherits nginx's global 20 MB body limit (documented). Uploads over 20 MB through it fail until
client_max_body_size
is raised there. • Mixed versions: • Older osctrld clients get an empty list. • Older osctrld clients report a failed removal as
rejected
, which isn't flagged as lingering. • Known limits: • A host's
kept
record that never reports again blocks deleting that extension until the node is deleted. • Expiry is changed by re-running Allow. Validation • `go test ./... -count=1`: 57 packages pass • `go vet ./...`;
gofmt -l pkg cmd
(empty); `golangci-lint run ./...`: 0 issues • `make openapi-check`: clean • `cd frontend && npm test`: 560 tests pass; `npm run check`: clean • New tests, each seen failing first: • `pkg/extensions`: • name validation, including case-insensitive uniqueness; • chunking, shared chunks and cleanup; • exactly 64 MiB accepted and 64 MiB + 1 re… jmpsec/osctrl