-
-
Notifications
You must be signed in to change notification settings - Fork 2
/
lambda.py
237 lines (177 loc) · 7.22 KB
/
lambda.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
import os
import sys
import ssl
import smtplib
import traceback
from time import gmtime, strftime
import boto3
import botocore
DEFAULT_REGION = 'us-east-1'
BACKUP_TAG_NAME = 'backup_policy'
BACKUP_DEFAULT_VALUE = 'grandfather-father-son'
BACKUP_TAG = {
'Key': BACKUP_TAG_NAME,
'Value': BACKUP_DEFAULT_VALUE
}
SMTP_PORT = 465
SMTP_HOST = 'email-smtp.us-east-1.amazonaws.com'
SMTP_SECURE = True
SMTP_USER_SECRET_NAME = 'ses/smtp_user'
SMTP_PASS_SECRET_NAME = 'ses/smtp_pass'
MAIL_TO = os.environ.get('MAIL_TO') or 'ops-team@example.com'
MAIL_FROM = os.environ.get('MAIL_FROM') or 'ops-team@example.com'
MAIL_SUBJECT_FMT = 'Default backup policy set for ARN %s'
def log(message):
timestamp = strftime('%Y-%m-%d %H:%M:%S', gmtime())
print('[%s] %s' % (timestamp, message))
def get_all_regions():
client = boto3.client('ec2', DEFAULT_REGION)
for region in client.describe_regions()['Regions']:
yield region['RegionName']
def send_email(subject, message):
subject_message = ('Subject: %s\n'
'\n'
'%s')
subject_message %= (subject, message)
secrets_client = boto3.client('secretsmanager')
smtp_user = secrets_client.get_secret_value(SecretId=SMTP_USER_SECRET_NAME)['SecretString']
smtp_pass = secrets_client.get_secret_value(SecretId=SMTP_PASS_SECRET_NAME)['SecretString']
context = ssl.create_default_context()
with smtplib.SMTP_SSL(SMTP_HOST, SMTP_PORT, context=context) as server:
server.login(smtp_user, smtp_pass)
server.sendmail(MAIL_FROM, MAIL_TO, subject_message)
def tag_rds(region):
rds_client = boto3.client('rds', region_name=region)
instances = rds_client.describe_db_instances()
for instance in instances['DBInstances']:
db_arn = instance['DBInstanceArn']
rds_tags = rds_client.list_tags_for_resource(ResourceName=db_arn)['TagList']
rds_tags = [rds_tag['Key'].lower() for rds_tag in rds_tags]
if BACKUP_TAG_NAME in rds_tags:
# This RDS instance has already been tagged
continue
#
# Need to tag this RDS instance and notify the ops team
#
rds_client.add_tags_to_resource(
ResourceName=db_arn,
Tags=[BACKUP_TAG, ]
)
notify_missing_tag(db_arn)
def tag_ebs(region):
ec2_client = boto3.client('ec2', region_name=region)
volumes = ec2_client.describe_volumes()
for volume in volumes['Volumes']:
volume_id = volume['VolumeId']
volume_tags = ec2_client.describe_tags(Filters=[{'Name': 'resource-id',
'Values': [volume_id]}])['Tags']
volume_tags = [volume_tag['Key'].lower() for volume_tag in volume_tags]
if BACKUP_TAG_NAME in volume_tags:
# This volume has already been tagged
continue
#
# Need to tag this volume and notify the ops team
#
ec2_client.create_tags(
Resources=[volume_id],
Tags=[BACKUP_TAG, ]
)
notify_missing_tag(volume_id)
def tag_efs(region):
efs_client = boto3.client('efs', region_name=region)
file_systems = efs_client.describe_file_systems()
for file_system in file_systems['FileSystems']:
file_system_id = file_system['FileSystemId']
file_system_tags = efs_client.describe_tags(FileSystemId=file_system_id)['Tags']
file_system_tags = [file_system_tag['Key'].lower() for file_system_tag in file_system_tags]
if BACKUP_TAG_NAME in file_system_tags:
# This file system has already been tagged
continue
#
# Need to tag this table and notify the ops team
#
efs_client.create_tags(
FileSystemId=file_system_id,
Tags=[BACKUP_TAG, ]
)
notify_missing_tag(file_system_id)
def tag_dynamodb(region):
dynamodb_client = boto3.client('dynamodb', region_name=region)
tables = dynamodb_client.list_tables()
for table_name in tables['TableNames']:
table_description = dynamodb_client.describe_table(TableName=table_name)
table_arn = table_description['Table']['TableArn']
table_tags = dynamodb_client.list_tags_of_resource(ResourceArn=table_arn)['Tags']
table_tags = [table_tag['Key'].lower() for table_tag in table_tags]
if BACKUP_TAG_NAME in table_tags:
# This table has already been tagged
continue
#
# Need to tag this table and notify the ops team
#
dynamodb_client.tag_resource(
ResourceArn=table_arn,
Tags=[BACKUP_TAG, ]
)
notify_missing_tag(table_arn)
def notify_missing_tag(arn):
message = ('The resource with ARN %s had no %s tags.\n'
'\n'
'The default tag "%s: %s" was added to force this resource to have backups.\n'
'\n'
'Please review if the default backup policy is adequate for this resource.'
' Apply any changes using terraform configuration files by adding tags to'
' the newly created resource.\n'
'\n'
'The list of potential backup plans to use for this resource can be'
'retrieved using:\n'
'\n'
'aws backup list-backup-plans | jq ".BackupPlansList[] | .BackupPlanName"\n'
'\n')
args = (arn, BACKUP_TAG_NAME, BACKUP_TAG_NAME, BACKUP_DEFAULT_VALUE)
log(message % args)
# Send the email notification
send_email(MAIL_SUBJECT_FMT % arn, message % args)
TAG_FUNCTIONS = {
tag_rds,
tag_ebs,
tag_efs,
tag_dynamodb
}
def handle(event, context):
"""
Identify RDS, EBS, EFS and DynamoDB resources in the AWS account which
do not have a `backup_policy` tag and:
* Notify the ops team
* Add a tag with `backup_policy: daily_two_weeks`
This tag is used in backup.tf to select which resources to backup using
AWS Backup.
"""
log('Start backup_auto_tagging')
success = True
for tag_function in TAG_FUNCTIONS:
for region in get_all_regions():
try:
tag_function(region)
except botocore.exceptions.EndpointConnectionError as e:
#
# This is most likely because one of the regions returned by
# get_all_regions() does not support the service we want to
# query.
#
# Got this with tag_efs() and sa-east-1
#
args = (region, tag_function.__name__)
log('%s does not support %s' % args)
continue
except Exception as e:
# Send error message to log
args = (tag_function.__name__, e)
log('%s raised an exception: %s' % args)
# Detailed traceback to log
exc_type, exc_value, exc_traceback = sys.exc_info()
traceback.print_tb(exc_traceback, file=sys.stdout)
# Store failure and continue with the next function
success = False
log('End backup_auto_tagging')
return success