1import base64
2import hashlib
3import fnmatch
4import random
5import re
6import ipaddress
7
8from cryptography.hazmat.primitives import serialization
9from cryptography.hazmat.backends import default_backend
10from cryptography.hazmat.primitives.asymmetric import rsa
11
12from moto.core import ACCOUNT_ID
13from moto.iam import iam_backends
14
15EC2_RESOURCE_TO_PREFIX = {
16    "customer-gateway": "cgw",
17    "transit-gateway": "tgw",
18    "transit-gateway-route-table": "tgw-rtb",
19    "transit-gateway-attachment": "tgw-attach",
20    "dhcp-options": "dopt",
21    "flow-logs": "fl",
22    "image": "ami",
23    "instance": "i",
24    "internet-gateway": "igw",
25    "egress-only-internet-gateway": "eigw",
26    "launch-template": "lt",
27    "nat-gateway": "nat",
28    "network-acl": "acl",
29    "network-acl-subnet-assoc": "aclassoc",
30    "network-interface": "eni",
31    "network-interface-attachment": "eni-attach",
32    "reserved-instance": "uuid4",
33    "route-table": "rtb",
34    "route-table-association": "rtbassoc",
35    "security-group": "sg",
36    "security-group-rule": "sgr",
37    "snapshot": "snap",
38    "spot-instance-request": "sir",
39    "spot-fleet-request": "sfr",
40    "subnet": "subnet",
41    "subnet-ipv6-cidr-block-association": "subnet-cidr-assoc",
42    "reservation": "r",
43    "volume": "vol",
44    "vpc": "vpc",
45    "vpc-endpoint": "vpce",
46    "managed-prefix-list": "pl",
47    "vpc-cidr-association-id": "vpc-cidr-assoc",
48    "vpc-elastic-ip": "eipalloc",
49    "vpc-elastic-ip-association": "eipassoc",
50    "vpc-peering-connection": "pcx",
51    "vpn-connection": "vpn",
52    "vpn-gateway": "vgw",
53    "iam-instance-profile-association": "iip-assoc",
54    "carrier-gateway": "cagw",
55}
56
57
58EC2_PREFIX_TO_RESOURCE = dict((v, k) for (k, v) in EC2_RESOURCE_TO_PREFIX.items())
59
60
61def random_resource_id(size=8):
62    chars = list(range(10)) + ["a", "b", "c", "d", "e", "f"]
63    resource_id = "".join(str(random.choice(chars)) for _ in range(size))
64    return resource_id
65
66
67def random_id(prefix="", size=8):
68    return "{0}-{1}".format(prefix, random_resource_id(size))
69
70
71def random_ami_id():
72    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["image"])
73
74
75def random_instance_id():
76    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["instance"], size=17)
77
78
79def random_reservation_id():
80    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["reservation"])
81
82
83def random_security_group_id():
84    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["security-group"], size=17)
85
86
87def random_security_group_rule_id():
88    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["security-group-rule"], size=17)
89
90
91def random_flow_log_id():
92    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["flow-logs"])
93
94
95def random_snapshot_id():
96    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["snapshot"])
97
98
99def random_spot_request_id():
100    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["spot-instance-request"])
101
102
103def random_spot_fleet_request_id():
104    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["spot-fleet-request"])
105
106
107def random_subnet_id():
108    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["subnet"])
109
110
111def random_subnet_ipv6_cidr_block_association_id():
112    return random_id(
113        prefix=EC2_RESOURCE_TO_PREFIX["subnet-ipv6-cidr-block-association"]
114    )
115
116
117def random_subnet_association_id():
118    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["route-table-association"])
119
120
121def random_network_acl_id():
122    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["network-acl"])
123
124
125def random_network_acl_subnet_association_id():
126    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["network-acl-subnet-assoc"])
127
128
129def random_vpn_gateway_id():
130    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpn-gateway"])
131
132
133def random_vpn_connection_id():
134    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpn-connection"])
135
136
137def random_customer_gateway_id():
138    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["customer-gateway"])
139
140
141def random_volume_id():
142    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["volume"])
143
144
145def random_vpc_id():
146    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc"])
147
148
149def random_vpc_ep_id():
150    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc-endpoint"], size=8)
151
152
153def random_vpc_cidr_association_id():
154    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc-cidr-association-id"])
155
156
157def random_vpc_peering_connection_id():
158    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc-peering-connection"])
159
160
161def random_eip_association_id():
162    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc-elastic-ip-association"])
163
164
165def random_internet_gateway_id():
166    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["internet-gateway"])
167
168
169def random_egress_only_internet_gateway_id():
170    return random_id(
171        prefix=EC2_RESOURCE_TO_PREFIX["egress-only-internet-gateway"], size=17
172    )
173
174
175def random_route_table_id():
176    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["route-table"])
177
178
179def random_eip_allocation_id():
180    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["vpc-elastic-ip"])
181
182
183def random_dhcp_option_id():
184    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["dhcp-options"])
185
186
187def random_eni_id():
188    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["network-interface"])
189
190
191def random_eni_attach_id():
192    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["network-interface-attachment"])
193
194
195def random_nat_gateway_id():
196    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["nat-gateway"], size=17)
197
198
199def random_transit_gateway_id():
200    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["transit-gateway"], size=17)
201
202
203def random_transit_gateway_route_table_id():
204    return random_id(
205        prefix=EC2_RESOURCE_TO_PREFIX["transit-gateway-route-table"], size=17
206    )
207
208
209def random_transit_gateway_attachment_id():
210    return random_id(
211        prefix=EC2_RESOURCE_TO_PREFIX["transit-gateway-attachment"], size=17
212    )
213
214
215def random_launch_template_id():
216    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["launch-template"], size=17)
217
218
219def random_iam_instance_profile_association_id():
220    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["iam-instance-profile-association"])
221
222
223def random_carrier_gateway_id():
224    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["carrier-gateway"], size=17)
225
226
227def random_public_ip():
228    return "54.214.{0}.{1}".format(random.choice(range(255)), random.choice(range(255)))
229
230
231def random_private_ip(cidr=None, ipv6=False):
232    # prefix - ula.prefixlen : get number of remaing length for the IP.
233    #                          prefix will be 32 for IPv4 and 128 for IPv6.
234    #  random.getrandbits() will generate remaining bits for IPv6 or Ipv4 in decimal format
235    if cidr:
236        if ipv6:
237            ula = ipaddress.IPv6Network(cidr)
238            return str(ula.network_address + (random.getrandbits(128 - ula.prefixlen)))
239        ula = ipaddress.IPv4Network(cidr)
240        return str(ula.network_address + (random.getrandbits(32 - ula.prefixlen)))
241    if ipv6:
242        return "2001::cafe:%x/64" % random.getrandbits(16)
243    return "10.{0}.{1}.{2}".format(
244        random.choice(range(255)), random.choice(range(255)), random.choice(range(255))
245    )
246
247
248def random_ip():
249    return "127.{0}.{1}.{2}".format(
250        random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)
251    )
252
253
254def generate_dns_from_ip(ip, type="internal"):
255    splits = ip.split("/")[0].split(".") if "/" in ip else ip.split(".")
256    return "ip-{}-{}-{}-{}.ec2.{}".format(
257        splits[0], splits[1], splits[2], splits[3], type
258    )
259
260
261def random_mac_address():
262    return "02:00:00:%02x:%02x:%02x" % (
263        random.randint(0, 255),
264        random.randint(0, 255),
265        random.randint(0, 255),
266    )
267
268
269def randor_ipv4_cidr():
270    return "10.0.{}.{}/16".format(random.randint(0, 255), random.randint(0, 255))
271
272
273def random_ipv6_cidr():
274    return "2400:6500:{}:{}00::/56".format(random_resource_id(4), random_resource_id(2))
275
276
277def generate_route_id(
278    route_table_id, cidr_block, ipv6_cidr_block=None, prefix_list=None
279):
280    if ipv6_cidr_block and not cidr_block:
281        cidr_block = ipv6_cidr_block
282    if prefix_list and not cidr_block:
283        cidr_block = prefix_list
284    return "%s~%s" % (route_table_id, cidr_block)
285
286
287def random_managed_prefix_list_id():
288    return random_id(prefix=EC2_RESOURCE_TO_PREFIX["managed-prefix-list"], size=8)
289
290
291def create_dns_entries(service_name, vpc_endpoint_id):
292    dns_entries = {}
293    dns_entries["dns_name"] = "{}-{}.{}".format(
294        vpc_endpoint_id, random_resource_id(8), service_name
295    )
296    dns_entries["hosted_zone_id"] = random_resource_id(13).upper()
297    return dns_entries
298
299
300def split_route_id(route_id):
301    values = route_id.split("~")
302    return values[0], values[1]
303
304
305def dhcp_configuration_from_querystring(querystring, option="DhcpConfiguration"):
306    """
307    turn:
308        {u'AWSAccessKeyId': [u'the_key'],
309         u'Action': [u'CreateDhcpOptions'],
310         u'DhcpConfiguration.1.Key': [u'domain-name'],
311         u'DhcpConfiguration.1.Value.1': [u'example.com'],
312         u'DhcpConfiguration.2.Key': [u'domain-name-servers'],
313         u'DhcpConfiguration.2.Value.1': [u'10.0.0.6'],
314         u'DhcpConfiguration.2.Value.2': [u'10.0.0.7'],
315         u'Signature': [u'uUMHYOoLM6r+sT4fhYjdNT6MHw22Wj1mafUpe0P0bY4='],
316         u'SignatureMethod': [u'HmacSHA256'],
317         u'SignatureVersion': [u'2'],
318         u'Timestamp': [u'2014-03-18T21:54:01Z'],
319         u'Version': [u'2013-10-15']}
320    into:
321        {u'domain-name': [u'example.com'], u'domain-name-servers': [u'10.0.0.6', u'10.0.0.7']}
322    """
323
324    key_needle = re.compile("{0}.[0-9]+.Key".format(option), re.UNICODE)
325    response_values = {}
326
327    for key, value in querystring.items():
328        if key_needle.match(key):
329            values = []
330            key_index = key.split(".")[1]
331            value_index = 1
332            while True:
333                value_key = "{0}.{1}.Value.{2}".format(option, key_index, value_index)
334                if value_key in querystring:
335                    values.extend(querystring[value_key])
336                else:
337                    break
338                value_index += 1
339            response_values[value[0]] = values
340    return response_values
341
342
343def filters_from_querystring(querystring_dict):
344    response_values = {}
345    last_tag_key = None
346    for key, value in sorted(querystring_dict.items()):
347        match = re.search(r"Filter.(\d).Name", key)
348        if match:
349            filter_index = match.groups()[0]
350            value_prefix = "Filter.{0}.Value".format(filter_index)
351            filter_values = [
352                filter_value[0]
353                for filter_key, filter_value in querystring_dict.items()
354                if filter_key.startswith(value_prefix)
355            ]
356            if value[0] == "tag-key":
357                last_tag_key = "tag:" + filter_values[0]
358            elif last_tag_key and value[0] == "tag-value":
359                response_values[last_tag_key] = filter_values
360            response_values[value[0]] = filter_values
361    return response_values
362
363
364def dict_from_querystring(parameter, querystring_dict):
365    use_dict = {}
366    for key, value in querystring_dict.items():
367        match = re.search(r"{0}.(\d).(\w+)".format(parameter), key)
368        if match:
369            use_dict_index = match.groups()[0]
370            use_dict_element_property = match.groups()[1]
371
372            if not use_dict.get(use_dict_index):
373                use_dict[use_dict_index] = {}
374            use_dict[use_dict_index][use_dict_element_property] = value[0]
375
376    return use_dict
377
378
379def get_attribute_value(parameter, querystring_dict):
380    for key, value in querystring_dict.items():
381        match = re.search(r"{0}.Value".format(parameter), key)
382        if match:
383            if value[0].lower() in ["true", "false"]:
384                return True if value[0].lower() in ["true"] else False
385            return value[0]
386    return None
387
388
389def get_object_value(obj, attr):
390    keys = attr.split(".")
391    val = obj
392    for key in keys:
393        if key == "owner_id":
394            return ACCOUNT_ID
395        elif hasattr(val, key):
396            val = getattr(val, key)
397        elif isinstance(val, dict):
398            val = val[key]
399        elif isinstance(val, list):
400            for item in val:
401                item_val = get_object_value(item, key)
402                if item_val:
403                    return item_val
404        else:
405            return None
406    return val
407
408
409def is_tag_filter(filter_name):
410    return (
411        filter_name.startswith("tag:")
412        or filter_name.startswith("tag-value")
413        or filter_name.startswith("tag-key")
414    )
415
416
417def get_obj_tag(obj, filter_name):
418    tag_name = filter_name.replace("tag:", "", 1)
419    tags = dict((tag["key"], tag["value"]) for tag in obj.get_tags())
420    return tags.get(tag_name)
421
422
423def get_obj_tag_names(obj):
424    tags = set((tag["key"] for tag in obj.get_tags()))
425    return tags
426
427
428def get_obj_tag_values(obj, key=None):
429    tags = set(
430        (tag["value"] for tag in obj.get_tags() if tag["key"] == key or key is None)
431    )
432    return tags
433
434
435def add_tag_specification(tags):
436    tags = tags[0] if isinstance(tags, list) and len(tags) == 1 else tags
437    tags = (tags or {}).get("Tag", [])
438    tags = {t["Key"]: t["Value"] for t in tags}
439    return tags
440
441
442def tag_filter_matches(obj, filter_name, filter_values):
443    regex_filters = [re.compile(simple_aws_filter_to_re(f)) for f in filter_values]
444    if filter_name == "tag-key":
445        tag_values = get_obj_tag_names(obj)
446    elif filter_name == "tag-value":
447        tag_values = get_obj_tag_values(obj)
448    elif filter_name.startswith("tag:"):
449        key = filter_name[4:]
450        tag_values = get_obj_tag_values(obj, key=key)
451    else:
452        tag_values = [get_obj_tag(obj, filter_name) or ""]
453
454    for tag_value in tag_values:
455        if any(regex.match(tag_value) for regex in regex_filters):
456            return True
457
458    return False
459
460
461filter_dict_attribute_mapping = {
462    "instance-state-name": "state",
463    "instance-id": "id",
464    "state-reason-code": "_state_reason.code",
465    "source-dest-check": "source_dest_check",
466    "vpc-id": "vpc_id",
467    "group-id": "security_groups.id",
468    "instance.group-id": "security_groups.id",
469    "instance.group-name": "security_groups.name",
470    "instance-type": "instance_type",
471    "private-ip-address": "private_ip",
472    "ip-address": "public_ip",
473    "availability-zone": "placement",
474    "architecture": "architecture",
475    "image-id": "image_id",
476    "network-interface.private-dns-name": "private_dns",
477    "private-dns-name": "private_dns",
478    "owner-id": "owner_id",
479    "subnet-id": "subnet_id",
480}
481
482
483def passes_filter_dict(instance, filter_dict):
484    for filter_name, filter_values in filter_dict.items():
485        if filter_name in filter_dict_attribute_mapping:
486            instance_attr = filter_dict_attribute_mapping[filter_name]
487            instance_value = get_object_value(instance, instance_attr)
488            if not instance_value_in_filter_values(instance_value, filter_values):
489                return False
490
491        elif is_tag_filter(filter_name):
492            if not tag_filter_matches(instance, filter_name, filter_values):
493                return False
494        else:
495            raise NotImplementedError(
496                "Filter dicts have not been implemented in Moto for '%s' yet. Feel free to open an issue at https://github.com/spulec/moto/issues"
497                % filter_name
498            )
499    return True
500
501
502def instance_value_in_filter_values(instance_value, filter_values):
503    if isinstance(instance_value, list):
504        if not set(filter_values).intersection(set(instance_value)):
505            return False
506    elif instance_value not in filter_values:
507        return False
508    return True
509
510
511def filter_reservations(reservations, filter_dict):
512    result = []
513    for reservation in reservations:
514        new_instances = []
515        for instance in reservation.instances:
516            if passes_filter_dict(instance, filter_dict):
517                new_instances.append(instance)
518        if new_instances:
519            reservation.instances = new_instances
520            result.append(reservation)
521    return result
522
523
524filter_dict_igw_mapping = {
525    "attachment.vpc-id": "vpc.id",
526    "attachment.state": "attachment_state",
527    "internet-gateway-id": "id",
528}
529
530
531def passes_igw_filter_dict(igw, filter_dict):
532    for filter_name, filter_values in filter_dict.items():
533        if filter_name in filter_dict_igw_mapping:
534            igw_attr = filter_dict_igw_mapping[filter_name]
535            if get_object_value(igw, igw_attr) not in filter_values:
536                return False
537        elif is_tag_filter(filter_name):
538            if not tag_filter_matches(igw, filter_name, filter_values):
539                return False
540        else:
541            raise NotImplementedError(
542                "Internet Gateway filter dicts have not been implemented in Moto for '%s' yet. Feel free to open an issue at https://github.com/spulec/moto/issues",
543                filter_name,
544            )
545    return True
546
547
548def filter_internet_gateways(igws, filter_dict):
549    result = []
550    for igw in igws:
551        if passes_igw_filter_dict(igw, filter_dict):
552            result.append(igw)
553    return result
554
555
556def is_filter_matching(obj, filter, filter_value):
557    value = obj.get_filter_value(filter)
558
559    if filter_value is None:
560        return False
561
562    if isinstance(value, str):
563        if not isinstance(filter_value, list):
564            filter_value = [filter_value]
565        if any(fnmatch.fnmatch(value, pattern) for pattern in filter_value):
566            return True
567        return False
568
569    if isinstance(value, type({}.keys())):
570        if isinstance(filter_value, str) and filter_value in value:
571            return True
572
573    try:
574        value = set(value)
575        return (value and value.issubset(filter_value)) or value.issuperset(
576            filter_value
577        )
578    except TypeError:
579        return value in filter_value
580
581
582def generic_filter(filters, objects):
583    if filters:
584        for (_filter, _filter_value) in filters.items():
585            objects = [
586                obj
587                for obj in objects
588                if is_filter_matching(obj, _filter, _filter_value)
589            ]
590
591    return objects
592
593
594def simple_aws_filter_to_re(filter_string):
595    tmp_filter = filter_string.replace(r"\?", "[?]")
596    tmp_filter = tmp_filter.replace(r"\*", "[*]")
597    tmp_filter = fnmatch.translate(tmp_filter)
598    return tmp_filter
599
600
601def random_key_pair():
602    private_key = rsa.generate_private_key(
603        public_exponent=65537, key_size=2048, backend=default_backend()
604    )
605    private_key_material = private_key.private_bytes(
606        encoding=serialization.Encoding.PEM,
607        format=serialization.PrivateFormat.TraditionalOpenSSL,
608        encryption_algorithm=serialization.NoEncryption(),
609    )
610    public_key_fingerprint = rsa_public_key_fingerprint(private_key.public_key())
611
612    return {
613        "fingerprint": public_key_fingerprint,
614        "material": private_key_material.decode("ascii"),
615    }
616
617
618def get_prefix(resource_id):
619    resource_id_prefix, separator, after = resource_id.partition("-")
620    if resource_id_prefix == EC2_RESOURCE_TO_PREFIX["transit-gateway"]:
621        if after.startswith("rtb"):
622            resource_id_prefix = EC2_RESOURCE_TO_PREFIX["transit-gateway-route-table"]
623        if after.startswith("attach"):
624            resource_id_prefix = EC2_RESOURCE_TO_PREFIX["transit-gateway-attachment"]
625    if resource_id_prefix == EC2_RESOURCE_TO_PREFIX["network-interface"]:
626        if after.startswith("attach"):
627            resource_id_prefix = EC2_RESOURCE_TO_PREFIX["network-interface-attachment"]
628    if resource_id_prefix not in EC2_RESOURCE_TO_PREFIX.values():
629        uuid4hex = re.compile(r"[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}\Z", re.I)
630        if uuid4hex.match(resource_id) is not None:
631            resource_id_prefix = EC2_RESOURCE_TO_PREFIX["reserved-instance"]
632        else:
633            return None
634    return resource_id_prefix
635
636
637def is_valid_resource_id(resource_id):
638    valid_prefixes = EC2_RESOURCE_TO_PREFIX.values()
639    resource_id_prefix = get_prefix(resource_id)
640    if resource_id_prefix not in valid_prefixes:
641        return False
642    resource_id_pattern = resource_id_prefix + "-[0-9a-f]{8}"
643    resource_pattern_re = re.compile(resource_id_pattern)
644    return resource_pattern_re.match(resource_id) is not None
645
646
647def is_valid_cidr(cird):
648    cidr_pattern = r"^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$"
649    cidr_pattern_re = re.compile(cidr_pattern)
650    return cidr_pattern_re.match(cird) is not None
651
652
653def is_valid_ipv6_cidr(cird):
654    cidr_pattern = r"^s*((([0-9A-Fa-f]{1,4}:){7}([0-9A-Fa-f]{1,4}|:))|(([0-9A-Fa-f]{1,4}:){6}(:[0-9A-Fa-f]{1,4}|((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){5}(((:[0-9A-Fa-f]{1,4}){1,2})|:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3})|:))|(([0-9A-Fa-f]{1,4}:){4}(((:[0-9A-Fa-f]{1,4}){1,3})|((:[0-9A-Fa-f]{1,4})?:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){3}(((:[0-9A-Fa-f]{1,4}){1,4})|((:[0-9A-Fa-f]{1,4}){0,2}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){2}(((:[0-9A-Fa-f]{1,4}){1,5})|((:[0-9A-Fa-f]{1,4}){0,3}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(([0-9A-Fa-f]{1,4}:){1}(((:[0-9A-Fa-f]{1,4}){1,6})|((:[0-9A-Fa-f]{1,4}){0,4}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:))|(:(((:[0-9A-Fa-f]{1,4}){1,7})|((:[0-9A-Fa-f]{1,4}){0,5}:((25[0-5]|2[0-4]d|1dd|[1-9]?d)(.(25[0-5]|2[0-4]d|1dd|[1-9]?d)){3}))|:)))(%.+)?s*(\/([0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8]))?$"
655    cidr_pattern_re = re.compile(cidr_pattern)
656    return cidr_pattern_re.match(cird) is not None
657
658
659def generate_instance_identity_document(instance):
660    """
661    http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
662
663    A JSON file that describes an instance. Usually retrieved by URL:
664    http://169.254.169.254/latest/dynamic/instance-identity/document
665    Here we just fill a dictionary that represents the document
666
667    Typically, this document is used by the amazon-ecs-agent when registering a
668    new ContainerInstance
669    """
670
671    document = {
672        "devPayProductCodes": None,
673        "availabilityZone": instance.placement["AvailabilityZone"],
674        "privateIp": instance.private_ip_address,
675        "version": "2010-8-31",
676        "region": instance.placement["AvailabilityZone"][:-1],
677        "instanceId": instance.id,
678        "billingProducts": None,
679        "instanceType": instance.instance_type,
680        "accountId": "012345678910",
681        "pendingTime": "2015-11-19T16:32:11Z",
682        "imageId": instance.image_id,
683        "kernelId": instance.kernel_id,
684        "ramdiskId": instance.ramdisk_id,
685        "architecture": instance.architecture,
686    }
687
688    return document
689
690
691def rsa_public_key_parse(key_material):
692    # These imports take ~.5s; let's keep them local
693    import sshpubkeys.exceptions
694    from sshpubkeys.keys import SSHKey
695
696    try:
697        if not isinstance(key_material, bytes):
698            key_material = key_material.encode("ascii")
699
700        decoded_key = base64.b64decode(key_material).decode("ascii")
701        public_key = SSHKey(decoded_key)
702    except (sshpubkeys.exceptions.InvalidKeyException, UnicodeDecodeError):
703        raise ValueError("bad key")
704
705    if not public_key.rsa:
706        raise ValueError("bad key")
707
708    return public_key.rsa
709
710
711def rsa_public_key_fingerprint(rsa_public_key):
712    key_data = rsa_public_key.public_bytes(
713        encoding=serialization.Encoding.DER,
714        format=serialization.PublicFormat.SubjectPublicKeyInfo,
715    )
716    fingerprint_hex = hashlib.md5(key_data).hexdigest()
717    fingerprint = re.sub(r"([a-f0-9]{2})(?!$)", r"\1:", fingerprint_hex)
718    return fingerprint
719
720
721def filter_iam_instance_profile_associations(iam_instance_associations, filter_dict):
722    if not filter_dict:
723        return iam_instance_associations
724    result = []
725    for iam_instance_association in iam_instance_associations:
726        filter_passed = True
727        if filter_dict.get("instance-id"):
728            if (
729                iam_instance_association.instance.id
730                not in filter_dict.get("instance-id").values()
731            ):
732                filter_passed = False
733        if filter_dict.get("state"):
734            if iam_instance_association.state not in filter_dict.get("state").values():
735                filter_passed = False
736        if filter_passed:
737            result.append(iam_instance_association)
738    return result
739
740
741def filter_iam_instance_profiles(iam_instance_profile_arn, iam_instance_profile_name):
742    instance_profile = None
743    instance_profile_by_name = None
744    instance_profile_by_arn = None
745    if iam_instance_profile_name:
746        instance_profile_by_name = iam_backends["global"].get_instance_profile(
747            iam_instance_profile_name
748        )
749        instance_profile = instance_profile_by_name
750    if iam_instance_profile_arn:
751        instance_profile_by_arn = iam_backends["global"].get_instance_profile_by_arn(
752            iam_instance_profile_arn
753        )
754        instance_profile = instance_profile_by_arn
755    # We would prefer instance profile that we found by arn
756    if iam_instance_profile_arn and iam_instance_profile_name:
757        if instance_profile_by_name == instance_profile_by_arn:
758            instance_profile = instance_profile_by_arn
759        else:
760            instance_profile = None
761
762    return instance_profile
763
764
765def describe_tag_filter(filters, instances):
766    result = instances.copy()
767    for instance in instances:
768        for key in filters:
769            if key.startswith("tag:"):
770                match = re.match(r"tag:(.*)", key)
771                if match:
772                    tag_key_name = match.group(1)
773                    need_delete = True
774                    for tag in instance.get_tags():
775                        if tag.get("key") == tag_key_name and tag.get(
776                            "value"
777                        ) in filters.get(key):
778                            need_delete = False
779                        elif tag.get("key") == tag_key_name and tag.get(
780                            "value"
781                        ) not in filters.get(key):
782                            need_delete = True
783                    if need_delete:
784                        result.remove(instance)
785    return result
786