Skip to content

Commit

Permalink
aws - account - managed config rule (cloud-custodian#7029)
Browse files Browse the repository at this point in the history
  • Loading branch information
darrendao authored Dec 14, 2022
1 parent 90e1623 commit f7b59e1
Show file tree
Hide file tree
Showing 24 changed files with 4,125 additions and 1 deletion.
18 changes: 18 additions & 0 deletions c7n/filters/missing.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ class Missing(Filter):
Intended for use at a logical account/subscription/project level
This works as an effectively an embedded policy thats evaluated.
:example:
Notify if an s3 bucket is missing
.. code-block:: yaml
policies:
- name: missing-s3-bucket
resource: account
filters:
- type: missing
policy:
resource: s3
filters:
- Name: my-bucket
actions:
- notify
"""
schema = type_schema(
'missing',
Expand Down
204 changes: 204 additions & 0 deletions c7n/resources/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import time
import datetime
import jmespath
from contextlib import suppress
from botocore.exceptions import ClientError
from fnmatch import fnmatch
from dateutil.parser import parse as parse_date
Expand Down Expand Up @@ -1944,6 +1945,209 @@ def process_account(self, account):
return True


@actions.register('toggle-config-managed-rule')
class ToggleConfigManagedRule(BaseAction):
"""Enables or disables an AWS Config Managed Rule
:example:
.. code-block:: yaml
policies:
- name: config-managed-s3-bucket-public-write-remediate-event
description: |
This policy detects if S3 bucket allows public write by the bucket policy
or ACL and remediates.
comment: |
This policy detects if S3 bucket policy or ACL allows public write access.
When the bucket is evaluated as 'NON_COMPLIANT', the action
'AWS-DisableS3BucketPublicReadWrite' is triggered and remediates.
resource: account
filters:
- type: missing
policy:
resource: config-rule
filters:
- type: remediation
rule_name: &rule_name 'config-managed-s3-bucket-public-write-remediate-event'
remediation: &remediation-config
TargetId: AWS-DisableS3BucketPublicReadWrite
Automatic: true
MaximumAutomaticAttempts: 5
RetryAttemptSeconds: 211
Parameters:
AutomationAssumeRole:
StaticValue:
Values:
- 'arn:aws:iam::{account_id}:role/myrole'
S3BucketName:
ResourceValue:
Value: RESOURCE_ID
actions:
- type: toggle-config-managed-rule
rule_name: *rule_name
managed_rule_id: S3_BUCKET_PUBLIC_WRITE_PROHIBITED
resource_types:
- 'AWS::S3::Bucket'
rule_parameters: '{}'
remediation: *remediation-config
"""

permissions = (
'config:DescribeConfigRules',
'config:DescribeRemediationConfigurations',
'config:PutRemediationConfigurations',
'config:PutConfigRule',
)

schema = type_schema('toggle-config-managed-rule',
enabled={'type': 'boolean', 'default': True},
rule_name={'type': 'string'},
rule_prefix={'type': 'string'},
managed_rule_id={'type': 'string'},
resource_types={'type': 'array', 'items':
{'pattern': '^AWS::*', 'type': 'string'}},
resource_tag={
'type': 'object',
'properties': {
'key': {'type': 'string'},
'value': {'type': 'string'},
},
'required': ['key', 'value'],
},
resource_id={'type': 'string'},
rule_parameters={'type': 'string'},
remediation={
'type': 'object',
'properties': {
'TargetType': {'type': 'string'},
'TargetId': {'type': 'string'},
'Automatic': {'type': 'boolean'},
'Parameters': {'type': 'object'},
'MaximumAutomaticAttempts': {
'type': 'integer',
'minimum': 1, 'maximum': 25,
},
'RetryAttemptSeconds': {
'type': 'integer',
'minimum': 1, 'maximum': 2678000,
},
'ExecutionControls': {'type': 'object'},
},
},
tags={'type': 'object'},
required=['rule_name'],
)

def validate(self):
if (
self.data.get('enabled', True) and
not self.data.get('managed_rule_id')
):
raise PolicyValidationError("managed_rule_id required to enable a managed rule")
return self

def process(self, accounts):
client = local_session(self.manager.session_factory).client('config')
rule = self.ConfigManagedRule(self.data)
params = self.get_rule_params(rule)

if self.data.get('enabled', True):
client.put_config_rule(**params)

if rule.remediation:
remediation_params = self.get_remediation_params(rule)
client.put_remediation_configurations(
RemediationConfigurations=[remediation_params]
)
else:
with suppress(client.exceptions.NoSuchRemediationConfigurationException):
client.delete_remediation_configuration(
ConfigRuleName=rule.name
)

with suppress(client.exceptions.NoSuchConfigRuleException):
client.delete_config_rule(
ConfigRuleName=rule.name
)

def get_rule_params(self, rule):
params = dict(
ConfigRuleName=rule.name,
Description=rule.description,
Source={
'Owner': 'AWS',
'SourceIdentifier': rule.managed_rule_id,
},
InputParameters=rule.rule_parameters
)

# A config rule scope can include one or more resource types,
# a combination of a tag key and value, or a combination of
# one resource type and one resource ID
params.update({'Scope': {'ComplianceResourceTypes': rule.resource_types}})
if rule.resource_tag:
params.update({'Scope': {
'TagKey': rule.resource_tag['key'],
'TagValue': rule.resource_tag['value']}
})
elif rule.resource_id:
params.update({'Scope': {'ComplianceResourceId': rule.resource_id}})

return dict(ConfigRule=params)

def get_remediation_params(self, rule):
rule.remediation['ConfigRuleName'] = rule.name
if 'TargetType' not in rule.remediation:
rule.remediation['TargetType'] = 'SSM_DOCUMENT'
return rule.remediation

class ConfigManagedRule:
"""Wraps the action data into an AWS Config Managed Rule.
"""

def __init__(self, data):
self.data = data

@property
def name(self):
prefix = self.data.get('rule_prefix', 'custodian-')
return "%s%s" % (prefix, self.data.get('rule_name', ''))

@property
def description(self):
return self.data.get(
'description', 'cloud-custodian AWS Config Managed Rule policy')

@property
def tags(self):
return self.data.get('tags', {})

@property
def resource_types(self):
return self.data.get('resource_types', [])

@property
def managed_rule_id(self):
return self.data.get('managed_rule_id', '')

@property
def resource_tag(self):
return self.data.get('resource_tag', {})

@property
def resource_id(self):
return self.data.get('resource_id', '')

@property
def rule_parameters(self):
return self.data.get('rule_parameters', '')

@property
def remediation(self):
return self.data.get('remediation', {})


@filters.register('ses-agg-send-stats')
class SesAggStats(ValueFilter):
"""This filter queries SES send statistics and aggregates all
Expand Down
106 changes: 105 additions & 1 deletion c7n/resources/config.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright The Cloud Custodian Authors.
# SPDX-License-Identifier: Apache-2.0
from c7n.actions import BaseAction
from c7n.filters import ValueFilter, CrossAccountAccessFilter
from c7n.filters import Filter, ValueFilter, CrossAccountAccessFilter
from c7n.manager import resources
from c7n.resolver import ValuesFrom
from c7n.query import QueryResourceManager, TypeInfo
Expand Down Expand Up @@ -126,3 +126,107 @@ def process(self, resources):
for r in resources:
client.delete_config_rule(
ConfigRuleName=r['ConfigRuleName'])


@ConfigRule.filter_registry.register('remediation')
class RuleRemediation(Filter):
"""Filter to look for config rules that match the given remediation configuration settings
This filter can be used in conjunction with account missing filter to look for
managed config rules with missing remediation and to enable it accordingly.
:example:
.. code-block:: yaml
policies:
- name: config-managed-s3-bucket-public-write-remediate-event-with-filter
description: |
This policy detects if S3 bucket allows public write by the bucket policy
or ACL and remediates.
comment: |
This policy detects if S3 bucket policy or ACL allows public write access.
When the bucket is evaluated as 'NON_COMPLIANT', the action
'AWS-DisableS3BucketPublicReadWrite' is triggered and remediates.
resource: account
filters:
- type: missing
policy:
resource: config-rule
filters:
- type: remediation
rule_name: &rule_name 'config-managed-s3-bucket-public-write-remediate-event'
remediation: &remediation-config
TargetId: AWS-DisableS3BucketPublicReadWrite
Automatic: true
MaximumAutomaticAttempts: 5
RetryAttemptSeconds: 211
Parameters:
AutomationAssumeRole:
StaticValue:
Values:
- 'arn:aws:iam::{account_id}:role/myrole'
S3BucketName:
ResourceValue:
Value: RESOURCE_ID
actions:
- type: toggle-config-managed-rule
rule_name: *rule_name
managed_rule_id: S3_BUCKET_PUBLIC_WRITE_PROHIBITED
resource_types:
- 'AWS::S3::Bucket'
rule_parameters: '{}'
remediation: *remediation-config
"""

schema = type_schema('remediation',
rule_name={'type': 'string'},
remediation={
'type': 'object',
'properties': {
'target_type': {'type': 'string'},
'target_id': {'type': 'string'},
'automatic': {'type': 'boolean'},
'parameters': {'type': 'object'},
'maximum_automatic_attempts': {
'type': 'integer',
'minimum': 1, 'maximum': 25,
},
'retry_attempt_seconds': {
'type': 'integer',
'minimum': 1, 'maximum': 2678000,
},
'execution_controls': {'type': 'object'},
},
},
)

schema_alias = False
permissions = ('config:DescribeRemediationConfigurations',)

def process(self, resources, event=None):
prefix = self.data.get('rule_prefix', 'custodian-')
rule_name = "%s%s" % (prefix, self.data['rule_name'])
results = [r for r in resources if r['ConfigRuleName'] == rule_name]

# no matched rule
if not results:
return []

client = local_session(self.manager.session_factory).client('config')
resp = client.describe_remediation_configurations(
ConfigRuleNames=[rule_name]
)

desired_remediation_config = self.data['remediation']
desired_remediation_config['ConfigRuleName'] = rule_name
if 'TargetType' not in desired_remediation_config:
desired_remediation_config['TargetType'] = 'SSM_DOCUMENT'

# check if matched rule has matched remediation configuration
for r in resp.get('RemediationConfigurations', []):
r.pop('Arn', None) # don't include this for comparison
if r == desired_remediation_config:
return results

return []
Loading

0 comments on commit f7b59e1

Please sign in to comment.