Repository: cloud-custodian/cloud-custodian

Issue:
aws - ec2 set-traffic-mirror-session enhancement
Add an action to the ec2 resource which invokes the create-traffic-mirror-session passing the ec2 instances network-interface-id.  Also add the delete-taffic-mirror-mession action.

Use Case: Based on a finding on a Production instance, enable traffic mirroring on that instance.  For findings with confidence less than that which justifies immediate isolation/termination/stopping, enabling traffic mirroring can supply the responder with additional data, while not having traffic mirroring enabled full time supports cost optimization.


Discussion:
we don't do enable/disable separate actions anymore, this would be spelled `set-traffic-mirror` which has a state boolean
also this should also be on an action on an eni, although I'm curious if this is allowed on non owned enis.

Resolving patch:
diff --git a/c7n/resources/resource_map.py b/c7n/resources/resource_map.py
--- a/c7n/resources/resource_map.py
+++ b/c7n/resources/resource_map.py
@@ -133,6 +133,8 @@
     "aws.log-metric": "c7n.resources.cw.LogMetric",
     "aws.message-broker": "c7n.resources.mq.MessageBroker",
     "aws.message-config": "c7n.resources.mq.MessageConfig",
+    "aws.mirror-session": "c7n.resources.vpc.TrafficMirrorSession",
+    "aws.mirror-target": "c7n.resources.vpc.TrafficMirrorTarget",
     "aws.ml-model": "c7n.resources.ml.MLModel",
     "aws.nat-gateway": "c7n.resources.vpc.NATGateway",
     "aws.network-acl": "c7n.resources.vpc.NetworkAcl",
diff --git a/c7n/resources/vpc.py b/c7n/resources/vpc.py
--- a/c7n/resources/vpc.py
+++ b/c7n/resources/vpc.py
@@ -2654,3 +2654,57 @@ def process(self, resources):
                 client.modify_subnet_attribute,
                 SubnetId=r['SubnetId'], **params)
         return resources
+
+
+@resources.register('mirror-session')
+class TrafficMirrorSession(query.QueryResourceManager):
+
+    class resource_type(query.TypeInfo):
+        service = 'ec2'
+        enum_spec = ('describe_traffic_mirror_sessions', 'TrafficMirrorSessions', None)
+        name = id = 'TrafficMirrorSessionId'
+        cfn_type = 'AWS::EC2::TrafficMirrorSession'
+        arn_type = 'traffic-mirror-session'
+        universal_taggable = object()
+        id_prefix = 'tms-'
+
+
+@TrafficMirrorSession.action_registry.register('delete')
+class DeleteTrafficMirrorSession(BaseAction):
+    """Action to delete traffic mirror session(s)
+
+    :example:
+
+    .. code-block:: yaml
+
+            policies:
+              - name: traffic-mirror-session-paclength
+                resource: mirror-session
+                filters:
+                  - type: value
+                    key: tag:Owner
+                    value: xyz
+                actions:
+                  - delete
+    """
+
+    schema = type_schema('delete')
+    permissions = ('ec2:DeleteTrafficMirrorSession',)
+
+    def process(self, resources):
+        client = local_session(self.manager.session_factory).client('ec2')
+        for r in resources:
+            client.delete_traffic_mirror_session(TrafficMirrorSessionId=r['TrafficMirrorSessionId'])
+
+
+@resources.register('mirror-target')
+class TrafficMirrorTarget(query.QueryResourceManager):
+
+    class resource_type(query.TypeInfo):
+        service = 'ec2'
+        enum_spec = ('describe_traffic_mirror_targets', 'TrafficMirrorTargets', None)
+        name = id = 'TrafficMirrorTargetId'
+        cfn_type = 'AWS::EC2::TrafficMirrorTarget'
+        arn_type = 'traffic-mirror-target'
+        universal_taggable = object()
+        id_prefix = 'tmt-'


Test patch:
diff --git a/tests/data/placebo/test_traffic_mirror_session_delete/ec2.DeleteTrafficMirrorSession_1.json b/tests/data/placebo/test_traffic_mirror_session_delete/ec2.DeleteTrafficMirrorSession_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_traffic_mirror_session_delete/ec2.DeleteTrafficMirrorSession_1.json
@@ -0,0 +1,7 @@
+{
+    "status_code": 200,
+    "data": {
+        "TrafficMirrorSessionId": "tms-084dc356a819e99ae",
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_traffic_mirror_session_delete/ec2.DescribeTrafficMirrorSessions_1.json b/tests/data/placebo/test_traffic_mirror_session_delete/ec2.DescribeTrafficMirrorSessions_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_traffic_mirror_session_delete/ec2.DescribeTrafficMirrorSessions_1.json
@@ -0,0 +1,24 @@
+{
+    "status_code": 200,
+    "data": {
+        "TrafficMirrorSessions": [
+            {
+                "TrafficMirrorSessionId": "tms-084dc356a819e99ae",
+                "TrafficMirrorTargetId": "tmt-02cc3955d41358894",
+                "TrafficMirrorFilterId": "tmf-003b318669f29b788",
+                "NetworkInterfaceId": "eni-0054f909285935030",
+                "OwnerId": "644160558196",
+                "PacketLength": 19,
+                "SessionNumber": 1,
+                "VirtualNetworkId": 5918387,
+                "Tags": [
+                    {
+                        "Key": "Name",
+                        "Value": "test-session"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_traffic_mirror_session_delete/ec2.DescribeTrafficMirrorSessions_2.json b/tests/data/placebo/test_traffic_mirror_session_delete/ec2.DescribeTrafficMirrorSessions_2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_traffic_mirror_session_delete/ec2.DescribeTrafficMirrorSessions_2.json
@@ -0,0 +1,7 @@
+{
+    "status_code": 200,
+    "data": {
+        "TrafficMirrorSessions": [],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/data/placebo/test_traffic_mirror_target/ec2.DescribeTrafficMirrorTargets_1.json b/tests/data/placebo/test_traffic_mirror_target/ec2.DescribeTrafficMirrorTargets_1.json
new file mode 100644
--- /dev/null
+++ b/tests/data/placebo/test_traffic_mirror_target/ec2.DescribeTrafficMirrorTargets_1.json
@@ -0,0 +1,20 @@
+{
+    "status_code": 200,
+    "data": {
+        "TrafficMirrorTargets": [
+            {
+                "TrafficMirrorTargetId": "tmt-02cc3955d41358894",
+                "NetworkInterfaceId": "eni-06dd77d6d23aa41e5",
+                "Type": "network-interface",
+                "OwnerId": "644160558196",
+                "Tags": [
+                    {
+                        "Key": "Owner",
+                        "Value": "pratyush"
+                    }
+                ]
+            }
+        ],
+        "ResponseMetadata": {}
+    }
+}
\ No newline at end of file
diff --git a/tests/test_vpc.py b/tests/test_vpc.py
--- a/tests/test_vpc.py
+++ b/tests/test_vpc.py
@@ -3218,3 +3218,45 @@ def test_subnet_modify_attributes(self):
             SubnetIds=[resources[0]['SubnetId']])
         MapPublicIpOnLaunch = ModifiedSubnet["Subnets"][0]["MapPublicIpOnLaunch"]
         self.assertEqual(MapPublicIpOnLaunch, False)
+
+
+class TrafficMirror(BaseTest):
+
+    def test_traffic_mirror_session_delete(self):
+        session_factory = self.replay_flight_data('test_traffic_mirror_session_delete')
+        p = self.load_policy({
+            'name': 'traffic-mirror-session-delete',
+            'resource': 'mirror-session',
+            "filters": [
+                {"tag:Owner": "absent"}
+            ],
+            "actions": [
+                {
+                    "type": "delete"
+                }
+            ],
+        },
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(resources[0]["TrafficMirrorSessionId"], "tms-084dc356a819e99ae")
+        client = session_factory(region="us-east-1").client("ec2")
+        if self.recording:
+            time.sleep(5)
+        self.assertEqual(
+            client.describe_traffic_mirror_sessions().get('TrafficMirrorSessions'), [])
+
+    def test_traffic_mirror_target(self):
+        session_factory = self.replay_flight_data('test_traffic_mirror_target')
+        p = self.load_policy({
+            'name': 'traffic-mirror-target',
+            'resource': 'mirror-target',
+            "filters": [
+                {"tag:Owner": "present"}
+            ],
+        },
+            session_factory=session_factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        self.assertEqual(resources[0]["TrafficMirrorTargetId"], "tmt-02cc3955d41358894")
+        self.assertEqual(resources[0]['Tags'], [{"Key": "Owner", "Value": "pratyush"}])