Skip to content

Commit

Permalink
- Added IASGPatchManager for automated Windows patching
Browse files Browse the repository at this point in the history
  • Loading branch information
gitwater committed Jun 30, 2021
1 parent 7203f40 commit e2d0820
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 85 deletions.
254 changes: 204 additions & 50 deletions src/paco/application/reseng_asg.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ def stack_name(self):
def init_resource(self):
# Create instance role
role_profile_arn = None
self.windows_log_groups = {
'Windows-System': '',
'Windows-Security': ''
}

if self.resource.instance_iam_role.enabled == False:
role_config_yaml = """
Expand Down Expand Up @@ -113,6 +117,7 @@ def init_resource(self):
},
)
if self.resource.instance_ami_type.startswith("windows") == False:
# Linux uses EC2LM
self.stack.hooks.add(
name='EC2LMUpdateInstances.' + self.resource.name,
stack_action='update',
Expand All @@ -122,24 +127,11 @@ def init_resource(self):
hook_arg=(bucket.paco_ref_parts, self.resource)
)
else:
# TODO: Make this work with Linux too
self.stack.hooks.add(
name='UpdateSSMAgent.' + self.resource.name,
stack_action=['create', 'update'],
stack_timing='post',
hook_method=self.asg_hook_update_ssm_agent,
cache_method=None,
hook_arg=self.resource
)
# TODO: Make this work with Linux too
self.stack.hooks.add(
name='UpdateCloudWatchAgent.' + self.resource.name,
stack_action=['create', 'update'],
stack_timing='post',
hook_method=self.asg_hook_update_cloudwatch_agent,
cache_method=None,
hook_arg=self.resource
)
# Windows uses SSM
self.update_windows_ssm_agent()
self.update_windows_cloudwatch_agent()
self.update_windows_patching()


# For ECS ASGs add an ECS Hook
if self.resource.ecs != None and self.resource.is_enabled() == True:
Expand All @@ -158,13 +150,188 @@ def get_ec2lm_cache_id(self, hook, hook_arg):
"EC2LM cache id"
return self.ec2lm_cache_id

def update_windows_patching(self):
pass

def asg_hook_update_ssm_agent(self, hook, asg):
ssm_ctl = self.paco_ctx.get_controller('SSM')
ssm_ctl.command_update_ssm_agent(asg, self.account_ctx, self.aws_region)

def update_windows_ssm_agent(self):
iam_policy_name = '-'.join([self.resource.name, 'ssmagent-policy'])
ssm_prefixed_name = prefixed_name(self.resource, 'paco_ssm', self.paco_ctx.legacy_flag)
# allows instance to create a LogGroup with any name - this is a requirement of the SSM Agent
# if you limit the resource to just the LogGroups names you want SSM to use, the agent will not work
ssm_log_group_arn = f"arn:aws:logs:{self.aws_region}:{self.account_ctx.id}:log-group:*"
ssm_log_stream_arn = f"arn:aws:logs:{self.aws_region}:{self.account_ctx.id}:log-group:{ssm_prefixed_name}:log-stream:*"
policy_config_yaml = f"""
policy_name: '{iam_policy_name}'
enabled: true
statement:
- effect: Allow
action:
- ssmmessages:CreateControlChannel
- ssmmessages:CreateDataChannel
- ssmmessages:OpenControlChannel
- ssmmessages:OpenDataChannel
- ec2messages:AcknowledgeMessage
- ec2messages:DeleteMessage
- ec2messages:FailMessage
- ec2messages:GetEndpoint
- ec2messages:GetMessages
- ec2messages:SendReply
- ssm:UpdateInstanceInformation
- ssm:ListInstanceAssociations
- ssm:DescribeInstanceProperties
- ssm:DescribeDocumentParameters
- ssm:PutInventory
- ssm:GetDeployablePatchSnapshotForInstance
- ssm:PutInventory
resource:
- '*'
- effect: Allow
action:
- s3:GetEncryptionConfiguration
- ssm:GetManifest
resource:
- '*'
- effect: Allow
action:
- s3:GetObject
resource:
- 'arn:aws:s3:::aws-ssm-{self.aws_region}/*'
- 'arn:aws:s3:::aws-windows-downloads-{self.aws_region}/*'
- 'arn:aws:s3:::amazon-ssm-{self.aws_region}/*'
- 'arn:aws:s3:::amazon-ssm-packages-{self.aws_region}/*'
- 'arn:aws:s3:::{self.aws_region}-birdwatcher-prod/*'
- 'arn:aws:s3:::patch-baseline-snapshot-{self.aws_region}/*'
- effect: Allow
action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:DescribeLogGroups
- logs:DescribeLogStreams
resource:
- {ssm_log_group_arn}
- effect: Allow
action:
- logs:PutLogEvents
resource:
- {ssm_log_stream_arn}
"""

iam_ctl = self.paco_ctx.get_controller('IAM')
iam_ctl.add_managed_policy(
role=self.resource.instance_iam_role,
resource=self.resource,
policy_name='policy',
policy_config_yaml=policy_config_yaml,
extra_ref_names=['ec2lm','ssmagent'],
)

# TODO: Make this work with Linux too
self.stack.hooks.add(
name='UpdateSSMAgent.' + self.resource.name,
stack_action=['create', 'update'],
stack_timing='post',
hook_method=self.asg_hook_update_ssm_agent,
cache_method=None,
hook_arg=self.resource
)

def update_windows_cloudwatch_agent(self):

iam_policy_name = '-'.join([self.resource.name, 'cloudwatchagent'])
policy_config_yaml = f"""
policy_name: '{iam_policy_name}'
enabled: true
statement:
- effect: Allow
resource: "*"
action:
- "cloudwatch:PutMetricData"
- "autoscaling:Describe*"
- "ec2:DescribeTags"
"""
policy_config_yaml += """ - "logs:CreateLogGroup"\n"""
log_group_resources = ""
log_stream_resources = ""
for log_group_name in self.windows_log_groups.keys():
lg_name = prefixed_name(self.resource, log_group_name, self.paco_ctx.legacy_flag)
self.windows_log_groups[log_group_name] = lg_name
log_group_resources += " - arn:aws:logs:{}:{}:log-group:{}:*\n".format(
self.aws_region,
self.account_ctx.id,
lg_name,
)
log_stream_resources += " - arn:aws:logs:{}:{}:log-group:{}:log-stream:*\n".format(
self.aws_region,
self.account_ctx.id,
lg_name,
)
policy_config_yaml += f"""
- effect: Allow
action:
- "logs:DescribeLogStreams"
- "logs:DescribeLogGroups"
- "logs:CreateLogStream"
resource:
{log_group_resources}
- effect: Allow
action:
- "logs:PutLogEvents"
resource:
{log_stream_resources}
"""
policy_name = 'policy_ssm_cloudwatchagent'
iam_ctl = self.paco_ctx.get_controller('IAM')
iam_ctl.add_managed_policy(
role=self.resource.instance_iam_role,
resource=self.resource,
policy_name='policy',
policy_config_yaml=policy_config_yaml,
extra_ref_names=['ssm','cloudwatchagent'],
)

# TODO: Make this work with Linux too
self.stack.hooks.add(
name='UpdateCloudWatchAgent.' + self.resource.name,
stack_action=['create', 'update'],
stack_timing='post',
hook_method=self.asg_hook_update_cloudwatch_agent,
cache_method=self.asg_hook_update_cloudwatch_agent_cache,
hook_arg=self.resource
)

def gen_windows_cloudwatch_agent_config(self):
# """Unused Metrics

# "PhysicalDisk": {
# "measurement": [
# "%% Disk Time",
# "Disk Write Bytes/sec",
# "Disk Read Bytes/sec",
# "Disk Writes/sec",
# "Disk Reads/sec"
# ],
# "metrics_collection_interval": 60,
# "resources": [
# "*"
# ]
# },
# "Processor": {
# "measurement": [
# "%% User Time",
# "%% Idle Time",
# "%% Interrupt Time"
# ],
# "metrics_collection_interval": 60,
# "resources": [
# "*"
# ]
# },
# """

def asg_hook_update_cloudwatch_agent(self, hook, asg):
ssm_ctl = self.paco_ctx.get_controller('SSM')
cloudwatch_config = """{
"logs": {
"logs_collected": {
Expand All @@ -180,7 +347,7 @@ def asg_hook_update_cloudwatch_agent(self, hook, asg):
"CRITICAL"
],
"event_name": "System",
"log_group_name": "Paco-Test-Windows-System",
"log_group_name": "%s",
"log_stream_name": "{instance_id}"
},
{
Expand All @@ -193,7 +360,7 @@ def asg_hook_update_cloudwatch_agent(self, hook, asg):
"CRITICAL"
],
"event_name": "Security",
"log_group_name": "Paco-Test-Windows-Security",
"log_group_name": "%s",
"log_stream_name": "{instance_id}"
}
]
Expand All @@ -210,7 +377,7 @@ def asg_hook_update_cloudwatch_agent(self, hook, asg):
"metrics_collected": {
"LogicalDisk": {
"measurement": [
"% Free Space"
"%% Free Space"
],
"metrics_collection_interval": 60,
"resources": [
Expand All @@ -219,37 +386,13 @@ def asg_hook_update_cloudwatch_agent(self, hook, asg):
},
"Memory": {
"measurement": [
"% Committed Bytes In Use"
"%% Committed Bytes In Use"
],
"metrics_collection_interval": 60
},
"Paging File": {
"measurement": [
"% Usage"
],
"metrics_collection_interval": 60,
"resources": [
"*"
]
},
"PhysicalDisk": {
"measurement": [
"% Disk Time",
"Disk Write Bytes/sec",
"Disk Read Bytes/sec",
"Disk Writes/sec",
"Disk Reads/sec"
],
"metrics_collection_interval": 60,
"resources": [
"*"
]
},
"Processor": {
"measurement": [
"% User Time",
"% Idle Time",
"% Interrupt Time"
"%% Usage"
],
"metrics_collection_interval": 60,
"resources": [
Expand All @@ -275,7 +418,18 @@ def asg_hook_update_cloudwatch_agent(self, hook, asg):
}
}
}
}"""
}""" % (self.windows_log_groups['Windows-System'], self.windows_log_groups['Windows-Security'])
return cloudwatch_config

def asg_hook_update_cloudwatch_agent_cache(self, hook, asg):
"Cache method for ECS ASG"
cloudwatch_config = self.gen_windows_cloudwatch_agent_config()
return md5sum(str_data=cloudwatch_config)


def asg_hook_update_cloudwatch_agent(self, hook, asg):
ssm_ctl = self.paco_ctx.get_controller('SSM')
cloudwatch_config = self.gen_windows_cloudwatch_agent_config()
ssm_ctl.command_update_cloudwatch_agent(asg, self.account_ctx, self.aws_region, cloudwatch_config)


Expand Down
24 changes: 23 additions & 1 deletion src/paco/cftemplates/asg.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import troposphere
import troposphere.autoscaling
import troposphere.policies
import troposphere.ssm
from io import StringIO
from awacs.aws import Allow, Statement, Policy, PolicyDocument, Principal, Action, Condition, StringEquals, StringLike
from enum import Enum
Expand Down Expand Up @@ -351,7 +352,7 @@ def __init__(
min_instances_in_service_param = self.create_cfn_parameter(
param_type='String',
name='MinInstancesInService',
description='Roling update minimum instances to remain in service during update.',
description='Rolling update minimum instances to remain in service during update.',
value=update_policy.min_instances_in_service
)

Expand Down Expand Up @@ -468,6 +469,27 @@ def __init__(
NotificationTargetARN=lifecycle_hook.notification_target_arn
)

if asg_config.patch_manager != None and asg_config.patch_manager.is_enabled():
patch_ssm_associate_dict = {
'AssociationName': f'OpusPatchBaseline{asg_config.patch_manager.operation}',
'Name': 'AWS-RunPatchBaseline',
'ScheduleExpression': asg_config.patch_manager.schedule_expression,
'Targets': [{
'Key': 'tag:Name',
'Values': [asg_config.get_aws_name()]
}],
'Parameters': {
'Operation': [asg_config.patch_manager.operation]
},
'WaitForSuccessTimeoutSeconds': 900
}
patch_ssm_associate_res = troposphere.ssm.Association.from_dict(
'PatchAssociation',
patch_ssm_associate_dict
)
template.add_resource(patch_ssm_associate_res)


def script_manager_ecs(self, ecs_group, asg_dict, asg_config, template):
idx=0
policy_statements = []
Expand Down
3 changes: 3 additions & 0 deletions src/paco/cftemplates/vpcendpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ def __init__(self, stack, paco_ctx):
'VpcEndpointType': 'Interface'
}

if endpoint.service in ['s3', 'ec2']:
endpoint_dict['PrivateDnsEnabled'] = False

endpoint_res = troposphere.ec2.VPCEndpoint.from_dict(
self.create_cfn_logical_id(endpoint_name),
endpoint_dict
Expand Down

0 comments on commit e2d0820

Please sign in to comment.