Repository: cloud-custodian/cloud-custodian

Issue:
Add action to enable GCP Firewall Rule Logging
I manage security for GCP projects that consist of large number of existing Firewall Rules and new ones get created very often. The gcp.firewall value filter `"logConfig": {"enable": false}` tells me if logging is enabled or not. However, it would be great to have an action that can actually enable logging. The use case is that we have 3rd party SIEM that sucks up all of our GCP network related logs and this firewall rule log is very useful for their security analytics/alerting purpose.

More information about Firewall Rules Logging:
https://cloud.google.com/vpc/docs/firewall-rules-logging


Discussion:

Resolving patch:
diff --git a/tools/c7n_gcp/c7n_gcp/resources/network.py b/tools/c7n_gcp/c7n_gcp/resources/network.py
--- a/tools/c7n_gcp/c7n_gcp/resources/network.py
+++ b/tools/c7n_gcp/c7n_gcp/resources/network.py
@@ -158,6 +158,53 @@ def get_resource_params(self, m, r):
         return {'project': project, 'firewall': resource_name}
 
 
+@Firewall.action_registry.register('modify')
+class ModifyFirewall(MethodAction):
+    """Modify filtered Firewall Rules
+
+    :example: Enable logging on filtered firewalls
+
+    .. yaml:
+
+     policies:
+       - name: enable-firewall-logging
+         resource: gcp.firewall
+         filters:
+         - type: value
+           key: name
+           value: no-logging
+         actions:
+         - type: modify
+           logConfig:
+             enabled: true
+    """
+
+    schema = type_schema(
+        'modify',
+        **{'description': {'type': 'string'},
+           'network': {'type': 'string'},
+           'priority': {'type': 'number'},
+           'sourceRanges': {'type': 'array', 'items': {'type': 'string'}},
+           'destinationRanges': {'type': 'array', 'items': {'type': 'string'}},
+           'sourceTags': {'type': 'array', 'items': {'type': 'string'}},
+           'targetTags': {'type': 'array', 'items': {'type': 'string'}},
+           'sourceServiceAccounts': {'type': 'array', 'items': {'type': 'string'}},
+           'targetServiceAccounts': {'type': 'array', 'items': {'type': 'string'}},
+           'allowed': {'type': 'array', 'items': {'type': 'object'}},
+           'denied': {'type': 'array', 'items': {'type': 'object'}},
+           'direction': {'enum': ['INGRESS', 'EGRESS']},
+           'logConfig': {'type': 'object'},
+           'disabled': {'type': 'boolean'}})
+    method_spec = {'op': 'patch'}
+    permissions = ('compute.networks.updatePolicy', 'compute.firewalls.update')
+    path_param_re = re.compile('.*?/projects/(.*?)/global/firewalls/(.*)')
+
+    def get_resource_params(self, m, r):
+        project, resource_name = self.path_param_re.match(
+            r['selfLink']).groups()
+        return {'project': project, 'firewall': resource_name, 'body': self.data}
+
+
 @resources.register('router')
 class Router(QueryResourceManager):
     """GCP resource: https://cloud.google.com/compute/docs/reference/rest/v1/routers


Test patch:
diff --git a/tools/c7n_gcp/tests/data/flights/firewall-modify/get-compute-v1-projects-cloud-custodian-global-firewalls-test_1.json b/tools/c7n_gcp/tests/data/flights/firewall-modify/get-compute-v1-projects-cloud-custodian-global-firewalls-test_1.json
new file mode 100644
--- /dev/null
+++ b/tools/c7n_gcp/tests/data/flights/firewall-modify/get-compute-v1-projects-cloud-custodian-global-firewalls-test_1.json
@@ -0,0 +1,48 @@
+{
+  "headers": {
+    "etag": "mKIkXzG_CsXtACJ-pjHU1QvbY6E=/XOjuCw1ioqrMA3GKAKOgT_Op8EQ=",
+    "content-type": "application/json; charset=UTF-8",
+    "vary": "Origin, X-Origin, Referer",
+    "date": "Wed, 17 Mar 2021 19:54:48 GMT",
+    "server": "ESF",
+    "cache-control": "private",
+    "x-xss-protection": "0",
+    "x-frame-options": "SAMEORIGIN",
+    "x-content-type-options": "nosniff",
+    "alt-svc": "h3-29=\":443\"; ma=2592000,h3-T051=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"",
+    "transfer-encoding": "chunked",
+    "status": "200",
+    "content-length": "656",
+    "-content-encoding": "gzip",
+    "content-location": "https://compute.googleapis.com/compute/v1/projects/cloud-custodian/global/firewalls/test?alt=json"
+  },
+  "body": {
+    "id": "4853772226933023804",
+    "creationTimestamp": "2021-03-17T12:38:59.990-07:00",
+    "name": "test",
+    "description": "",
+    "network": "https://www.googleapis.com/compute/v1/projects/cloud-custodian/global/networks/default",
+    "priority": 500,
+    "sourceRanges": [
+      "1.1.1.1"
+    ],
+    "targetTags": [
+      "newtag"
+    ],
+    "allowed": [
+      {
+        "IPProtocol": "tcp",
+        "ports": [
+          "22"
+        ]
+      }
+    ],
+    "direction": "INGRESS",
+    "logConfig": {
+      "enable": false
+    },
+    "disabled": false,
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/cloud-custodian/global/firewalls/test",
+    "kind": "compute#firewall"
+  }
+}
\ No newline at end of file
diff --git a/tools/c7n_gcp/tests/data/flights/firewall-modify/get-compute-v1-projects-cloud-custodian-global-firewalls_1.json b/tools/c7n_gcp/tests/data/flights/firewall-modify/get-compute-v1-projects-cloud-custodian-global-firewalls_1.json
new file mode 100644
--- /dev/null
+++ b/tools/c7n_gcp/tests/data/flights/firewall-modify/get-compute-v1-projects-cloud-custodian-global-firewalls_1.json
@@ -0,0 +1,55 @@
+{
+  "headers": {
+    "etag": "9V13kgN2MxsEq9IbD5kJQ0R-UXI=/hu_9Mer_bwfDfMhzv5q0EJNCBwA=",
+    "content-type": "application/json; charset=UTF-8",
+    "vary": "Origin, X-Origin, Referer",
+    "date": "Wed, 17 Mar 2021 19:54:41 GMT",
+    "server": "ESF",
+    "cache-control": "private",
+    "x-xss-protection": "0",
+    "x-frame-options": "SAMEORIGIN",
+    "x-content-type-options": "nosniff",
+    "alt-svc": "h3-29=\":443\"; ma=2592000,h3-T051=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"",
+    "transfer-encoding": "chunked",
+    "status": "200",
+    "content-length": "5237",
+    "-content-encoding": "gzip",
+    "content-location": "https://compute.googleapis.com/compute/v1/projects/cloud-custodian/global/firewalls?alt=json"
+  },
+  "body": {
+    "id": "projects/cloud-custodian/global/firewalls",
+    "items": [
+      {
+        "id": "4853772226933023804",
+        "creationTimestamp": "2021-03-17T12:38:59.990-07:00",
+        "name": "test",
+        "description": "",
+        "network": "https://www.googleapis.com/compute/v1/projects/cloud-custodian/global/networks/default",
+        "priority": 1000,
+        "sourceRanges": [
+          "1.1.1.1"
+        ],
+        "targetTags": [
+          "oldtag"
+        ],
+        "allowed": [
+          {
+            "IPProtocol": "tcp",
+            "ports": [
+              "22"
+            ]
+          }
+        ],
+        "direction": "INGRESS",
+        "logConfig": {
+          "enable": false
+        },
+        "disabled": false,
+        "selfLink": "https://www.googleapis.com/compute/v1/projects/cloud-custodian/global/firewalls/test",
+        "kind": "compute#firewall"
+      }
+    ],
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/cloud-custodian/global/firewalls",
+    "kind": "compute#firewallList"
+  }
+}
\ No newline at end of file
diff --git a/tools/c7n_gcp/tests/data/flights/firewall-modify/patch-compute-v1-projects-cloud-custodian-global-firewalls-test_1.json b/tools/c7n_gcp/tests/data/flights/firewall-modify/patch-compute-v1-projects-cloud-custodian-global-firewalls-test_1.json
new file mode 100644
--- /dev/null
+++ b/tools/c7n_gcp/tests/data/flights/firewall-modify/patch-compute-v1-projects-cloud-custodian-global-firewalls-test_1.json
@@ -0,0 +1,30 @@
+{
+  "headers": {
+    "content-type": "application/json; charset=UTF-8",
+    "vary": "Origin, X-Origin, Referer",
+    "date": "Wed, 17 Mar 2021 19:54:42 GMT",
+    "server": "ESF",
+    "cache-control": "private",
+    "x-xss-protection": "0",
+    "x-frame-options": "SAMEORIGIN",
+    "x-content-type-options": "nosniff",
+    "alt-svc": "h3-29=\":443\"; ma=2592000,h3-T051=\":443\"; ma=2592000,h3-Q050=\":443\"; ma=2592000,h3-Q046=\":443\"; ma=2592000,h3-Q043=\":443\"; ma=2592000,quic=\":443\"; ma=2592000; v=\"46,43\"",
+    "transfer-encoding": "chunked",
+    "status": "200",
+    "content-length": "644",
+    "-content-encoding": "gzip"
+  },
+  "body": {
+    "id": "8915306104775237741",
+    "name": "operation-1616010881632-5bdc0da8d88e9-c6119e51-58d48670",
+    "operationType": "patch",
+    "targetLink": "https://www.googleapis.com/compute/v1/projects/cloud-custodian/global/firewalls/test",
+    "targetId": "4853772226933023804",
+    "status": "RUNNING",
+    "progress": 0,
+    "insertTime": "2021-03-17T12:54:42.014-07:00",
+    "startTime": "2021-03-17T12:54:42.023-07:00",
+    "selfLink": "https://www.googleapis.com/compute/v1/projects/cloud-custodian/global/operations/operation-1616010881632-5bdc0da8d88e9-c6119e51-58d48670",
+    "kind": "compute#operation"
+  }
+}
\ No newline at end of file
diff --git a/tools/c7n_gcp/tests/test_network.py b/tools/c7n_gcp/tests/test_network.py
--- a/tools/c7n_gcp/tests/test_network.py
+++ b/tools/c7n_gcp/tests/test_network.py
@@ -19,6 +19,25 @@ def test_firewall_get(self):
             'project_id': 'cloud-custodian'})
         self.assertEqual(fw['name'], 'allow-inbound-xyz')
 
+    def test_firewall_modify(self):
+        project_id = 'cloud-custodian'
+        factory = self.replay_flight_data('firewall-modify', project_id=project_id)
+        p = self.load_policy(
+            {'name': 'fdelete',
+             'resource': 'gcp.firewall',
+             'filters': [{'name': 'test'}],
+             'actions': [{'type': 'modify', 'priority': 500, 'targetTags': ['newtag']}]
+             },
+            session_factory=factory)
+        resources = p.run()
+        self.assertEqual(len(resources), 1)
+        if self.recording:
+            time.sleep(5)
+        client = p.resource_manager.get_client()
+        result = client.execute_query('get', {'project': project_id, 'firewall': 'test'})
+        self.assertEqual(result["targetTags"][0], 'newtag')
+        self.assertEqual(result["priority"], 500)
+
     def test_firewall_delete(self):
         project_id = 'cloud-custodian'
         factory = self.replay_flight_data('firewall-delete', project_id=project_id)