Search for AWS

Use cases

The nine commands (Command reference) are ordinary SPL, so anything you can express in a search you can run, save, schedule, and alert on. This page collects searches by goal and shows how to turn them into alerts that watch your accounts live. The bundled dashboards package many of these as ready-made panels - this page is for building your own.

Inventory and posture

Instances by type and state, across the estate:

| awsget kind=instances account=* region=*
| spath path=State.Name output=state
| stats count by account region InstanceType state

S3 buckets without a full public-access block - a posture check. A bucket is listed when any of the four block settings is off or missing, which is what the S3 dashboard marks REVIEW. It does not show that a bucket is public; that depends on the bucket’s policy and ACLs:

| awsget kind=buckets account=prod detail=true
| spath input=PublicAccessBlockConfiguration path=BlockPublicAcls output=b1
| spath input=PublicAccessBlockConfiguration path=BlockPublicPolicy output=b2
| spath input=PublicAccessBlockConfiguration path=IgnorePublicAcls output=b3
| spath input=PublicAccessBlockConfiguration path=RestrictPublicBuckets output=b4
| eval all_blocked=if(b1="true" AND b2="true" AND b3="true" AND b4="true", "true", "false")
| where all_blocked="false"
| table BucketName b1 b2 b3 b4

Security groups that allow SSH from anywhere:

| awsget kind=security-groups region=us-east-1 detail=true
| spath path=SecurityGroupIngress{} output=rules
| mvexpand rules
| spath input=rules
| eval source=coalesce(CidrIp, CidrIpv6), lo=coalesce(FromPort, 0), hi=coalesce(ToPort, 65535)
| where (source="0.0.0.0/0" OR source="::/0") AND (IpProtocol="-1" OR IpProtocol="tcp") AND lo<=22 AND hi>=22
| table GroupId GroupName IpProtocol FromPort ToPort source

Lambda functions still on a deprecated runtime:

| awsget kind=lambdas account=* region=*
| where Runtime IN ("nodejs16.x", "python3.6", "python3.7", "python3.8", "python3.9", "ruby2.7", "go1.x", "java8", "dotnetcore3.1")
| table account region FunctionName Runtime

CloudWatch alarms that would notify nobody - actions switched off, or no alarm action at all. | awsget reads an alarm’s configuration; its current state (OK, ALARM) is not part of what the Cloud Control API returns, so this is a posture check, not a list of what is firing:

| awsget kind=alarms region=us-east-1
| spath input=_raw output=actions path="AlarmActions{}"
| eval action_count=coalesce(mvcount(actions), 0), actions_enabled=coalesce(ActionsEnabled, "true")
| where actions_enabled="false" OR action_count=0
| table AlarmName Namespace MetricName actions_enabled action_count

An inventory of IAM users, the starting point for an access review. IAM users list by name only, so detail=true fetches each one’s full record:

| awsget kind=users account=prod detail=true
| table UserName Arn

A user’s record does not carry its access keys or their age. To see what one key has been doing, ask CloudTrail for it:

| awscloudtrail attribute=AccessKeyId value=AKIAIOSFODNN7EXAMPLE start=-7d account=prod
| stats count by event_source event_name

Incidents and errors

Lambda timeouts in the last hour, by function:

| awslogs groups=/aws/lambda/* filter="Task timed out" start=-1h region=us-east-1
| where isnull(notice)
| stats count by logGroup
| sort - count

These are counts of the newest events limit= allows each group, not of the whole window. When a group holds more than its share, the command adds a row with a notice field saying so; where isnull(notice) keeps that row out of the count. For a count over the whole window, use a Logs Insights query= instead.

Timeouts and crashes for one function, over time - a crash logs Process exited before completing or Runtime exited with error rather than a timeout, and a Logs Insights query counts both. This is the query behind the Lambda detail dashboard’s failures panel:

| awslogs groups=/aws/lambda/my-function query="stats sum(@message like /Task timed out/) as timeouts, sum(@message like /Process exited before completing|Runtime exited with error/) as crashes by bin(5m)" start=-1h region=us-east-1

Errors for one function from CloudWatch:

| awsmetrics namespace=AWS/Lambda metric=Errors dimensions=FunctionName=payments-api stat=Sum period=300 start=-3h region=us-east-1
| timechart span=5m sum(value) as errors

Errors for every function at once, with a Metrics Insights query - one series per function, named in label:

| awsmetrics expression="SELECT SUM(Errors) FROM SCHEMA(\"AWS/Lambda\", FunctionName) GROUP BY FunctionName" period=300 start=-3h region=us-east-1
| stats sum(value) as errors by label
| where errors>0
| sort - errors

What changed before the incident - management events in the window, by who:

| awscloudtrail start=-2h end=-1h region=us-east-1
| where read_only="false"
| stats count values(event_name) as events by username event_source

Who did what

Console logins and where from:

| awscloudtrail attribute=EventName value=ConsoleLogin start=-7d region=us-east-1
| spath path=sourceIPAddress output=source_ip
| table _time username source_ip

Every write action taken with one access key:

| awscloudtrail attribute=AccessKeyId value=AKIAEXAMPLE start=-30d region=us-east-1
| where read_only="false"
| table _time event_source event_name

The CloudTrail - Event Explorer and Principal investigation dashboards package these with pickers; see Dashboards.

Cost

Cost Explorer bills per request, so these are searches to run on purpose, not to schedule every minute.

This month by service:

| awscost granularity=DAILY group_by=SERVICE start=@mon account=prod
| stats sum(UnblendedCost) as cost by group
| sort - cost

Cost by team tag, month over month:

| awscost granularity=MONTHLY group_by=TAG:team start=-2mon@mon account=*
| chart sum(UnblendedCost) as cost over group by start

Tags and governance

Tagged resources missing the owner tag, by service:

| awstag account=prod region=us-east-1
| where isnull(tag_owner)
| rex field=arn "arn:aws:(?<service>[^:]+):"
| stats count by service

This covers resources that carry at least one tag, or did once: the Tagging API does not return a resource that was never tagged, so a count of zero here does not mean everything has an owner. For a complete answer about one kind of resource, list that kind and read its tags:

| awsget kind=instances account=prod region=us-east-1 detail=true
| spath path=Tags{}.Key output=tag_keys
| eval has_owner=if(isnotnull(mvfind(tag_keys, "^owner$")), "yes", "no")
| where has_owner="no"
| table InstanceId tag_keys

Everything tagged for one environment:

| awstag tag="env=prod" account=* region=*
| stats count by account region

Athena and the catalog

Explore a database before querying it:

| awsglue resource=tables database=cloudtrail_logs region=us-east-1

Then query it:

| awsathena database=cloudtrail_logs region=us-east-1
    query="SELECT useridentity.arn, count(*) AS calls FROM cloudtrail WHERE eventtime > '2026-09-01' GROUP BY 1 ORDER BY 2 DESC LIMIT 25"

Alerting on live AWS state

Turn any of these into an alert: build the search, choose Save As - Alert, set a schedule, and trigger on the result count (for example, “number of results is greater than 0”). Because each run queries AWS live, the alert evaluates the account’s current state every time it fires - ideal for “something is wrong right now” conditions: a bucket whose public-access block came off, a security group opened to the world, a function on a deprecated runtime, an alarm whose actions were switched off.

Three things follow from how scheduled searches run:

  • An inventory search is a snapshot. | awsget sees the account at the moment it runs, not a window of history: “a security group allows 0.0.0.0/0 right now” is a good alert, “which groups were open last Tuesday” is a question for ingested data. The time-series commands are different - | awsmetrics, | awslogs, | awscloudtrail, and | awscost take start= and end=, or the search’s own time range - so a scheduled search can ask CloudWatch for last week’s errors and invocations without ingesting anything.
  • Know which credential an alert runs with. A scheduled search runs as its owner, so it uses the owner’s per-user credential for the account when they have one, and the account’s shared credential otherwise. For an identity that does not depend on who saved the alert, give alerts an owner with no per-user credential, and point them at an account whose shared credential is read-only and scoped to what the alert needs.
  • Mind the cache and the bill. A result cached for a minute will not reflect a change from ten seconds ago; add cache=0 when timing matters. Do not schedule | awscost or Logs Insights queries on a tight cadence - both are billed per call or per GB scanned.

Example - alert when a security group opens SSH to the world. Use the security-group search above, save it as an alert, schedule it every few minutes with cache=0, and trigger when the result count is greater than zero. The triggered alert lists exactly which groups and rules matched, so the notification is actionable on its own.