A safety-first CLI to operate Elastic Security as code.
Examples in this document run elkctl, a short alias for the same binary as
elasticctl. Every install method below puts both names on PATH. The
project, the crate on crates.io, and the GitHub repository stay named
elasticctl.
elasticctl is a Rust CLI for managing Elastic Security detection rules as
code across self-managed stacks, Elastic Cloud Hosted deployments, and Elastic
Cloud Serverless projects. It is a sibling to
splunkctl and shares its operating
contracts:
- Remote mutations are dry runs by default. Nothing is applied until you
pass
--yes, and every preview names the profile, host, and space it would touch. - Configuration as code. Pull live rules, review structured drift, push approved changes with a change-evidence report. Push never deletes remote rules.
- Stable machine output. Table by default,
--jsonon request, typed error envelopes on stderr. - Named profiles for separate development, UAT, and production instances.
An MCP server is planned once the CLI surface is stable.
Install from crates.io or download a prebuilt binary.
Requires the stable Rust toolchain.
cargo install elasticctlThis installs both the elasticctl and elkctl binaries. Not every release
reaches crates.io, so this can be a version behind. GitHub Releases always
carry the newest version.
Each release ships prebuilt binaries for Linux (glibc and musl), macOS
(Intel and Apple Silicon), and Windows. Every archive contains both the
elasticctl and elkctl binaries. Download the archive for your platform
from the latest release,
put both binaries on your PATH, or use an installer script, which does that
for you:
# macOS and Linux
curl --proto '=https' --tlsv1.2 -LsSf \
https://github.com/dannyota/elasticctl/releases/latest/download/elasticctl-installer.sh \
| sh# Windows
powershell -ExecutionPolicy Bypass -c "irm https://github.com/dannyota/elasticctl/releases/latest/download/elasticctl-installer.ps1 | iex"Create a profile from ELASTICCTL_* environment variables; .env.example
documents them. The key must be a project-scoped Elasticsearch API key created
inside Kibana. An organization-level Cloud key can read and create disabled
rules but cannot enable one.
export ELASTICCTL_KIBANA_URL=https://YOUR-PROJECT.kb.YOUR-REGION.aws.elastic.cloud
export ELASTICCTL_API_KEY=...
elkctl config init --from-envConfirm the stack is reachable, the key scope is right, and rules are readable:
elkctl doctorelkctl state pull --dir state # writes state/rules/*.ndjson
# edit rules, or add new ones
elkctl state diff --dir state # field-level drift, no changes made
elkctl state push --dir state # preview; add --yes to applypush and every other remote mutation preview by default and apply only with
--yes.
All state commands accept positional selectors, --tag, and
--source custom|customized|prebuilt|all. --source defaults to custom;
--source all includes the full rule corpus. --source limits an unselected
state command to matching rules. Positional selectors and --tag override
that unselected source scope: they first resolve rule IDs, then read those
rules:
elkctl state diff --dir state my-rule-id # one rule
elkctl state push --dir state --tag prod # one tagdiff and push resolve a selector against the directory first, so a rule you
have only written locally is selectable by name before it exists on the stack.
Inspect and manage individual rules:
elkctl rules list
elkctl rules get <rule_id-or-name>
elkctl rules validate --path rule.yaml
elkctl rules enable <rule_id> --yes
elkctl rules export --tag my-corpus --out rules.ndjson
elkctl rules preview my-rule-id --sample 3Data views use stable ids and portable JSON or YAML files. Legacy scripted fields are not portable and are rejected before any remote request.
elkctl data-views list --search logs
elkctl data-views get logs-default
elkctl data-views validate --path data-views.yaml
elkctl data-views export logs-default --format-file yaml > data-views.yaml
elkctl data-views import --path data-views.yaml --yes
elkctl data-views default set logs-default --yes
elkctl data-views default unset --yes
elkctl data-views delete old-logs --replace-with logs-default --yesimport, delete, and default set|unset are guarded mutations. They print
a preview unless --yes is supplied. Delete refuses a referenced or current
default data view until its references/default are safely replaced or unset.
Dashboards use stable ids and portable JSON or YAML files. Typed export omits server metadata and refuses a dashboard carrying loss warnings. Use an opaque Saved Objects bundle when the dashboard needs its deep dependencies moved with it.
elkctl dashboards list --search security --tag production
elkctl dashboards get security-overview
elkctl dashboards validate --path dashboards.yaml
elkctl dashboards export security-overview --format-file yaml > dashboards.yaml
elkctl dashboards import --path dashboards.yaml --yes
elkctl dashboards delete retired-overview --yes
elkctl dashboards bundle export security-overview > dashboards.ndjson
elkctl dashboards bundle import --path dashboards.ndjson --yesimport, delete, and bundle import are guarded mutations. Typed import
checks referenced data views before it writes. Bundle bytes stay opaque so the
server can restore the dashboard and its exported dependencies together.
elkctl fleet agent-policies list --search linux
elkctl fleet agent-policies get production-linux
elkctl fleet agent-policies validate --path agent-policies.yaml
elkctl fleet agent-policies export --all-custom --format-file yaml > agent-policies.yaml
elkctl fleet agent-policies import --path agent-policies.yaml --yes
elkctl fleet agent-policies delete retired-linux --yeselkctl alerts list --status open --severity critical
elkctl alerts ack <alert_id> --yes
elkctl alerts close <alert_id> --reason false_positive --yes
elkctl cases create --title "Suspicious PowerShell activity" --severity high --yes
elkctl cases attach <case_id> --alert <alert_id> --yes
elkctl cases comment <case_id> --message "Confirmed benign, closing." --yeselkctl config init | list | show | test
elkctl doctor
elkctl info
elkctl rules list [--source custom|customized|prebuilt|all]
| get | validate | enable | disable | delete
elkctl rules export [<selector>...] [--tag TAG] [--source custom|customized|prebuilt|all]
| import [--skip-existing] | preview [--sample N]
elkctl rules prebuilt status|install
elkctl exceptions list | get | validate | export | import | delete
elkctl data-views list [--search TEXT] | get <id-or-exact-name> | validate --path FILE
elkctl data-views export [<id-or-exact-name>...] [--format-file json|yaml]
| import --path FILE [--overwrite|--skip-existing] --yes
| delete <id-or-exact-name>... [--replace-with ID] --yes
| default get | set <id-or-exact-name> --yes | unset --yes
elkctl dashboards list [--search TEXT] [--tag TAG] [--limit N]
| get <id-or-exact-title> | validate --path FILE
elkctl dashboards export [<id-or-exact-title>...] [--format-file json|yaml]
| import --path FILE [--overwrite|--skip-existing] --yes
| delete <id-or-exact-title>... --yes
| bundle export [<id-or-exact-title>...]
| bundle import --path FILE [--overwrite] --yes
elkctl fleet agent-policies list [--search TEXT] [--limit N] | get <id-or-exact-name>
| validate --path FILE
elkctl fleet agent-policies export [<id-or-exact-name>...|--all-custom] [--format-file json|yaml]
| import --path FILE [--overwrite|--skip-existing] --yes
| delete <id-or-exact-name>... --yes
elkctl state {pull|diff|push} --dir DIR [<selector>...] [--tag TAG]
[--source custom|customized|prebuilt|all]
elkctl search esql <QUERY> [--data-view DV | --index IDX] [--limit N]
elkctl search dsl <BODY> [--data-view DV | --index IDX] [--limit N] [--with-meta]
elkctl alerts list [--status open|acknowledged|closed] [--severity S] [--rule R]
[--tag T] [--assignee USER] [--since DUR|ISO] [--search TEXT]
elkctl alerts get <alert_id>
elkctl alerts ack|open|close (<alert_id>... | --query <dsl|@file>) --yes
elkctl alerts close <alert_id>... --reason false_positive --yes
elkctl alerts tag <alert_id>... --add triaged --remove noise --yes
elkctl alerts assign <alert_id>... --add USER --yes
elkctl cases list [--status open|in-progress|closed] [--severity S]
[--tag T] [--search TEXT]
elkctl cases get <case_id>
elkctl cases create --title T [--description D] [--tag T]... [--severity S]
[--assignee USER]... --yes
elkctl cases close|open <case_id>... --yes
elkctl cases delete <case_id>... --yes
elkctl cases attach <case_id> --alert <alert_id>... --yes
elkctl cases comment <case_id> --message TEXT --yes
elkctl completion bash|elvish|fish|powershell|zsh
elkctl commands
--profile, --config, --space, --json / --format, --fields, --out,
--yes, --timeout, --debug. Run elkctl help for details.
git clone https://github.com/dannyota/elasticctl
cd elasticctl
cargo testSetup, the gates a pull request must pass, and the rules review holds you to:
CONTRIBUTING.md. Cutting a release:
docs/releasing.md.
Apache-2.0