class Policy(Construct): def __init__(self, scope: Construct, id: str) -> None: super().__init__(scope, id) policy = ManagedPolicy(self, "Policy") # (de)registerTaskDefinitions doesn't support specific resources ecs_task = PolicyStatement( actions=[ "ecs:DeregisterTaskDefinition", "ecs:RegisterTaskDefinition", ], resources=["*"], ) # ListTagsForResource cannot be set for a service only, but has to be # on the cluster (despite it only looking at the tags for the service). # We make a separate statement for this, to avoid giving other polcies # more rights than required, as they can be per service. self._cluster_statement = PolicyStatement(actions=[ "ecs:ListTagsForResource", ], ) # All other actions can be combined, as they don't collide. As policies # have a maximum amount of bytes they can consume, this spares a few of # them. self._statement = PolicyStatement(actions=[ "iam:PassRole", "ssm:GetParameter", "ssm:GetParameters", "ssm:PutParameter", "ecs:UpdateService", "ecs:DescribeServices", "cloudformation:UpdateStack", "cloudformation:DescribeStacks", ], ) policy.add_statements(ecs_task) policy.add_statements(self._cluster_statement) policy.add_statements(self._statement) def add_role(self, role: IRole) -> None: self._statement.add_resources(role.role_arn) def add_parameter(self, parameter: IParameter) -> None: self._statement.add_resources(parameter.parameter_arn) def add_service(self, service: IService) -> None: self._statement.add_resources(service.service_arn) def add_cluster(self, cluster: ICluster) -> None: self._cluster_statement.add_resources(cluster.cluster_arn) def add_stack(self, stack: Stack) -> None: self._statement.add_resources(stack.stack_id)
def create_event_handling( self, secrets: List[secretsmanager.Secret], slack_host_ssm_name: str, slack_webhook_ssm_name: str, ) -> lambda_.Function: """ Args: secrets: a list of secrets that we will track for events slack_host_ssm_name: the SSM parameter name for the slack host slack_webhook_ssm_name: the SSM parameter name for the slack webhook id Returns: a lambda event handler """ dirname = os.path.dirname(__file__) filename = os.path.join(dirname, "runtime/notify_slack") env = { # for the moment we don't parametrise at the CDK level.. only needed if this is liable to change "SLACK_HOST_SSM_NAME": slack_host_ssm_name, "SLACK_WEBHOOK_SSM_NAME": slack_webhook_ssm_name, } notifier = lambda_.Function( self, "NotifySlack", runtime=lambda_.Runtime.PYTHON_3_8, code=lambda_.AssetCode(filename), handler="lambda_entrypoint.main", timeout=Duration.minutes(1), environment=env, ) get_ssm_policy = PolicyStatement() # there is some weirdness around SSM parameter ARN formation and leading slashes.. can't be bothered # looking into right now - as the ones we want to use do a have a leading slash # but put in this exception in case if not slack_webhook_ssm_name.startswith( "/") or not slack_host_ssm_name.startswith("/"): raise Exception( "SSM parameters need to start with a leading slash") # see here - the *required* slash between parameter and the actual name uses the leading slash from the actual # name itself.. which is wrong.. get_ssm_policy.add_resources( f"arn:aws:ssm:*:*:parameter{slack_host_ssm_name}") get_ssm_policy.add_resources( f"arn:aws:ssm:*:*:parameter{slack_webhook_ssm_name}") get_ssm_policy.add_actions("ssm:GetParameter") notifier.add_to_role_policy(get_ssm_policy) # we want a rule that traps all the rotation failures for our JWT secrets rule = Rule( self, "NotifySlackRule", ) rule.add_event_pattern( source=["aws.secretsmanager"], detail={ # at the moment only interested in these - add extra events into this array if wanting more "eventName": ["RotationFailed", "RotationSucceeded"], "additionalEventData": { "SecretId": list(map(lambda s: s.secret_arn, secrets)) }, }, ) rule.add_target(LambdaFunction(notifier)) return notifier