├── .env
├── .gitignore
├── LICENSE
├── README.md
├── explorer.py
├── images
├── ASG.png
├── Database-mysql.png
├── Database.png
├── Instance.png
├── InternetGateway.png
├── LoadBalancer.png
├── Subnet.png
└── VPC.png
├── mapall.py
└── requirements.txt
/.env:
--------------------------------------------------------------------------------
1 | use_env aws_map
2 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .cache
2 | .Python
3 | bin/*
4 | include/*
5 | lib/*
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2013 Daniel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of
6 | this software and associated documentation files (the "Software"), to deal in
7 | the Software without restriction, including without limitation the rights to
8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
9 | the Software, and to permit persons to whom the Software is furnished to do so,
10 | subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | aws-map
2 | ------------
3 | Generate basic graphviz/dot maps of your AWS deployments.
4 |
5 | installation
6 | ------------
7 |
8 | Debian
9 | ```
10 | $ pip install -r requirements.txt
11 | $ sudo apt-get install graphviz
12 | ```
13 |
14 | OSX
15 | ```
16 | $ pip install -r requirements.txt
17 | $ brew install graphviz
18 | ```
19 |
20 | windows
21 | ```
22 | https://www.debian.org
23 | ```
24 |
25 | running
26 | -------
27 |
28 | ```
29 | $ ./mapall.py --region us-east-1 | dot -Tpng > aws-map.png
30 | ```
31 |
32 | viewing the imaage on linux
33 | ```
34 | $ eog aws-map.png
35 | ```
36 |
37 | viewing the image on OSX
38 | ```
39 | $ open aws-map.png
40 | ```
41 |
42 | viewing the image on windows
43 | ```
44 | https://www.debian.org
45 | ```
46 |
47 | Options include specifying just one VPC to draw with:
48 | ./mapall.py --vpc vpc_123456
49 |
50 | Or specifying a subnet to draw with:
51 | ./mapall.py --subnet subnet_123456
52 |
53 | If you want to use [virtualenv](http://docs.python-guide.org/en/latest/dev/virtualenvs/):
54 |
55 | ```
56 | $ sudo apt-get install -y python-setuptools
57 | $ virtualenv -p /usr/bin/python2.7 venv
58 | $ source venv/bin/activate
59 | $ pip install -r requirements.txt
60 | $ ./mapall.py --region us-east-1 | dot -Tpng > aws-map.png
61 |
62 | # And to leave the virtual environment:
63 | $ deactivate
64 | ```
65 |
66 | Iterating
67 | ---------
68 | You can generate a map of each vpc or subnet individually. This is
69 | very useful if you have a large and complex setup where putting it
70 | all on a single page becomes spaghetti.
71 |
72 | ```
73 | $ ./mapall.py --iterate vpc
74 | $ ./mapall.py --iterate subnet
75 | ```
76 |
77 | Security Groups
78 | ---------------
79 | Normally security groups get in the way and obscure what you want
80 | to see so they aren't included. You can add them back with --security.
81 | Note that if you only want to map a single subnet you shouldn't
82 | turn security groups on as there is no easy way to determine which
83 | subnet a security group operates on - so it draws them all - leading
84 | to potentially huge, unusable maps.
85 |
86 | Cacheing
87 | --------
88 | The program will write the results of the aws query to a .cache
89 | directory and use that unless you specify --nocache. Cacheing is
90 | much faster than querying AWS everytime but obviously won't react
91 | to changes that are made.
92 |
93 | Region
94 | ------
95 | You must indicate a region for the queries. This can be through the
96 | --region CLI option, or the AWS_DEFAULT_REGION environment variable.
97 | If both are set, the CLI opton takes precedence.
98 |
99 |
100 | Thanks
101 | ----------
102 |
103 | With the effort of everyone below this project would not be possible.
104 |
105 | * @dwagon
106 | * @justinholmes
107 | * @joerayme
108 | * @hposca
109 | * @bjorand
110 | * @ngfw
111 |
112 |
--------------------------------------------------------------------------------
/explorer.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | import boto.ec2
3 | import boto.vpc
4 | from local_settings import AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY
5 | import pprint
6 |
7 | def region_connect(region_name):
8 | vpc_conn = boto.vpc.connect_to_region(region_name,
9 | aws_access_key_id=AWS_ACCESS_KEY_ID,
10 | aws_secret_access_key=AWS_SECRET_ACCESS_KEY)
11 | ec2_conn = boto.ec2.connect_to_region(region_name,
12 | aws_access_key_id=AWS_ACCESS_KEY_ID,
13 | aws_secret_access_key=AWS_SECRET_ACCESS_KEY)
14 |
15 | return vpc_conn
16 |
17 | def get_all_routetables(vpc_conn, filters={}):
18 | raw_route_tables = vpc_conn.get_all_route_tables(filters=filters)
19 | for rt in raw_route_tables:
20 | #pprint.pprint(rt.__dict__)
21 | for a in rt.associations:
22 | if not a.subnet_id:
23 | continue
24 | pprint.pprint(a.__dict__)
25 | for r in rt.routes:
26 | gateway = r.gateway_id
27 | if r.instance_id:
28 | gateway = r.instance_id
29 | print "%-20s -> %s" % (r.destination_cidr_block, gateway)
30 | print "=="
31 |
32 | def get_all_subnets(vpc_conn, filters={}):
33 | raw_subnet_list = vpc_conn.get_all_subnets()
34 | for s in raw_subnet_list:
35 | get_all_routetables(vpc_conn, filters={'vpc_id': s.vpc_id})
36 | #get_all_internet_gateways(vpc_conn)
37 |
38 | def get_all_internet_gateways(vpc_conn, filters={}):
39 | raw_igw_list = vpc_conn.get_all_internet_gateways(filters=filters)
40 | for igw in raw_igw_list:
41 | print igw
42 |
43 | def main():
44 | "Main"
45 |
46 | vpc_conn = region_connect('ap-southeast-2')
47 | get_all_subnets(vpc_conn)
48 |
49 |
50 | if __name__ == '__main__':
51 | main()
52 |
--------------------------------------------------------------------------------
/images/ASG.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daniellawrence/aws-map/6717c64407bed41c8f4f09bf0df3f32f470faa10/images/ASG.png
--------------------------------------------------------------------------------
/images/Database-mysql.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daniellawrence/aws-map/6717c64407bed41c8f4f09bf0df3f32f470faa10/images/Database-mysql.png
--------------------------------------------------------------------------------
/images/Database.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daniellawrence/aws-map/6717c64407bed41c8f4f09bf0df3f32f470faa10/images/Database.png
--------------------------------------------------------------------------------
/images/Instance.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daniellawrence/aws-map/6717c64407bed41c8f4f09bf0df3f32f470faa10/images/Instance.png
--------------------------------------------------------------------------------
/images/InternetGateway.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daniellawrence/aws-map/6717c64407bed41c8f4f09bf0df3f32f470faa10/images/InternetGateway.png
--------------------------------------------------------------------------------
/images/LoadBalancer.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daniellawrence/aws-map/6717c64407bed41c8f4f09bf0df3f32f470faa10/images/LoadBalancer.png
--------------------------------------------------------------------------------
/images/Subnet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daniellawrence/aws-map/6717c64407bed41c8f4f09bf0df3f32f470faa10/images/Subnet.png
--------------------------------------------------------------------------------
/images/VPC.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/daniellawrence/aws-map/6717c64407bed41c8f4f09bf0df3f32f470faa10/images/VPC.png
--------------------------------------------------------------------------------
/mapall.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | #
3 | # Map AWS setup
4 | # Images are available from http://aws.amazon.com/architecture/icons/
5 | import argparse
6 | import time
7 | import os
8 | import sys
9 | import boto
10 | import netaddr
11 |
12 | objects = {}
13 | clusternum = 0
14 | awsflags = []
15 | nocache = False
16 | secGrpToDraw = set()
17 |
18 | colours = ['azure', 'coral', 'wheat', 'deepskyblue', 'firebrick', 'gold', 'green', 'plum', 'salmon', 'sienna']
19 |
20 |
21 | class RetryLimitExceeded(Exception):
22 | def __init__(self, exception, retries):
23 | self.exception = exception
24 | self.tries = retries
25 |
26 | def __str__(self):
27 | return repr(
28 | "Throttling retry limit exceeded, no_of_tries(%s), last exception: %s" % (self.tries, self.exception))
29 |
30 |
31 | def get_api_error_code(exception):
32 | if hasattr(exception, "body"):
33 | if exception.body is not None and hasattr(exception.body, "split"):
34 | code = exception.body.split("")[1]
35 | code = code.split("
")[0]
36 | return code
37 | else:
38 | return ""
39 | else:
40 | return ""
41 |
42 |
43 | def paginate_boto_response(api_call, *args, **kwargs):
44 | resultset = []
45 | tries = 0
46 | retry_interval = 2
47 | retry = 10
48 | while True:
49 |
50 | tries += 1
51 | try:
52 | results = api_call(*args, **kwargs)
53 | if results:
54 | resultset += results
55 | if results.next_token:
56 | kwargs['next_token'] = results.next_token
57 | else:
58 | break
59 | else:
60 | break
61 | except Exception, e:
62 | last_exception = e
63 | code = get_api_error_code(e)
64 | if retry <= 0:
65 | raise RetryLimitExceeded(last_exception, tries)
66 | elif retry > 0 and (code == "Throttling" or code == "RequestLimitExceeded"):
67 | retry -= 1
68 | retry_interval += 1
69 | time.sleep(retry_interval)
70 | else:
71 | raise e
72 |
73 | return resultset
74 |
75 | ###############################################################################
76 | ###############################################################################
77 | ###############################################################################
78 | class Dot(object):
79 | def __init__(self, data, args):
80 | self.data = data
81 | self.args = args
82 |
83 | ##########################################################################
84 | def __getitem__(self, key):
85 | return self.data.get(key, None)
86 |
87 | ##########################################################################
88 | def draw(self, fh):
89 | fh.write('%s [label="%s:%s" %s];\n' % (self.mn(self.name), self.__class__.__name__, self.name, self.image()))
90 |
91 | ##########################################################################
92 | def mn(self, s=None):
93 | """ Munge name to be dottable """
94 | if not s:
95 | s = self.name
96 | try:
97 | s = s.replace('-', '_')
98 | s = s.replace("'", '"')
99 | return '"' + s + '"'
100 | except AttributeError as e:
101 | return 'NoName'
102 |
103 | ##########################################################################
104 | def partOfInstance(self, instid):
105 | return False
106 |
107 | ##########################################################################
108 | def inSubnet(self, subnet):
109 | return True
110 |
111 | ##########################################################################
112 | def drawSec(self, fh):
113 | sys.stderr.write("%s.drawSec() undefined\n" % self.__class__.__name__)
114 |
115 | ##########################################################################
116 | def connect(self, fh, a, b, **kwargs):
117 | blockstr = ''
118 | for kk, kv in kwargs.items():
119 | blockstr += '%s=%s ' % (kk, kv)
120 | if blockstr:
121 | blockstr = '[ %s ]' % blockstr
122 | fh.write("%s -> %s %s;\n" % (self.mn(a), self.mn(b), blockstr))
123 |
124 | ##########################################################################
125 | def tags(self, key=None):
126 | tagd = {}
127 | if 'Tags' not in self.data:
128 | return None
129 | for t in self['Tags']:
130 | tagd[t['Key']] = t['Value']
131 | if key:
132 | return tagd.get(key, None)
133 | else:
134 | return tagd
135 |
136 | ##########################################################################
137 | def inVpc(self, vpc):
138 | return False
139 |
140 | ##########################################################################
141 | def relevent_to_ip(self, ip):
142 | return False
143 |
144 | ##########################################################################
145 | def rank(self, fh):
146 | fh.write(self.mn())
147 |
148 | ##########################################################################
149 | def image(self, names=[], shape='box', style='solid'):
150 | if not names:
151 | names = [self.__class__.__name__]
152 |
153 | for name in names:
154 | imgfile = os.path.join(os.path.realpath(os.path.dirname(__file__)), 'images', '%s.png' % name)
155 |
156 | if os.path.exists(imgfile):
157 | imagestr = ', image="%s", style=%s, shape=%s ' % (imgfile, style, shape)
158 | break
159 | else:
160 | imagestr = ', style=%s, shape=%s' % (style, shape)
161 | return imagestr
162 |
163 |
164 | ###############################################################################
165 | ###############################################################################
166 | ###############################################################################
167 | class NetworkAcl(Dot):
168 | """
169 | {
170 | "Associations": [
171 | {
172 | "SubnetId": "subnet-XXXXXXXX",
173 | "NetworkAclId": "acl-XXXXXXXX",
174 | "NetworkAclAssociationId": "aclassoc-XXXXXXXX"
175 | },
176 | ],
177 | "NetworkAclId": "acl-XXXXXXXX",
178 | "VpcId": "vpc-XXXXXXXX",
179 | "Tags": [],
180 | "Entries": [ {
181 | "CidrBlock": "0.0.0.0/0",
182 | "RuleNumber": 1,
183 | "Protocol": "-1",
184 | "Egress": true,
185 | "RuleAction": "allow"
186 | }, ],
187 | "IsDefault": true
188 | }
189 | """
190 |
191 | def __init__(self, instance, args):
192 | self.data = instance
193 | self.name = instance.id
194 | self.args = args
195 |
196 | def inVpc(self, vpc):
197 | if vpc and self.data.vpc_id != vpc:
198 | return False
199 | return True
200 |
201 | def inSubnet(self, subnet=None):
202 | if subnet:
203 | for assoc in self['Associations']:
204 | if assoc['SubnetId'] == subnet:
205 | return True
206 | return False
207 | return True
208 |
209 | def draw(self, fh):
210 | fh.write("// NACL %s\n" % self.name)
211 |
212 | def drawSec(self, fh):
213 | fh.write("// NACL %s\n" % self.name)
214 | fh.write('%s [shape="box", label="%s"];\n' % (self.mn(), self.name))
215 | self.genRuleBlock('ingress', fh)
216 | fh.write("%s -> %s_ingress_rules\n" % (self.mn(), self.mn()))
217 | self.genRuleBlock('egress', fh)
218 | fh.write("%s_egress_rules -> %s\n" % (self.mn(), self.mn()))
219 |
220 | def genRuleBlock(self, direct, fh):
221 | fh.write("// NACL %s\n" % self.name)
222 | fh.write('%s_%s_rules [ shape="Mrecord" label=<
' % (self.mn(), direct))
223 | fh.write('%s %s |
\n' % (self.name, direct))
224 | fh.write('%s %s %s
\n' % (header("Rule"), header("CIDR"), header("Ports")))
225 | for e in self['Entries']:
226 | if direct == 'ingress' and e['Egress']:
227 | continue
228 | if direct == 'egress' and not e['Egress']:
229 | continue
230 | col = "green" if e['RuleAction'] == 'allow' else "red"
231 | protocol = {'6': 'tcp', '17': 'udp'}.get(e['Protocol'], e['Protocol'])
232 | if 'PortRange' in e:
233 | if e['PortRange']['From'] == e['PortRange']['To']:
234 | portrange = "%s/%s" % (e['PortRange']['From'], protocol)
235 | else:
236 | portrange = "%s-%s/%s" % (e['PortRange']['From'], e['PortRange']['To'], protocol)
237 | else:
238 | portrange = ''
239 | fh.write("\n")
240 | fh.write('%s | ' % (col, e['RuleNumber']))
241 | fh.write("%s | " % e['CidrBlock'])
242 | fh.write("%s | \n" % portrange)
243 | fh.write("
\n")
244 | fh.write("
>\n")
245 | fh.write("];\n")
246 |
247 | def relevent_to_ip(self, ip):
248 | for e in self['Entries']:
249 | if netaddr.IPAddress(ip) in netaddr.IPNetwork(e['CidrBlock']):
250 | print "NACL %s - ip %s is relevent to %s" % (self.name, ip, e['CidrBlock'])
251 | return True
252 | return False
253 |
254 |
255 | ###############################################################################
256 | ###############################################################################
257 | ###############################################################################
258 | class Instance(Dot):
259 | """
260 | u'AmiLaunchIndex': 0
261 | u'Architecture': u'x86_64',
262 | u'BlockDeviceMappings': [
263 | {u'DeviceName': u'/dev/sda1',
264 | u'Ebs': {u'Status': u'attached', u'DeleteOnTermination': True, u'VolumeId': u'vol-XXXXXXXX', u'AttachTime': u'2000-01-01T01:00:00.000Z'}
265 | }],
266 | u'ClientToken': u'stuff',
267 | u'EbsOptimized': False,
268 | u'Hypervisor': u'xen',
269 | u'ImageId': u'ami-XXXXXXXX',
270 | u'InstanceId': u'i-XXXXXXXX',
271 | u'InstanceType': u't1.micro',
272 | u'KernelId': u'aki-XXXXXXXX',
273 | u'KeyName': u'KeyName',
274 | u'LaunchTime': u'2000-01-01T01:00:00.000Z',
275 | u'Monitoring': {u'State': u'disabled'},
276 | u'NetworkInterfaces': [...],
277 | u'Placement': {u'GroupName': None, u'Tenancy': u'default', u'AvailabilityZone': u'ap-southeast-2a'},
278 | u'PrivateDnsName': u'ip-10-1-2-3.ap-southeast-2.compute.internal',
279 | u'PrivateIpAddress': u'10.1.2.3',
280 | u'ProductCodes': [],
281 | u'PublicDnsName': u'ec2-54-1-2-3.ap-southeast-2.compute.amazonaws.com',
282 | u'PublicIpAddress': u'54.1.2.3',
283 | u'RootDeviceName': u'/dev/sda1',
284 | u'RootDeviceType': u'ebs',
285 | u'SecurityGroups': [{u'GroupName': u'XXX_GroupName_XXX', u'GroupId': u'sg-XXXXXXXX'}, ...
286 | u'SourceDestCheck': True,
287 | u'State': {u'Code': 16, u'Name': u'running'},
288 | u'StateTransitionReason': None,
289 | u'SubnetId': u'subnet-XXXXXXXX',
290 | u'Tags': [{u'Key': u'aws:cloudformation:stack-id', u'Value': u'Stuff'},
291 | {u'Key': u'aws:cloudformation:stack-name', u'Value': u'Stuff'},
292 | {u'Key': u'Name', u'Value': u'Stuff'},
293 | {u'Key': u'aws:cloudformation:logical-id', u'Value': u'JumpHost'}],
294 | u'VirtualizationType': u'paravirtual',
295 | u'VpcId': u'vpc-XXXXXXXX',
296 | """
297 |
298 | def __init__(self, instance, args):
299 | self.data = instance
300 | self.name = instance.id
301 | self.args = args
302 |
303 | def inSubnet(self, subnet=None):
304 | if subnet and self['SubnetId'] != subnet:
305 | return False
306 | return True
307 |
308 | def inVpc(self, vpc=None):
309 | if vpc and self.data.vpc_id != vpc:
310 | return False
311 | return True
312 |
313 | def rank(self, fh):
314 | if self.inVpc(self.args.vpc) and self.inSubnet(self.args.subnet):
315 | fh.write("%s;" % self.mn())
316 |
317 | def drawSec(self, fh):
318 | fh.write('// Instance %s\n' % self.name)
319 | label = "%s\\n%s\\n%s" % (self.tags('Name'), self.name, self['PrivateIpAddress'])
320 | fh.write('%s [label="%s" %s];\n' % (self.mn(self.name), label, self.image()))
321 | for sg in self['SecurityGroups']:
322 | self.connect(fh, self.name, sg['GroupId'])
323 | if self['SubnetId']:
324 | self.connect(fh, self.name, self['SubnetId'])
325 |
326 | def draw(self, fh):
327 | global clusternum
328 | if not self.inVpc(self.args.vpc) or not self.inSubnet(self.args.subnet):
329 | return
330 | fh.write('// Instance %s\n' % self.name)
331 | fh.write('subgraph cluster_%d {\n' % clusternum)
332 |
333 | if 'Name' in self.data.tags:
334 | label = self.data.tags['Name'] + "\n(" + self.name +")"
335 | else:
336 | label = self.name
337 |
338 | Style='solid'
339 | if self.data.state != 'running':
340 | Style = 'dashed'
341 |
342 | fh.write('%s [label="%s" %s];\n' % (self.mn(self.name), label, self.image(style=Style)))
343 |
344 | extraconns = []
345 | for o in objects.values():
346 | if o.partOfInstance(self.name):
347 | self.connect(fh, self.name, o.name)
348 | extraconns = o.subclusterDraw(fh)
349 | fh.write('graph [style=dotted]\n')
350 | fh.write('}\n') # End subgraph cluster
351 | if self.data.subnet_id:
352 | self.connect(fh, self.name, self.data.subnet_id)
353 | for ic, ec in extraconns:
354 | self.connect(fh, ic, ec)
355 | clusternum += 1
356 | if self.args.security:
357 | for sg in self.data.groups:
358 | self.connect(fh, self.name, sg.id)
359 |
360 |
361 | ###############################################################################
362 | ###############################################################################
363 | ###############################################################################
364 | class Subnet(Dot):
365 | """
366 | u'AvailabilityZone': u'ap-southeast-2a',
367 | u'AvailableIpAddressCount': 10,
368 | u'CidrBlock': u'10.1.2.3/28'
369 | u'DefaultForAz': False,
370 | u'MapPublicIpOnLaunch': False,
371 | u'State': u'available',
372 | u'SubnetId': u'subnet-XXXXXXXX',
373 | u'Tags': [{u'Key': u'aws:cloudformation:stack-id',
374 | u'Value': u'arn:aws:cloudformation:ap-southeast-2:XXXXXXXXXXXX:stack/Stuff'},
375 | {u'Key': u'aws:cloudformation:stack-name', u'Value': u'Stuff'},
376 | {u'Key': u'aws:cloudformation:logical-id', u'Value': u'SubnetA3'}],
377 | u'VpcId': u'vpc-XXXXXXXX',
378 | """
379 |
380 | def __init__(self, subnet, args):
381 | self.data = subnet
382 | self.name = subnet.id
383 | self.args = args
384 |
385 | def inVpc(self, vpc):
386 | if vpc and self.data.vpc_id != vpc:
387 | return False
388 | return True
389 |
390 | def relevent_to_ip(self, ip):
391 | if netaddr.IPAddress(ip) in netaddr.IPNetwork(self.data.cidr_block):
392 | print "Subnet %s - ip %s is relevent to %s" % (self.name, ip, self.data.cidr_block)
393 | return True
394 | return False
395 |
396 | def inSubnet(self, subnet=None):
397 | if subnet and self['SubnetId'] != subnet:
398 | return False
399 | return True
400 |
401 | def rank(self, fh):
402 | if self.inVpc(self.args.vpc) and self.inSubnet(self.args.subnet):
403 | fh.write("%s;" % self.mn())
404 |
405 | def drawSec(self, fh):
406 | fh.write('// Subnet %s\n' % self.name)
407 | fh.write('%s [label="%s\\n%s" %s];\n' % (self.mn(self.name), self.name, self.data.cidr_block, self.image()))
408 | self.connect(fh, self.name, self.data.vpc_id)
409 |
410 | def draw(self, fh):
411 | if not self.inVpc(self.args.vpc) or not self.inSubnet(self.args.subnet):
412 | return
413 | if 'Name' in self.data.tags:
414 | label = self.data.tags['Name']
415 | else:
416 | label = self.name
417 |
418 | fh.write('// Subnet %s\n' % self.name)
419 | fh.write('%s [label="%s\\n%s" %s];\n' % (self.mn(self.name), label, self.data.cidr_block, self.image()))
420 | self.connect(fh, self.name, self.data.vpc_id)
421 |
422 |
423 | ###############################################################################
424 | ###############################################################################
425 | ###############################################################################
426 | class Volume(Dot):
427 | """
428 | u'Attachments': [
429 | {u'AttachTime': u'2000-01-01T01:00:00.000Z', u'InstanceId': u'i-XXXXXXXX',
430 | u'VolumeId': u'vol-XXXXXXXX', u'State': u'attached',
431 | u'DeleteOnTermination': True, u'Device': u'/dev/sda1'}],
432 | u'AvailabilityZone': u'ap-southeast-2b',
433 | u'CreateTime': u'2000-01-01T01:00:00.000Z',
434 | u'Size': 6
435 | u'SnapshotId': u'snap-XXXXXXXX',
436 | u'State': u'in-use',
437 | u'VolumeId': u'vol-XXXXXXXX',
438 | u'VolumeType': u'standard',
439 | """
440 |
441 | def __init__(self, vol, args):
442 | self.data = vol
443 | self.name = vol['VolumeId']
444 | self.args = args
445 |
446 | def partOfInstance(self, instid):
447 | for a in self['Attachments']:
448 | if a['InstanceId'] == instid:
449 | return True
450 | return False
451 |
452 | def drawSec(self, fh):
453 | return
454 |
455 | def draw(self, fh):
456 | if self['State'] not in ('in-use',):
457 | if self.args.vpc:
458 | return
459 | if self.args.subnet or self.args.vpc:
460 | return
461 | fh.write('%s [label="Unattached Volume:%s\\n%s Gb" %s];\n' % (
462 | self.mn(self.name), self.name, self['Size'], self.image()))
463 |
464 | def subclusterDraw(self, fh):
465 | fh.write('%s [shape=box, label="%s\\n%s Gb"];\n' % (self.mn(self.name), self.name, self['Size']))
466 | return []
467 |
468 |
469 | ###############################################################################
470 | ###############################################################################
471 | ###############################################################################
472 | class SecurityGroup(Dot):
473 | """
474 | u'Description': u'SG Description',
475 | u'GroupId': u'sg-XXXXXXXX'
476 | u'GroupName': u'XXX_GroupName_XXX',
477 | u'IpPermissions': [
478 | {u'ToPort': 443, u'IpProtocol': u'tcp',
479 | u'IpRanges': [{u'CidrIp': u'0.0.0.0/0'}],
480 | u'UserIdGroupPairs': [], u'FromPort': 443}],
481 | u'IpPermissionsEgress': [
482 | {u'ToPort': 4502, u'IpProtocol': u'tcp',
483 | u'IpRanges': [{u'CidrIp': u'0.0.0.0/0'}],
484 | u'UserIdGroupPairs': [], u'FromPort': 4502}],
485 | u'OwnerId': u'XXXXXXXXXXXX',
486 | u'Tags': [{u'Key': u'Key', u'Value': u'Value'}, ...
487 | u'VpcId': u'vpc-XXXXXXXX',
488 | """
489 |
490 | def __init__(self, sg, args):
491 | self.data = sg
492 | self.name = sg.id
493 | self.args = args
494 | self.drawn = False
495 |
496 | def draw(self, fh):
497 | if self.args.vpc and self.data.vpc_id != self.args.vpc:
498 | return
499 |
500 | portstr = self.permstring(fh, self.data.rules)
501 | eportstr = self.permstring(fh, self.data.rules_egress)
502 |
503 | tportstr = []
504 | if portstr:
505 | tportstr.append("Ingress: %s" % portstr)
506 | if eportstr:
507 | tportstr.append("Egress: %s" % eportstr)
508 | desc = "\\n".join(chunkstring(self.data.description, 20))
509 | fh.write('%s [label="SG: %s\\n%s\\n%s" %s];\n' % (
510 | self.mn(self.name), self.name, desc, "\\n".join(tportstr), self.image()))
511 |
512 | def drawSec(self, fh):
513 | global clusternum
514 | self.extraRules = []
515 | fh.write("// SG %s\n" % self.name)
516 | fh.write('subgraph cluster_%d {\n' % clusternum)
517 | fh.write('style=filled; color="grey90";\n')
518 | fh.write('node [style=filled, color="%s"];\n' % colours[clusternum])
519 | desc = "\\n".join(chunkstring(self['Description'], 20))
520 | fh.write('%s [shape="rect", label="%s\\n%s"]\n' % (self.mn(), self.name, desc))
521 | if self['IpPermissions']:
522 | self.genRuleBlock(self['IpPermissions'], 'ingress', fh)
523 | if self['IpPermissionsEgress']:
524 | self.genRuleBlock(self['IpPermissionsEgress'], 'egress', fh)
525 | clusternum += 1
526 | fh.write("}\n")
527 |
528 | if self['IpPermissions']:
529 | fh.write("%s_ingress_rules -> %s [weight=5];\n" % (self.mn(), self.mn()))
530 | if self['IpPermissionsEgress']:
531 | fh.write("%s -> %s_egress_rules [weight=5];\n" % (self.mn(), self.mn()))
532 | for r in self.extraRules:
533 | fh.write(r)
534 | self.drawn = True
535 |
536 | def genRuleBlock(self, struct, direct, fh):
537 | fh.write("// SG %s %s\n" % (self.name, direct))
538 | for e in struct:
539 | fh.write("// %s\n" % e)
540 | fh.write('%s_%s_rules [ shape="Mrecord" label=<' % (self.mn(), direct))
541 | fh.write('%s %s |
\n' % (self.name, direct))
542 | fh.write('%s %s
\n' % (header('CIDR'), header('Ports')))
543 |
544 | for e in struct:
545 | fh.write("\n")
546 | ipranges = []
547 | for ipr in e['IpRanges']:
548 | if 'CidrIp' in ipr:
549 | ipranges.append(ipr['CidrIp'])
550 |
551 | if ipranges:
552 | if len(ipranges) > 1:
553 | iprangestr = ""
554 | for ipr in ipranges:
555 | iprangestr += "%s |
" % ipr
556 | iprangestr += "
"
557 | else:
558 | iprangestr = "%s" % ipranges[0]
559 | else:
560 | iprangestr = "See %s" % e['UserIdGroupPairs'][0]['GroupId']
561 | fh.write("%s | " % iprangestr)
562 | if 'FromPort' in e and e['FromPort']:
563 | fh.write("%s - %s/%s | " % (e['FromPort'], e['ToPort'], e['IpProtocol']))
564 | else:
565 | fh.write("ALL | \n")
566 | fh.write("
\n")
567 | fh.write("
>\n")
568 | fh.write("];\n")
569 |
570 | for e in struct:
571 | if e['UserIdGroupPairs']:
572 | for pair in e['UserIdGroupPairs']:
573 | secGrpToDraw.add(pair['GroupId'])
574 | self.extraRules.append('%s_%s_rules -> %s;\n' % (self.mn(), direct, self.mn(pair['GroupId'])))
575 |
576 | def relevent_to_ip(self, ip):
577 | for i in self['IpPermissions']:
578 | for ipr in i['IpRanges']:
579 | if netaddr.IPAddress(ip) in netaddr.IPNetwork(ipr['CidrIp']):
580 | return True
581 | for i in self['IpPermissionsEgress']:
582 | for ipr in i['IpRanges']:
583 | if netaddr.IPAddress(ip) in netaddr.IPNetwork(ipr['CidrIp']):
584 | return True
585 | return False
586 |
587 | def permstring(self, fh, obj):
588 | """
589 | Convert the permutations and combinations into a sensible output
590 | """
591 | ans = []
592 | if not obj:
593 | return ''
594 | for ip in obj:
595 | if ip.grants.__len__ > 0:
596 | for pair in ip.grants:
597 | self.connect(fh, self.name, pair.group_id)
598 | if ip.from_port is not None:
599 | ipranges = []
600 | for ipr in ip.grants:
601 | if ipr.cidr_ip is not None:
602 | ipranges.append(ipr.cidr_ip)
603 | iprangestr = ';'.join(ipranges)
604 | ans.append("%s %s->%s/%s" % (iprangestr, ip.from_port, ip.to_port, ip.ip_protocol))
605 | return " ".join(ans)
606 |
607 |
608 | ###############################################################################
609 | ###############################################################################
610 | ###############################################################################
611 | class VPC(Dot):
612 | """
613 | u'CidrBlock': u'172.1.2.3/16',
614 | u'DhcpOptionsId': u'dopt-XXXXXXXX',
615 | u'InstanceTenancy': u'default',
616 | u'IsDefault': True,
617 | u'State': u'available',
618 | u'VpcId': u'vpc-XXXXXXXX',
619 | """
620 |
621 | def __init__(self, vpc, args):
622 | self.data = vpc
623 | self.name = vpc.id
624 | self.args = args
625 |
626 | def inVpc(self, vpc):
627 | if vpc and self.name != vpc:
628 | return False
629 | return True
630 |
631 | def inSubnet(self, subnet):
632 | """ Return True if the subnet is in this VPC"""
633 | if not subnet:
634 | return True
635 | if objects[subnet].inVpc(self.name):
636 | return True
637 | return False
638 |
639 | def relevent_to_ip(self, ip):
640 | if netaddr.IPAddress(ip) in netaddr.IPNetwork(self.data.cidr_block):
641 | print "VPC %s - ip %s is relevent to %s" % (self.name, ip, self.data.cidr_block)
642 | return True
643 | return False
644 |
645 | def rank(self, fh):
646 | if self.inVpc(self.args.vpc) and self.inSubnet(self.args.subnet):
647 | fh.write("%s;" % self.mn())
648 |
649 | def drawSec(self, fh):
650 | fh.write('%s [label="%s:%s" %s];\n' % (self.mn(self.name), self.__class__.__name__, self.name, self.image()))
651 |
652 | def draw(self, fh):
653 | if not self.inVpc(self.args.vpc) or not self.inSubnet(self.args.subnet):
654 | return
655 | fh.write('%s [label="%s:%s" %s];\n' % (self.mn(self.name), self.__class__.__name__, self.name, self.image()))
656 |
657 |
658 | ###############################################################################
659 | ###############################################################################
660 | ###############################################################################
661 | class RouteTable(Dot):
662 | """
663 | u'Associations': [{u'SubnetId': u'subnet-XXXXXXXX', u'RouteTableAssociationId': u'rtbassoc-XXXXXXXX', u'RouteTableId': u'rtb-XXXXXXXX'}, ...]
664 | u'PropagatingVgws': [],
665 | u'RouteTableId': u'rtb-XXXXXXXX',
666 | u'Routes': [
667 | {u'GatewayId': u'local', u'DestinationCidrBlock': u'10.1.2.3/23',
668 | u'State': u'active', u'Origin': u'CreateRouteTable'},
669 | {u'Origin': u'CreateRoute', u'DestinationCidrBlock': u'0.0.0.0/0',
670 | u'InstanceId': u'i-XXXXXXXX', u'NetworkInterfaceId': u'eni-XXXXXXXX',
671 | u'State': u'active', u'InstanceOwnerId': u'XXXXXXXXXXXX'}]
672 | u'Tags': [{u'Key': u'Key', u'Value': u'Value'}, ...
673 | u'VpcId': u'vpc-XXXXXXXX',
674 |
675 | """
676 |
677 | def __init__(self, rt, args):
678 | self.data = rt
679 | self.args = args
680 | self.name = rt.id
681 |
682 | def rank(self, fh):
683 | if self.inVpc(self.args.vpc) and self.inSubnet(self.args.subnet):
684 | fh.write("%s;" % self.mn())
685 |
686 | def inVpc(self, vpc):
687 | if vpc and self.data.vpc_id != vpc:
688 | return False
689 | return True
690 |
691 | def relevent_to_ip(self, ip):
692 | for rt in self['Routes']:
693 | if netaddr.IPAddress(ip) in netaddr.IPNetwork(rt['DestinationCidrBlock']):
694 | print "RT %s - ip %s is relevent to %s" % (self.name, ip, rt['DestinationCidrBlock'])
695 | return True
696 | return False
697 |
698 | def inSubnet(self, subnet):
699 | if not subnet:
700 | return True
701 | for a in self['Associations']:
702 | if subnet == a.get('SubnetId', None):
703 | return True
704 | return False
705 |
706 | def drawSec(self, fh):
707 | routelist = []
708 | for rt in self['Routes']:
709 | if 'DestinationCidrBlock' in rt:
710 | routelist.append(rt['DestinationCidrBlock'])
711 | fh.write('%s [ shape="Mrecord" label=<' % self.mn())
712 | fh.write('%s |
\n' % self.name)
713 | fh.write('%s %s
\n' % (header('Source'), header('Dest')))
714 | for route in self['Routes']:
715 | colour = 'green'
716 | if route['State'] != 'active':
717 | colour = 'red'
718 | if 'GatewayId' in route:
719 | src = route['GatewayId']
720 | else:
721 | src = route['InstanceId']
722 | fh.write('%s | %s |
\n' % (colour, src, route['DestinationCidrBlock']))
723 | fh.write("
>];\n")
724 |
725 | def draw(self, fh):
726 | if not self.inVpc(self.args.vpc) or not self.inSubnet(self.args.subnet):
727 | return
728 | routelist = []
729 | for rt in self.data.routes:
730 | if rt.destination_cidr_block is not None:
731 | routelist.append(rt.destination_cidr_block)
732 | fh.write('%s [label="RT: %s\\n%s" %s];\n' % (self.mn(), self.name, ";".join(routelist), self.image()))
733 | for ass in self.data.associations:
734 | if ass.subnet_id is not None:
735 | if objects[ass.subnet_id].inSubnet(self.args.subnet):
736 | self.connect(fh, self.name, ass.subnet_id)
737 | for rt in self.data.routes:
738 | if rt.instance_id is not None:
739 | if objects[rt.instance_id].inSubnet(self.args.subnet):
740 | self.connect(fh, self.name, rt.instance_id)
741 | elif rt.interface_id is not None:
742 | self.connect(fh, self.name, rt.instance_id)
743 |
744 |
745 | ###############################################################################
746 | ###############################################################################
747 | ###############################################################################
748 | class NetworkInterface(Dot):
749 | """
750 | u'Association': {u'PublicIp': u'54.1.2.3', u'IpOwnerId': u'amazon'}
751 | u'Attachment': {
752 | u'Status': u'attached', u'DeviceIndex': 0,
753 | u'AttachTime': u'2000-01-01T01:00:00.000Z', u'InstanceId': u'i-XXXXXXXX',
754 | u'DeleteOnTermination': True, u'AttachmentId': u'eni-attach-XXXXXXXX',
755 | u'InstanceOwnerId': u'XXXXXXXXXXXX'},
756 | u'AvailabilityZone': u'ap-southeast-2b',
757 | u'Description': None,
758 | u'Groups': [{u'GroupName': u'XXX_GroupName_XXX', u'GroupId': u'sg-XXXXXXXX'}],
759 | u'MacAddress': u'aa:bb:cc:dd:ee:ff',
760 | u'NetworkInterfaceId': u'eni-XXXXXXXX',
761 | u'OwnerId': u'XXXXXXXXXXXX',
762 | u'PrivateDnsName': u'ip-172-1-2-3.ap-southeast-2.compute.internal',
763 | u'PrivateIpAddress': u'172.1.2.3',
764 | u'PrivateIpAddresses': [
765 | {u'PrivateDnsName': u'ip-172-1-2-3.ap-southeast-2.compute.internal',
766 | u'PrivateIpAddress': u'172.1.2.3', u'Primary': True,
767 | u'Association': {u'PublicIp': u'54.1.2.3', u'IpOwnerId': u'amazon'}}],
768 | u'RequesterManaged': False,
769 | u'SourceDestCheck': True,
770 | u'Status': u'in-use',
771 | u'SubnetId': u'subnet-XXXXXXXX',
772 | u'TagSet': [],
773 | u'VpcId': u'vpc-XXXXXXXX',
774 | """
775 |
776 | def __init__(self, nic, args):
777 | self.data = nic
778 | self.args = args
779 | self.name = nic.id
780 |
781 | def partOfInstance(self, instid):
782 | try:
783 | return self['Attachment'].get('InstanceId', None) == instid
784 | except AttributeError:
785 | return False
786 |
787 | def inSubnet(self, subnet=None):
788 | if subnet and self['SubnetId'] != subnet:
789 | return False
790 | return True
791 |
792 | def draw(self, fh):
793 | pass
794 |
795 | def subclusterDraw(self, fh):
796 | fh.write(
797 | '%s [label="NIC: %s\\n%s" %s];\n' % (self.mn(self.name), self.name, self['PrivateIpAddress'], self.image()))
798 | externallinks = []
799 | if self.args.security:
800 | for g in self['Groups']:
801 | externallinks.append((self.name, g['GroupId']))
802 | return externallinks
803 |
804 |
805 | ###############################################################################
806 | ###############################################################################
807 | ###############################################################################
808 | class InternetGateway(Dot):
809 | """
810 | u'Attachments': [{u'State': u'available', u'VpcId': u'vpc-XXXXXXXX'}],
811 | u'InternetGatewayId': u'igw-3a121a58',
812 | u'Tags': [
813 | {u'Key': u'aws:cloudformation:stack-id', u'Value': u'arn:aws:cloudformation:ap-southeast-2:XXXXXXXXXXXX:stack/Stuff'},
814 | {u'Key': u'aws:cloudformation:logical-id', u'Value': u'InternetGateway'},
815 | {u'Key': u'aws:cloudformation:stack-name', u'Value': u'Stuff'}],
816 | """
817 |
818 | def __init__(self, igw, args):
819 | self.data = igw
820 | self.name = igw.id
821 | self.conns = []
822 | for i in igw.attachments:
823 | # print(i)
824 | self.conns.append(i)
825 | self.args = args
826 |
827 | def rank(self, fh):
828 | if self.args.vpc:
829 | for i in self.conns[:]:
830 | if i != self.args.vpc:
831 | self.conns.remove(i)
832 | if self.conns:
833 | fh.write("%s;" % self.mn())
834 |
835 | def draw(self, fh):
836 | if self.args.vpc:
837 | for i in self.conns[:]:
838 | if i != self.args.vpc:
839 | self.conns.remove(i)
840 | if self.args.subnet:
841 | for i in self.conns[:]:
842 | if not objects[i].inSubnet(self.args.subnet):
843 | self.conns.remove(i)
844 | if self.conns:
845 | fh.write('%s [label="InternetGateway: %s" %s];\n' % (self.mn(self.name), self.name, self.image()))
846 | for i in self.conns:
847 | self.connect(fh, self.name, i.vpc_id)
848 |
849 |
850 | ###############################################################################
851 | ###############################################################################
852 | ###############################################################################
853 | class LoadBalancer(Dot):
854 | """
855 | u'AvailabilityZones': [u'ap-southeast-2b', u'ap-southeast-2a'],
856 | u'BackendServerDescriptions': [],
857 | u'CanonicalHostedZoneName': u'Stuff',
858 | u'CanonicalHostedZoneNameID': u'XXXXXXXXXXXXXX',
859 | u'CreatedTime': u'2000-01-01T01:00:00.300Z',
860 | u'DNSName': u'Stuff',
861 | u'HealthCheck': {u'HealthyThreshold': 2, u'Interval': 30, u'Target': u'TCP:7990', u'Timeout': 5, u'UnhealthyThreshold': 2},
862 | u'Instances': [{u'InstanceId': u'i-XXXXXXXX'}],
863 | u'ListenerDescriptions': [
864 | {u'Listener': {
865 | u'InstancePort': 7990, u'Protocol': u'HTTPS', u'LoadBalancerPort': 443,
866 | u'SSLCertificateId': u'arn:aws:iam::XXXXXXXXXXXX:server-certificate/GenericSSL',
867 | u'InstanceProtocol': u'HTTP'}, u'PolicyNames': [u'ELBSecurityPolicy-2011-08']},
868 | {u'Listener': {
869 | u'InstancePort': 7999, u'LoadBalancerPort': 7999, u'Protocol': u'TCP',
870 | u'InstanceProtocol': u'TCP'}, u'PolicyNames': []}],
871 | u'LoadBalancerName': u'Stuff',
872 | u'Policies': {u'LBCookieStickinessPolicies': [], u'AppCookieStickinessPolicies': [], u'OtherPolicies': [u'ELBSecurityPolicy-2011-08']},
873 | u'Scheme': u'internet-facing',
874 | u'SecurityGroups': [u'sg-XXXXXXXX'],
875 | u'SourceSecurityGroup': {u'OwnerAlias': u'XXXXXXXXXXXX', u'GroupName': u'XXX_GroupName_XXX'}
876 | u'Subnets': [u'subnet-XXXXXXXX', u'subnet-XXXXXXXX'],
877 | u'VPCId': u'vpc-XXXXXXXX',
878 | """
879 |
880 | def __init__(self, lb, args):
881 | self.data = lb
882 | self.name = lb.name
883 | self.args = args
884 |
885 | def inSubnet(self, subnet=None):
886 | if subnet and subnet not in self.data.subnets:
887 | return False
888 | return True
889 |
890 | def inVpc(self, vpc):
891 | if vpc and self.data.vpc_id != vpc:
892 | return False
893 | return True
894 |
895 | def rank(self, fh):
896 | if self.inVpc(self.args.vpc) and self.inSubnet(self.args.subnet):
897 | fh.write("%s;" % self.mn())
898 |
899 | def draw(self, fh):
900 | if not self.inVpc(self.args.vpc) or not self.inSubnet(self.args.subnet):
901 | return
902 | ports = []
903 | for l in self.data.listeners:
904 | # x = l['Listener']
905 | ports.append(
906 | "%s/%s -> %s/%s" % (l.load_balancer_port, l.protocol, l.instance_port, l.instance_protocol))
907 |
908 | fh.write('%s [label="ELB: %s\\n%s" %s];\n' % (self.mn(self.name), self.name, "\n".join(ports), self.image()))
909 | for i in self.data.instances:
910 | if objects[i.id].inSubnet(self.args.subnet):
911 | self.connect(fh, self.name, i.id)
912 | for s in self.data.subnets:
913 | if self.args.subnet:
914 | if s != self.args.subnet:
915 | continue
916 | self.connect(fh, self.name, s)
917 | if self.args.security:
918 | for sg in self.data.security_groups:
919 | self.connect(fh, self.name, sg)
920 |
921 |
922 | ###############################################################################
923 | ###############################################################################
924 | ###############################################################################
925 | class Database(Dot):
926 | """
927 | u'AllocatedStorage': 5,
928 | u'AutoMinorVersionUpgrade': True,
929 | u'AvailabilityZone': u'ap-southeast-2a',
930 | u'BackupRetentionPeriod': 0,
931 | u'DBInstanceClass': u'db.t1.micro',
932 | u'DBInstanceIdentifier': u'devapps'
933 | u'DBInstanceStatus': u'available',
934 | u'DBName': u'crowd',
935 | u'DBParameterGroups': [{u'DBParameterGroupName': u'XXX_GroupName_XXX', u'ParameterApplyStatus': u'in-sync'}],
936 | u'DBSecurityGroups': [],
937 | u'DBSubnetGroup': {
938 | u'DBSubnetGroupDescription': u'default',
939 | u'DBSubnetGroupName': u'default',
940 | u'SubnetGroupStatus': u'Complete'
941 | u'Subnets': [
942 | {
943 | u'SubnetStatus': u'Active',
944 | u'SubnetIdentifier': u'subnet-XXXXXXXX',
945 | u'SubnetAvailabilityZone': {u'Name': u'ap-southeast-2b', u'ProvisionedIopsCapable': False}
946 | },
947 | ...
948 | ],
949 | u'VpcId': u'vpc-XXXXXXXX',
950 | },
951 | u'Endpoint': {u'Port': 3306, u'Address': u'devapps.csgxwe0psnca.ap-southeast-2.rds.amazonaws.com'},
952 | u'Engine': u'mysql',
953 | u'EngineVersion': u'5.6.13',
954 | u'InstanceCreateTime': u'2000-01-01T01:00:00.275Z',
955 | u'LicenseModel': u'general-public-license',
956 | u'MasterUsername': u'rootmaster',
957 | u'MultiAZ': False,
958 | u'OptionGroupMemberships': [{u'Status': u'in-sync', u'OptionGroupName': u'default:mysql-5-6'}],
959 | u'PendingModifiedValues': {},
960 | u'PreferredBackupWindow': u'18:37-19:07',
961 | u'PreferredMaintenanceWindow': u'sat:15:17-sat:15:47',
962 | u'PubliclyAccessible': True,
963 | u'ReadReplicaDBInstanceIdentifiers': [],
964 | u'VpcSecurityGroups': [{u'Status': u'active', u'VpcSecurityGroupId': u'sg-XXXXXXXX'}],
965 | """
966 |
967 | def __init__(self, db, args):
968 | self.data = db
969 | self.name = db.id
970 | self.args = args
971 |
972 | def inSubnet(self, subnet=None):
973 | if not subnet:
974 | return True
975 | for snet in self['DBSubnetGroup']['Subnets']:
976 | if subnet == snet['SubnetIdentifier']:
977 | return True
978 | return False
979 |
980 | def inVpc(self, vpc):
981 | if vpc and self.data.subnet_group.vpc_id != vpc:
982 | return False
983 | return True
984 |
985 | def rank(self, fh):
986 | if self.inVpc(self.args.vpc) and self.inSubnet(self.args.subnet):
987 | fh.write("%s;" % self.mn())
988 |
989 | def drawSec(self, fh):
990 | imgstr = self.image(["Database-%s" % self['Engine'], 'Database'])
991 | fh.write('%s [label="DB: %s\\n%s" %s];\n' % (self.mn(self.name), self.name, self['Engine'], imgstr))
992 |
993 | def draw(self, fh):
994 | if not self.inVpc(self.args.vpc) or not self.inSubnet(self.args.subnet):
995 | return
996 | fh.write('// Database %s\n' % self.name)
997 | imgstr = self.image(["Database-%s" % self.data.engine, 'Database'])
998 | fh.write('%s [label="DB: %s\\n%s" %s];\n' % (self.mn(self.name), self.name, self.data.engine, imgstr))
999 | for subnet in self.data.subnet_group.subnet_ids:
1000 | # if subnet.SubnetStatus == 'Active':
1001 | if objects[subnet].inSubnet(self.args.subnet):
1002 | self.connect(fh, self.name, subnet)
1003 | if self.args.security:
1004 | for sg in self.data.vpc_security_groups:
1005 | self.connect(fh, self.name, sg.vpc_group)
1006 |
1007 |
1008 | class ASG(Dot):
1009 | def __init__(self, db, args):
1010 | self.data = db
1011 | self.name = db.name
1012 | self.args = args
1013 |
1014 | """
1015 | {
1016 | "AutoScalingGroups": [
1017 | {
1018 | "AutoScalingGroupARN": "arn:aws:autoscaling:us-west-2:803981987763:autoScalingGroup:930d940e-891e-4781-a11a-7b0acd480f03:autoScalingGroupName/my-test-asg",
1019 | "HealthCheckGracePeriod": 0,
1020 | "SuspendedProcesses": [],
1021 | "DesiredCapacity": 1,
1022 | "Tags": [],
1023 | "EnabledMetrics": [],
1024 | "LoadBalancerNames": [],
1025 | "AutoScalingGroupName": "my-test-asg",
1026 | "DefaultCooldown": 300,
1027 | "MinSize": 0,
1028 | "Instances": [
1029 | {
1030 | "InstanceId": "i-4ba0837f",
1031 | "AvailabilityZone": "us-west-2c",
1032 | "HealthStatus": "Healthy",
1033 | "LifecycleState": "InService",
1034 | "LaunchConfigurationName": "my-test-lc"
1035 | }
1036 | ],
1037 | "MaxSize": 1,
1038 | "VPCZoneIdentifier": null,
1039 | "TerminationPolicies": [
1040 | "Default"
1041 | ],
1042 | "LaunchConfigurationName": "my-test-lc",
1043 | "CreatedTime": "2013-08-19T20:53:25.584Z",
1044 | "AvailabilityZones": [
1045 | "us-west-2c"
1046 | ],
1047 | "HealthCheckType": "EC2"
1048 | }
1049 | ]
1050 | }
1051 |
1052 | """
1053 |
1054 | def image(self, names=[]):
1055 | return super(ASG, self).image(names)
1056 |
1057 | def draw(self, fh):
1058 | if self.inVpc(self.args.vpc) and self.inSubnet(self.args.subnet):
1059 | fh.write('// ASG %s\n' % self.name)
1060 | imgstr = self.image(["ASG-%s" % self.data.name, 'ASG'])
1061 | fh.write('%s [label="ASG: %s\\n%s" %s];\n' % (self.mn(self.name), self.name, '', imgstr))
1062 | for lb in self.data.load_balancers:
1063 | if objects[lb].inSubnet(self.args.subnet):
1064 | self.connect(fh, self.name, lb)
1065 |
1066 | def rank(self, fh):
1067 | if self.inVpc(self.args.vpc) and self.inSubnet(self.args.subnet):
1068 | fh.write("%s;" % self.mn())
1069 |
1070 | def inVpc(self, vpc):
1071 | if vpc:
1072 | subnets = self.data.vpc_zone_identifier
1073 | for subnet in subnets.split(','):
1074 | # sys.stderr.write(subnet)
1075 | if vpc and subnet in objects and objects[subnet].data.vpc_id == vpc:
1076 | return True
1077 | return False
1078 | return True
1079 |
1080 |
1081 | ###############################################################################
1082 |
1083 | def header(lbl):
1084 | return '%s | ' % lbl
1085 |
1086 |
1087 | ###############################################################################
1088 | def chunkstring(strng, length):
1089 | """ Break a string on word boundaries, where each line is up to
1090 | length characters long """
1091 | ans = []
1092 | line = []
1093 | for w in strng.split():
1094 | if len(w) >= length:
1095 | ans.append(" ".join(line))
1096 | ans.append(w)
1097 | line = []
1098 | continue
1099 | if len(" ".join(line)) + len(w) < length:
1100 | line.append(w)
1101 | else:
1102 | ans.append(" ".join(line))
1103 | line = []
1104 | ans.append(" ".join(line))
1105 | return ans
1106 |
1107 | ###############################################################################
1108 | def get_all_internet_gateways(args):
1109 | if args.verbose:
1110 | sys.stderr.write("Getting internet gateways\n")
1111 | # igw_data = ec2cmd("describe-internet-gateways")['InternetGateways']
1112 | import boto.vpc
1113 | igw_data = paginate_boto_response(boto.vpc.connect_to_region(args.region).get_all_internet_gateways)
1114 | for igw in igw_data:
1115 | g = InternetGateway(igw, args)
1116 | objects[g.name] = g
1117 |
1118 |
1119 | ###############################################################################
1120 | def get_vpc_list(args):
1121 | import boto.vpc
1122 | vpc_data = paginate_boto_response(boto.vpc.connect_to_region(args.region).get_all_vpcs)
1123 | # vpc_data = ec2cmd("describe-vpcs")['Vpcs']
1124 | for vpc in vpc_data:
1125 | if args.vpc and vpc.id != args.vpc:
1126 | continue
1127 | if args.verbose:
1128 | sys.stderr.write("VPC: %s\n" % vpc.id)
1129 | g = VPC(vpc, args)
1130 | objects[g.name] = g
1131 |
1132 |
1133 | ###############################################################################
1134 | def get_all_instances(args):
1135 | if args.verbose:
1136 | sys.stderr.write("Getting instances\n")
1137 | import boto.ec2
1138 | reservation_list = paginate_boto_response(boto.ec2.connect_to_region(args.region).get_all_reservations)
1139 | # reservation_list = ec2cmd("describe-instances")['Reservations']
1140 | for reservation in reservation_list:
1141 | for instance in reservation.instances:
1142 | i = Instance(instance, args)
1143 | objects[i.name] = i
1144 | if args.verbose:
1145 | sys.stderr.write("Instance: %s\n" % i.name)
1146 |
1147 |
1148 | ###############################################################################
1149 | def get_all_subnets(args):
1150 | if args.verbose:
1151 | sys.stderr.write("Getting subnets\n")
1152 | import boto.vpc
1153 | subnets = paginate_boto_response(boto.vpc.connect_to_region(args.region).get_all_subnets)
1154 | for subnet in subnets:
1155 | if args.subnet and subnet.id != args.subnet:
1156 | pass
1157 | elif args.verbose:
1158 | sys.stderr.write("Subnet: %s\n" % subnet.id)
1159 | s = Subnet(subnet, args)
1160 | objects[s.name] = s
1161 |
1162 |
1163 | ###############################################################################
1164 | def get_all_volumes(args):
1165 | if args.verbose:
1166 | sys.stderr.write("Getting volumes\n")
1167 | # volumes = ec2cmd("describe-volumes")['Volumes']
1168 | volumes = paginate_boto_response(boto.ec2.connect_to_region(args.region).get_all_volumes)
1169 | for volume in volumes:
1170 | v = Volume(volume, args)
1171 | objects[v.name] = v
1172 |
1173 |
1174 | ###############################################################################
1175 | def get_all_security_groups(args):
1176 | if args.verbose:
1177 | sys.stderr.write("Getting security groups\n")
1178 | import boto.ec2
1179 | sgs = paginate_boto_response(boto.ec2.connect_to_region(args.region).get_all_security_groups)
1180 | # sgs = ec2cmd("describe-security-groups")['SecurityGroups']
1181 | for sg in sgs:
1182 | s = SecurityGroup(sg, args)
1183 | objects[s.name] = s
1184 | if args.verbose:
1185 | sys.stderr.write("SG %s\n" % s.name)
1186 |
1187 |
1188 | ###############################################################################
1189 | def get_all_route_tables(args):
1190 | if args.verbose:
1191 | sys.stderr.write("Getting route tables\n")
1192 | # rts = ec2cmd('describe-route-tables')['RouteTables']
1193 | import boto.vpc
1194 | rts = paginate_boto_response(boto.vpc.connect_to_region(args.region).get_all_route_tables)
1195 | for rt in rts:
1196 | r = RouteTable(rt, args)
1197 | objects[r.name] = r
1198 |
1199 |
1200 | ###############################################################################
1201 | def get_all_network_interfaces(args):
1202 | if args.verbose:
1203 | sys.stderr.write("Getting NICs\n")
1204 | import boto.ec2
1205 | nics = paginate_boto_response(boto.ec2.connect_to_region(args.region).get_all_network_interfaces)
1206 | # nics = ec2cmd('describe-network-interfaces')['NetworkInterfaces']
1207 | for nic in nics:
1208 | n = NetworkInterface(nic, args)
1209 | objects[n.name] = n
1210 |
1211 |
1212 | ###############################################################################
1213 | def get_all_rds(args):
1214 | if args.verbose:
1215 | sys.stderr.write("Getting Databases\n")
1216 | import boto.rds
1217 | dbs = paginate_boto_response(boto.rds.connect_to_region(args.region).get_all_dbinstances)
1218 | # dbs = rdscmd('describe-db-instances')['DBInstances']
1219 | for db in dbs:
1220 | rds = Database(db, args)
1221 | objects[rds.name] = rds
1222 | if args.verbose:
1223 | sys.stderr.write("RDS: %s\n" % rds.name)
1224 |
1225 |
1226 | ###############################################################################
1227 | def get_all_elbs(args):
1228 | if args.verbose:
1229 | sys.stderr.write("Getting Load Balancers\n")
1230 | import boto.ec2.elb
1231 | elbs = paginate_boto_response(boto.ec2.elb.connect_to_region(args.region).get_all_load_balancers)
1232 | # elbs = elbcmd('describe-load-balancers')['LoadBalancerDescriptions']
1233 | for elb in elbs:
1234 | lb = LoadBalancer(elb, args)
1235 | if args.verbose:
1236 | sys.stderr.write("ELBs: %s\n" % lb.name)
1237 | objects[lb.name] = lb
1238 |
1239 |
1240 | ###############################################################################
1241 | def get_all_networkacls(args):
1242 | if args.verbose:
1243 | sys.stderr.write("Getting NACLs\n")
1244 | import boto.vpc
1245 | nacls = paginate_boto_response(boto.vpc.connect_to_region(args.region).get_all_network_acls)
1246 | # nacls = ec2cmd('describe-network-acls')['NetworkAcls']
1247 | for nacl in nacls:
1248 | nc = NetworkAcl(nacl, args)
1249 | objects[nc.name] = nc
1250 | if args.verbose:
1251 | sys.stderr.write("NACL: %s\n" % nc.name)
1252 |
1253 |
1254 | ###############################################################################
1255 | def get_all_asgs(args):
1256 | if args.verbose:
1257 | sys.stderr.write("Getting ASGs\n")
1258 | # asgs = asgcmd('describe-auto-scaling-groups')['AutoScalingGroups']
1259 | import boto.ec2.autoscale
1260 | asgs = paginate_boto_response(boto.ec2.autoscale.connect_to_region(args.region).get_all_groups)
1261 | for asg in asgs:
1262 | _asg = ASG(asg, args)
1263 | objects[_asg.name] = _asg
1264 | if args.verbose:
1265 | sys.stderr.write("ASGs: %s\n" % _asg.name)
1266 |
1267 |
1268 | def map_region(args):
1269 | # EC2
1270 | get_vpc_list(args)
1271 | get_all_internet_gateways(args)
1272 | get_all_network_interfaces(args)
1273 | get_all_instances(args)
1274 | get_all_subnets(args)
1275 | if args.volumes:
1276 | get_all_volumes(args)
1277 | get_all_route_tables(args)
1278 | get_all_security_groups(args)
1279 | get_all_networkacls(args)
1280 |
1281 | # RDS
1282 | get_all_rds(args)
1283 |
1284 | # ELB
1285 | get_all_elbs(args)
1286 |
1287 | get_all_asgs(args)
1288 |
1289 |
1290 | ###############################################################################
1291 | def parseArgs():
1292 | global nocache
1293 | global awsflags
1294 | parser = argparse.ArgumentParser()
1295 | parser.add_argument(
1296 | '--awsflag', default=None, help="Flags to pass to aws calls [None]")
1297 | parser.add_argument(
1298 | '--vpc', default=None, help="Which VPC to examine [all]")
1299 | parser.add_argument(
1300 | '--subnet', default=None, help="Which subnet to examine [all]")
1301 | parser.add_argument(
1302 | '--iterate', default=None, choices=['vpc', 'subnet'],
1303 | help="Create different maps for each vpc or subnet")
1304 | parser.add_argument(
1305 | '--nocache', default=False, action='store_true',
1306 | help="Don't read from cache'd data")
1307 | parser.add_argument(
1308 | '--output', default=sys.stdout, type=argparse.FileType('w'),
1309 | help="Which file to output to [stdout]")
1310 | parser.add_argument(
1311 | '--security', default=False, action='store_true',
1312 | help="Draw in security groups")
1313 | parser.add_argument(
1314 | '--secmap', default=None,
1315 | help="Draw a security map for specified ec2")
1316 | parser.add_argument(
1317 | '--volumes', default=False,
1318 | help="enables volumes")
1319 | parser.add_argument(
1320 | '-v', '--verbose', default=False, action='store_true',
1321 | help="Print some details")
1322 |
1323 | requiredNamed = parser.add_argument_group('required named arguments')
1324 |
1325 | if 'AWS_DEFAULT_REGION' in os.environ:
1326 | default_region=os.environ['AWS_DEFAULT_REGION']
1327 | region_required = False
1328 | else:
1329 | default_region=False
1330 | region_required = True
1331 |
1332 | requiredNamed.add_argument(
1333 | '--region', default=default_region, required=region_required,
1334 | help="ec2 region")
1335 |
1336 | args = parser.parse_args()
1337 | nocache = args.nocache
1338 | if args.vpc and not args.vpc.startswith('vpc-'):
1339 | args.vpc = "vpc-%s" % args.vpc
1340 | if args.subnet and not args.subnet.startswith('subnet-'):
1341 | args.subnet = "subnet-%s" % args.subnet
1342 | if args.awsflag:
1343 | awsflags = ["--%s" % args.awsflag]
1344 | return args
1345 |
1346 |
1347 | ###############################################################################
1348 | def generateHeader(fh):
1349 | fh.write("digraph G {\n")
1350 | fh.write('overlap=false\n')
1351 | fh.write('ranksep=1.6\n')
1352 | fh.write('splines=ortho\n')
1353 |
1354 |
1355 | ###############################################################################
1356 | def generateFooter(fh):
1357 | fh.write("}\n")
1358 |
1359 |
1360 | ###############################################################################
1361 | def generate_secmap(ec2, fh):
1362 | """ Generate a security map instead """
1363 | generateHeader(fh)
1364 | subnet = objects[ec2]['SubnetId']
1365 | vpc = objects[ec2]['VpcId']
1366 |
1367 | # The ec2
1368 | objects[ec2].drawSec(fh)
1369 |
1370 | # Security groups associated with the ec2
1371 | for sg in objects[ec2]['SecurityGroups']:
1372 | secGrpToDraw.add(sg['GroupId'])
1373 | objects[sg['GroupId']].drawSec(fh)
1374 |
1375 | # Subnet ec2 is on
1376 | subnet = objects[ec2]['SubnetId']
1377 | objects[subnet].drawSec(fh)
1378 |
1379 | # NACLs and RTs associated with that subnet
1380 | for obj in objects.values():
1381 | if obj.__class__ in (NetworkAcl, RouteTable):
1382 | for assoc in obj['Associations']:
1383 | if 'SubnetId' in assoc and assoc['SubnetId'] == subnet:
1384 | obj.drawSec(fh)
1385 | fh.write("%s -> %s\n" % (obj.mn(), objects[subnet].mn()))
1386 | continue
1387 | if obj.__class__ in (Database, ):
1388 | for sg in obj['VpcSecurityGroups']:
1389 | if sg['VpcSecurityGroupId'] in secGrpToDraw:
1390 | obj.drawSec(fh)
1391 |
1392 | # VPC that the EC2 is in
1393 | objects[vpc].drawSec(fh)
1394 |
1395 | # Finish any referred to SG
1396 | for sg in list(secGrpToDraw):
1397 | if not objects[sg].drawn:
1398 | objects[sg].drawSec(fh)
1399 |
1400 | generateFooter(fh)
1401 |
1402 |
1403 | ###############################################################################
1404 | def generate_map(fh, args):
1405 | generateHeader(fh)
1406 |
1407 | # Draw all the objects
1408 | for obj in sorted(objects.values()):
1409 | if obj.__class__ == SecurityGroup:
1410 | if not args.security:
1411 | continue
1412 | obj.draw(fh)
1413 |
1414 | # Assign Ranks
1415 | for objtype in [Database, LoadBalancer, Subnet, Instance, VPC, InternetGateway, RouteTable, ASG]:
1416 | fh.write('// Rank %s\n' % objtype.__name__)
1417 | fh.write('rank_%s [style=invisible]\n' % objtype.__name__)
1418 | fh.write('{ rank=same; rank_%s; ' % objtype.__name__)
1419 | for obj in sorted(objects.values()):
1420 | if obj.__class__ == objtype:
1421 | obj.rank(fh)
1422 | fh.write('}\n')
1423 | ranks = ['RouteTable', 'Subnet', 'Database', 'LoadBalancer', 'ASG', 'Instance', 'VPC', 'InternetGateway']
1424 | strout = " -> ".join(["rank_%s" % x for x in ranks])
1425 | fh.write("%s [style=invis];\n" % strout)
1426 |
1427 | generateFooter(fh)
1428 |
1429 |
1430 | ###############################################################################
1431 | def main():
1432 | args = parseArgs()
1433 | map_region(args)
1434 | if args.secmap:
1435 | generate_secmap(args.secmap, args.output)
1436 | return
1437 | if args.iterate:
1438 | for o in objects.keys():
1439 | if o.startswith(args.iterate):
1440 | f = open('%s.dot' % o, 'w')
1441 | setattr(args, args.iterate, o)
1442 | generate_map(f, args)
1443 | f.close()
1444 | else:
1445 | generate_map(args.output, args)
1446 |
1447 | ###############################################################################
1448 | if __name__ == '__main__':
1449 | main()
1450 |
1451 | # EOF
1452 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | netaddr
2 | argparse
3 | boto
4 | pydot
5 | pyparsing
6 | wsgiref
7 |
--------------------------------------------------------------------------------