AWS Modify Cloud Compute Infrastructure


Description

Detection when EC2 compute infrastructure is modified outside of expected automation methods.

Query · python

EC2_CRUD_ACTIONS = {
    "AssociateIamInstanceProfile",
    "AssociateInstanceEventWindow",
    "BundleInstance",
    "CancelSpotInstanceRequests",
    "ConfirmProductInstance",
    "CreateInstanceEventWindow",
    "CreateInstanceExportTask",
    "DeleteInstanceEventWindow",
    "DeregisterInstanceEventNotificationAttributes",
    "DisassociateIamInstanceProfile",
    "DisassociateInstanceEventWindow",
    "ImportInstance",
    "ModifyInstanceAttribute",
    "ModifyInstanceCapacityReservationAttributes",
    "ModifyInstanceCreditSpecification",
    "ModifyInstanceEventStartTime",
    "ModifyInstanceEventWindow",
    "ModifyInstanceMaintenanceOptions",
    "ModifyInstanceMetadataOptions",
    "ModifyInstancePlacement",
    "MonitorInstances",
    "RegisterInstanceEventNotificationAttributes",
    "ReportInstanceStatus",
    "RequestSpotInstances",
    "ResetInstanceAttribute",
    "RunInstances",
    "RunScheduledInstances",
    "StartInstances",
    "StopInstances",
    "TerminateInstances",
    "UnmonitorInstances",
}


def rule(event):
    # Disqualify any eventSource that is not ec2
    if event.get("eventSource", "") != "ec2.amazonaws.com":
        return False
    if event.get("readOnly"):
        return False
    # Disqualify AWS Service-Service operations, which can appear in a variety of forms
    if (
        # FYI there is a weird quirk in the sourceIPAddress field of CloudTrail
        #  events with ec2.amazonaws.com as the source name where users of the
        #  web-console will have their sourceIPAddress recorded as "AWS Internal"
        #  though their userIdentity will be more normal.
        #  Example cloudtrail event in the "Terminate instance From WebUI with assumedRole" test
        event.get("sourceIPAddress", "").endswith(".amazonaws.com")
        or event.deep_get("userIdentity", "type", default="") == "AWSService"
        or event.deep_get("userIdentity", "invokedBy", default="") == "AWS Internal"
        or event.deep_get("userIdentity", "invokedBy", default="").endswith(".amazonaws.com")
    ):
        return False
    # Dry run operations get logged as SES Internal in the sourceIPAddress
    #  but not in the invokedBy field
    if event.get("errorCode", "") == "Client.DryRunOperation":
        return False
    # Disqualify any eventNames that do not Include instance
    # and events that have readOnly set to false
    if event.get("eventName", "") in EC2_CRUD_ACTIONS:
        return True
    return False


def title(event):
    items = event.deep_get(
        "requestParameters", "instancesSet", "items", default=[{"instanceId": "none"}]
    )
    return (
        f"AWS Event [{event.get('eventName')}] Instance ID "
        f"[{items[0].get('instanceId')}] AWS Account ID [{event.get('recipientAccountId')}]"
    )


def dedup(event):
    items = event.deep_get(
        "requestParameters",
        "instancesSet",
        "items",
        default=[{"instanceId": "INSTANCE_ID_NOT_FOUND"}],
    )
    return items[0].get("instanceId", "INSTANCE_ID_NOT_FOUND")


def alert_context(event):
    items = event.deep_get(
        "requestParameters", "instancesSet", "items", default=[{"instanceId": "none"}]
    )
    return {
        "awsRegion": event.get("awsRegion"),
        "eventName": event.get("eventName"),
        "recipientAccountId": event.get("recipientAccountId"),
        "instanceId": items[0].get("instanceId"),
    }

Analyst notes

This detection reports on eventSource ec2 Change events. This detection excludes Cross-Service change events. As such, this detection will perform well in environments where changes are expected to originate only from AWS service entities.

This detection will emit alerts frequently in environments where users are making ec2 related changes.

Raw source AWS Modify Cloud Compute Infrastructure · Panther Python
Esc
Published by panther-labs/panther-analysis ↗, licensed under Apache 2.0 ↗. Reproduced here unmodified.
AnalysisType: rule
Description: Detection when EC2 compute infrastructure is modified outside of expected automation methods.
DisplayName: "AWS Modify Cloud Compute Infrastructure"
Enabled: false
Filename: aws_modify_cloud_compute_infrastructure.py
Reference: https://attack.mitre.org/techniques/T1578/
Severity: Medium
Reports:
  MITRE ATT&CK:
    - TA0005:T1578
Tags:
  # Note: This detection doesn't require configuration. It carries the Configuration Required
  # tag due to checks for Enabled:false and Pack content
  - Configuration Required
Runbook: |
  This detection reports on eventSource ec2 Change events. This detection excludes Cross-Service
  change events.  As such, this detection will perform well in environments where changes are
  expected to originate only from AWS service entities.

  This detection will emit alerts frequently in environments where users are
  making ec2 related changes.
Tests:
  - ExpectedResult: true
    Log:
      { awsRegion: "us-west-2",
          eventID: "59e8d6b8-de7b-43ca-961f-0c6f4531fcf0",
          eventName: "TerminateInstances",
          eventSource: "ec2.amazonaws.com",
          eventTime: "2021-10-29 23:50:09",
          eventType: "AwsApiCall",
          eventVersion: "1.08",
          managementEvent: true,
          p_any_aws_account_ids:
            [ "111222333444" ],
          p_any_aws_arns:
            [ "arn:aws:iam::111222333444:role/FakeRole" ],
          p_any_aws_instance_ids:
            [ "i-0d9853f67e40ab80b" ],
          p_any_domain_names:
            [ "ec2.amazonaws.com" ],
          p_event_time: "2021-10-29 23:50:09",
          p_log_type: "AWS.CloudTrail",
          p_parse_time: "2021-10-29 23:54:06.45",
          p_row_id: "e6f7bd65083bfeb7feced38f0da18a01",
          p_source_id: "5f9f0f60-9c56-4027-b93a-8bab3019f0f1",
          p_source_label: "SomeCloudTrail",
          readOnly: false,
          recipientAccountId: "111222333444",
          requestID: "a520eeaf-c258-4260-954e-b4a976e6c72b",
          requestParameters:
            { instancesSet:
                { items:
                    [ { instanceId: "i-0d9853f67e40ab80b" } ] } },
          responseElements:
            { instancesSet:
                { items:
                    [ { currentState:
                          { code: 32,
                            name: "shutting-down" },
                        instanceId: "i-0d9853f67e40ab80b",
                        previousState:
                          { code: 16,
                            name: "running" } } ],
                  requestId: "a520eeaf-c258-4260-954e-b4a976e6c72b" }},
          userIdentity:
            { accountId: "111222333444",
                arn: "arn:aws:sts::111222333444:assumed-role/SomeRole/AThing",
                sessionContext:
                  { attributes:
                      { creationDate: "2021-10-29T23:50:08Z",
                          mfaAuthenticated: "false" },
                      webIdFederationData: { } },
                type: "AssumedRole" } }
    Name: Terminate Instance from AssumedRole
  - ExpectedResult: false
    Log:
      { awsRegion: "us-west-2",
          eventID: "59e8d6b8-de7b-43ca-961f-0c6f4531fcf0",
          eventName: "TerminateInstances",
          eventSource: "ec2.amazonaws.com",
          eventTime: "2021-10-29 23:50:09",
          eventType: "AwsApiCall",
          eventVersion: "1.08",
          managementEvent: true,
          p_any_aws_account_ids:
            [ "111222333444" ],
          p_any_aws_arns:
            [ "arn:aws:iam::111222333444:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling",
              "arn:aws:sts::111222333444:assumed-role/AWSServiceRoleForAutoScaling/AutoScaling" ],
          p_any_aws_instance_ids:
            [ "i-0d9853f67e40ab80b" ],
          p_any_domain_names:
            [ "autoscaling.amazonaws.com" ],
          p_event_time: "2021-10-29 23:50:09",
          p_log_type: "AWS.CloudTrail",
          p_parse_time: "2021-10-29 23:54:06.45",
          p_row_id: "e6f7bd65083bfeb7feced38f0da18a01",
          p_source_id: "5f9f0f60-9c56-4027-b93a-8bab3019f0f1",
          p_source_label: "SomeCloudTrail",
          readOnly: false,
          recipientAccountId: "111222333444",
          requestID: "a520eeaf-c258-4260-954e-b4a976e6c72b",
          requestParameters:
            { instancesSet:
                { items:
                    [ { instanceId: "i-0d9853f67e40ab80b" } ] } },
          responseElements:
            { instancesSet:
                { items:
                    [ { currentState:
                          { code: 32,
                              name: "shutting-down" },
                          instanceId: i-0d9853f67e40ab80b,
                          previousState:
                            { code: 16,
                                name: "running" } } ] },
                requestId: "a520eeaf-c258-4260-954e-b4a976e6c72b" },
          sourceIPAddress: "autoscaling.amazonaws.com",
          userAgent: "autoscaling.amazonaws.com",
          userIdentity:
            { accountId: "111222333444",
                arn: "arn:aws:sts::111222333444:assumed-role/AWSServiceRoleForAutoScaling/AutoScaling",
                invokedBy: "autoscaling.amazonaws.com",
                principalId: "AROATSZWD7TDLUEWEUXXI:AutoScaling",
                sessionContext:
                  { attributes:
                      { creationDate: "2021-10-29T23:50:08Z",
                          mfaAuthenticated: "false" },
                      sessionIssuer:
                        { accountId: "111222333444",
                            arn: "arn:aws:iam::111222333444:role/aws-service-role/autoscaling.amazonaws.com/AWSServiceRoleForAutoScaling",
                            principalId: "AROATSZWD7TDLUEWEUXXI",
                            type: "Role",
                            userName: "AWSServiceRoleForAutoScaling" },
                      webIdFederationData: { } },
                type: "AssumedRole" } }
    Name: Terminate Instance from autoscaling
  - ExpectedResult: false
    Log:
      { additionalEventData:
          { insufficientLakeFormationPermissions:
              [ "panther_rule_errors:gsuite_activityevent" ],
              lakeFormationPrincipal: "arn:aws:iam::111222333444:role/panther-Panther-4JL51Q6AU6SH-LogAnal-CompactorRole-W1WCIV3PHU0S" },
          awsRegion: "us-east-1",
          errorCode: "EntityNotFoundException",
          errorMessage: "Cannot find partition.",
          eventID: "8780fc6b-7742-4a45-b757-c351a54c79b8",
          eventName: "GetPartition",
          eventSource: "glue.amazonaws.com",
          eventTime: "2021-10-14 14:21:22",
          eventType: "AwsApiCall",
          eventVersion: "1.08",
          managementEvent: true,
          p_any_aws_account_ids:
            [ "111222333444" ],
          p_any_aws_arns:
            [ "arn:aws:iam::111222333444:role/panther-Panther-4JL51Q6AU6SH-LogAnal-CompactorRole-W1WCIV3PHU0S",
              "arn:aws:sts::111222333444:assumed-role/panther-Panther-4JL51Q6AU6SH-LogAnal-CompactorRole-W1WCIV3PHU0S/panther-datacatalog-compactor" ],
          p_any_ip_addresses:
            [ "54.90.94.136" ],
          p_event_time: "2021-10-14 14:21:22",
          p_log_type: "AWS.CloudTrail",
          p_parse_time: "2021-10-14 14:27:06.54",
          p_row_id: "7235e51c49e780a5a4e281e90c850c",
          p_source_id: "5f9f0f60-9c56-4027-b93a-8bab3019f0f1",
          p_source_label: "SomeCloudTrail",
          readOnly: true,
          recipientAccountId: "111222333444",
          requestID: "cdb450f7-1cd8-463b-8449-71274d95a5a3",
          requestParameters:
            { databaseName: "panther_rule_errors",
                partitionValues:
                  [ "2021",
                    "10",
                    "13",
                    "19",
                    "1634151600" ],
                tableName: "gsuite_activityevent" },
          sourceIPAddress: "54.90.94.136",
          userAgent: "aws-sdk-go/1.40.21 (go1.17; linux; amd64) exec-env/AWS_Lambda_go1.x",
          userIdentity:
            { accessKeyId: "ASIAJMVY5WC5K4TDFNFA",
                accountId: "111222333444",
                arn: "arn:aws:sts::111222333444:assumed-role/panther-Panther-4JL51Q6AU6SH-LogAnal-CompactorRole-W1WCIV3PHU0S/panther-datacatalog-compactor",
                principalId: "AROA4UN2W2PXWZMJ2L3PC:panther-datacatalog-compactor",
                sessionContext:
                  { attributes:
                      { creationDate: "2021-10-14T14:20:28Z",
                          mfaAuthenticated: "false" },
                      sessionIssuer:
                        { accountId: "111222333444",
                            arn: "arn:aws:iam::111222333444:role/panther-Panther-4JL51Q6AU6SH-LogAnal-CompactorRole-W1WCIV3PHU0S",
                            principalId: "AROA4UN2W2PXWZMJ2L3PC",
                            type: "Role",
                            userName: "panther-Panther-4JL51Q6AU6SH-LogAnal-CompactorRole-W1WCIV3PHU0S" },
                      webIdFederationData: { } },
                type: "AssumedRole" } }
    Name: Get Partition
  - Name: Terminate instance From WebUI with assumedRole
    ExpectedResult: true
    Log:
      {
        "awsRegion": "us-west-2",
        "eventCategory": "Management",
        "eventID": "01f39d3b-4a26-4045-bb36-1e57b7d07997",
        "eventName": "RunInstances",
        "eventSource": "ec2.amazonaws.com",
        "eventTime": "2022-10-14 00:35:36",
        "eventType": "AwsApiCall",
        "eventVersion": "1.08",
        "managementEvent": true,
        "p_any_aws_account_ids": ["123412341234"],
        "p_any_aws_arns":
          [
            "arn:aws:iam::123412341234:role/SomeRole",
            "arn:aws:sts::123412341234:assumed-role/SomeRole/person",
          ],
        "p_any_aws_instance_ids": ["i-0690cd354a0c3850c"],
        "p_any_aws_tags": ["Name:fake thing whatever"],
        "p_any_domain_names":
          ["AWS Internal", "ip-10-1-0-14.us-west-2.compute.internal"],
        "p_any_ip_addresses": ["10.1.0.14"],
        "p_any_trace_ids": ["ASIARLIVEKVNGOY5UABO"],
        "p_any_usernames": ["SomeRole"],
        "p_event_time": "2022-10-14 00:35:36",
        "p_log_type": "AWS.CloudTrail",
        "p_parse_time": "2022-10-14 00:38:26.875",
        "p_row_id": "76663c86299fc3f2fa94acfd13f29311",
        "p_source_id": "125a8146-e3ea-454b-aed7-9e08e735b670",
        "p_source_label": "SomeCloudTrail",
        "readOnly": false,
        "recipientAccountId": "123412341234",
        "requestID": "557e2d68-904a-4fe7-81d4-33c056444a13",
        "requestParameters":
          {
            "blockDeviceMapping": {},
            "disableApiStop": false,
            "disableApiTermination": false,
            "ebsOptimized": false,
            "instanceType": "t2.micro",
            "instancesSet":
              {
                "items":
                  [
                    {
                      "imageId": "ami-08e2d37b6a0129927",
                      "maxCount": 1,
                      "minCount": 1,
                    },
                  ],
              },
            "monitoring": { "enabled": false },
            "networkInterfaceSet":
              {
                "items":
                  [
                    {
                      "associatePublicIpAddress": false,
                      "deviceIndex": 0,
                      "groupSet":
                        { "items": [{ "groupId": "sg-0077c778d7ad1f5f2" }] },
                      "subnetId": "subnet-0e3a508e43776c435",
                    },
                  ],
              },
            "privateDnsNameOptions":
              {
                "enableResourceNameDnsAAAARecord": false,
                "enableResourceNameDnsARecord": true,
                "hostnameType": "ip-name",
              },
            "tagSpecificationSet":
              {
                "items":
                  [
                    {
                      "resourceType": "instance",
                      "tags":
                        [{ "key": "Name", "value": "fake thing whatever" }],
                    },
                  ],
              },
          },
        "responseElements":
          {
            "groupSet": {},
            "instancesSet":
              {
                "items":
                  [
                    {
                      "amiLaunchIndex": 0,
                      "architecture": "x86_64",
                      "blockDeviceMapping": {},
                      "capacityReservationSpecification":
                        { "capacityReservationPreference": "open" },
                      "cpuOptions": { "coreCount": 1, "threadsPerCore": 1 },
                      "ebsOptimized": false,
                      "enaSupport": true,
                      "enclaveOptions": { "enabled": false },
                      "groupSet":
                        {
                          "items":
                            [
                              {
                                "groupId": "sg-0077c778d7ad1f5f2",
                                "groupName": "launch-wizard-1",
                              },
                            ],
                        },
                      "hypervisor": "xen",
                      "imageId": "ami-08e2d37b6a0129927",
                      "instanceId": "i-0690cd354a0c3850c",
                      "instanceState": { "code": 0, "name": "pending" },
                      "instanceType": "t2.micro",
                      "launchTime": 1665707736000,
                      "maintenanceOptions": { "autoRecovery": "default" },
                      "metadataOptions":
                        {
                          "httpEndpoint": "enabled",
                          "httpProtocolIpv4": "enabled",
                          "httpProtocolIpv6": "disabled",
                          "httpPutResponseHopLimit": 1,
                          "httpTokens": "optional",
                          "instanceMetadataTags": "disabled",
                          "state": "pending",
                        },
                      "monitoring": { "state": "disabled" },
                      "networkInterfaceSet":
                        {
                          "items":
                            [
                              {
                                "attachment":
                                  {
                                    "attachTime": 1665707736000,
                                    "attachmentId": "eni-attach-0b2f21743e26b7c8a",
                                    "deleteOnTermination": true,
                                    "deviceIndex": 0,
                                    "networkCardIndex": 0,
                                    "status": "attaching",
                                  },
                                "groupSet":
                                  {
                                    "items":
                                      [
                                        {
                                          "groupId": "sg-0077c778d7ad1f5f2",
                                          "groupName": "launch-wizard-1",
                                        },
                                      ],
                                  },
                                "interfaceType": "interface",
                                "ipv6AddressesSet": {},
                                "macAddress": "02:50:9d:da:61:79",
                                "networkInterfaceId": "eni-0f4e4d2d71627dd38",
                                "ownerId": "123412341234",
                                "privateDnsName": "ip-10-1-0-14.us-west-2.compute.internal",
                                "privateIpAddress": "10.1.0.14",
                                "privateIpAddressesSet":
                                  {
                                    "item":
                                      [
                                        {
                                          "primary": true,
                                          "privateDnsName": "ip-10-1-0-14.us-west-2.compute.internal",
                                          "privateIpAddress": "10.1.0.14",
                                        },
                                      ],
                                  },
                                "sourceDestCheck": true,
                                "status": "in-use",
                                "subnetId": "subnet-0e3a508e43776c435",
                                "tagSet": {},
                                "vpcId": "vpc-04fe895571b367c47",
                              },
                            ],
                        },
                      "placement":
                        {
                          "availabilityZone": "us-west-2a",
                          "tenancy": "default",
                        },
                      "privateDnsName": "ip-10-1-0-14.us-west-2.compute.internal",
                      "privateDnsNameOptions":
                        {
                          "enableResourceNameDnsAAAARecord": false,
                          "enableResourceNameDnsARecord": true,
                          "hostnameType": "ip-name",
                        },
                      "privateIpAddress": "10.1.0.14",
                      "productCodes": {},
                      "rootDeviceName": "/dev/xvda",
                      "rootDeviceType": "ebs",
                      "sourceDestCheck": true,
                      "stateReason":
                        { "code": "pending", "message": "pending" },
                      "subnetId": "subnet-0e3a508e43776c435",
                      "tagSet":
                        {
                          "items":
                            [{ "key": "Name", "value": "fake thing whatever" }],
                        },
                      "virtualizationType": "hvm",
                      "vpcId": "vpc-04fe895571b367c47",
                    },
                  ],
              },
            "ownerId": "123412341234",
            "requestId": "557e2d68-904a-4fe7-81d4-33c056444a13",
            "reservationId": "r-0de81af4b677c0252",
          },
        "sessionCredentialFromConsole": true,
        "sourceIPAddress": "AWS Internal",
        "userAgent": "AWS Internal",
        "userIdentity":
          {
            "accessKeyId": "ASIARLIVEKVNGOY5UABO",
            "accountId": "123412341234",
            "arn": "arn:aws:sts::123412341234:assumed-role/SomeRole/person",
            "principalId": "AROARLIVEKVNIRVGDLJWJ:person",
            "sessionContext":
              {
                "attributes":
                  {
                    "creationDate": "2022-10-14T00:34:59Z",
                    "mfaAuthenticated": "true",
                  },
                "sessionIssuer":
                  {
                    "accountId": "123412341234",
                    "arn": "arn:aws:iam::123412341234:role/SomeRole",
                    "principalId": "AROARLIVEKVNIRVGDLJWJ",
                    "type": "Role",
                    "userName": "SomeRole",
                  },
                "webIdFederationData": {},
              },
            "type": "AssumedRole",
          },
      }
  - Name: Weird AWS Internal Message
    ExpectedResult: false
    Log:
      {
        "awsRegion": "us-west-2",
        "errorCode": "Client.DryRunOperation",
        "errorMessage": "Request would have succeeded, but DryRun flag is set.",
        "eventCategory": "Management",
        "eventID": "b32f82e6-7375-4487-85f3-442face5eab4",
        "eventName": "RunInstances",
        "eventSource": "ec2.amazonaws.com",
        "eventTime": "2022-10-13 16:34:53",
        "eventType": "AwsApiCall",
        "eventVersion": "1.08",
        "managementEvent": true,
        "p_any_aws_account_ids": ["123412341234"],
        "p_any_aws_arns":
          [
            "arn:aws:iam::123412341234:role/funky-role-doing-dryrun",
            "arn:aws:sts::123412341234:assumed-role/funky-role-doing-dryrun/1665678811",
          ],
        "p_any_domain_names": ["AWS Internal"],
        "p_any_usernames": ["funky-role-doing-dryrun"],
        "p_event_time": "2022-10-13 16:34:53",
        "p_log_type": "AWS.CloudTrail",
        "p_parse_time": "2022-10-13 16:35:54.489",
        "p_row_id": "6650c5fe395984e0fdb7c1fc138ef507",
        "p_source_id": "125a8146-e3ea-454b-aed7-9e08e735b670",
        "p_source_label": "SomeCloudTrail",
        "readOnly": false,
        "recipientAccountId": "123412341234",
        "requestID": "99288e91-d3a5-494b-9fe6-9fdc02646a16",
        "requestParameters":
          {
            "blockDeviceMapping": {},
            "clientToken": "80a552a2-30ee-419e-bd73-3ee36b1242d6",
            "disableApiStop": false,
            "disableApiTermination": false,
            "instanceType": "m5.xlarge",
            "instancesSet":
              {
                "items":
                  [
                    {
                      "imageId": "ami-05074c40f29040248",
                      "maxCount": 1,
                      "minCount": 1,
                    },
                  ],
              },
            "launchTemplate":
              { "launchTemplateId": "lt-064c1a4dbc97b01fc", "version": "5" },
            "monitoring": { "enabled": false },
            "subnetId": "subnet-00559b970d3a60983",
          },
        "sourceIPAddress": "AWS Internal",
        "userAgent": "AWS Internal",
        "userIdentity":
          {
            "accountId": "123412341234",
            "arn": "arn:aws:sts::123412341234:assumed-role/funky-role-doing-dryrun/astronomer-managed-1665678811",
            "invokedBy": "AWS Internal",
            "principalId": "AROAZBD2CNPWEWKWTLX67:astronomer-managed-1665678811",
            "sessionContext":
              {
                "attributes":
                  {
                    "creationDate": "2022-10-13T16:33:31Z",
                    "mfaAuthenticated": "false",
                  },
                "sessionIssuer":
                  {
                    "accountId": "123412341234",
                    "arn": "arn:aws:iam::123412341234:role/funky-role-doing-dryrun",
                    "principalId": "AROAZBD2CNPWEWKWTLX67",
                    "type": "Role",
                    "userName": "funky-role-doing-dryrun",
                  },
                "webIdFederationData": {},
              },
            "type": "AssumedRole",
          },
      }
DedupPeriodMinutes: 120
LogTypes:
  - AWS.CloudTrail
RuleID: "AWS.Modify.Cloud.Compute.Infrastructure"
Threshold: 1


# ------ paired body: aws_modify_cloud_compute_infrastructure.py ------

EC2_CRUD_ACTIONS = {
    "AssociateIamInstanceProfile",
    "AssociateInstanceEventWindow",
    "BundleInstance",
    "CancelSpotInstanceRequests",
    "ConfirmProductInstance",
    "CreateInstanceEventWindow",
    "CreateInstanceExportTask",
    "DeleteInstanceEventWindow",
    "DeregisterInstanceEventNotificationAttributes",
    "DisassociateIamInstanceProfile",
    "DisassociateInstanceEventWindow",
    "ImportInstance",
    "ModifyInstanceAttribute",
    "ModifyInstanceCapacityReservationAttributes",
    "ModifyInstanceCreditSpecification",
    "ModifyInstanceEventStartTime",
    "ModifyInstanceEventWindow",
    "ModifyInstanceMaintenanceOptions",
    "ModifyInstanceMetadataOptions",
    "ModifyInstancePlacement",
    "MonitorInstances",
    "RegisterInstanceEventNotificationAttributes",
    "ReportInstanceStatus",
    "RequestSpotInstances",
    "ResetInstanceAttribute",
    "RunInstances",
    "RunScheduledInstances",
    "StartInstances",
    "StopInstances",
    "TerminateInstances",
    "UnmonitorInstances",
}


def rule(event):
    # Disqualify any eventSource that is not ec2
    if event.get("eventSource", "") != "ec2.amazonaws.com":
        return False
    if event.get("readOnly"):
        return False
    # Disqualify AWS Service-Service operations, which can appear in a variety of forms
    if (
        # FYI there is a weird quirk in the sourceIPAddress field of CloudTrail
        #  events with ec2.amazonaws.com as the source name where users of the
        #  web-console will have their sourceIPAddress recorded as "AWS Internal"
        #  though their userIdentity will be more normal.
        #  Example cloudtrail event in the "Terminate instance From WebUI with assumedRole" test
        event.get("sourceIPAddress", "").endswith(".amazonaws.com")
        or event.deep_get("userIdentity", "type", default="") == "AWSService"
        or event.deep_get("userIdentity", "invokedBy", default="") == "AWS Internal"
        or event.deep_get("userIdentity", "invokedBy", default="").endswith(".amazonaws.com")
    ):
        return False
    # Dry run operations get logged as SES Internal in the sourceIPAddress
    #  but not in the invokedBy field
    if event.get("errorCode", "") == "Client.DryRunOperation":
        return False
    # Disqualify any eventNames that do not Include instance
    # and events that have readOnly set to false
    if event.get("eventName", "") in EC2_CRUD_ACTIONS:
        return True
    return False


def title(event):
    items = event.deep_get(
        "requestParameters", "instancesSet", "items", default=[{"instanceId": "none"}]
    )
    return (
        f"AWS Event [{event.get('eventName')}] Instance ID "
        f"[{items[0].get('instanceId')}] AWS Account ID [{event.get('recipientAccountId')}]"
    )


def dedup(event):
    items = event.deep_get(
        "requestParameters",
        "instancesSet",
        "items",
        default=[{"instanceId": "INSTANCE_ID_NOT_FOUND"}],
    )
    return items[0].get("instanceId", "INSTANCE_ID_NOT_FOUND")


def alert_context(event):
    items = event.deep_get(
        "requestParameters", "instancesSet", "items", default=[{"instanceId": "none"}]
    )
    return {
        "awsRegion": event.get("awsRegion"),
        "eventName": event.get("eventName"),
        "recipientAccountId": event.get("recipientAccountId"),
        "instanceId": items[0].get("instanceId"),
    }

Detection rules belong to the projects that publish them and remain under their own licenses. This site indexes and links to them; it claims no rights in them.