location-importer.in: import additional IP information for Amazon AWS IP networks

Message ID 7e30f16b-687e-62f2-bf1f-1a6f17616919@ipfire.org
State New
Headers show
Series
  • location-importer.in: import additional IP information for Amazon AWS IP networks
Related show

Commit Message

Peter Müller April 10, 2021, 12:28 p.m. UTC
Amazon publishes information regarding some of their IP networks
primarily used for AWS cloud services in a machine-readable format. To
improve libloc lookup results for these, we have little choice other
than importing and parsing them.

Unfortunately, there seems to be no machine-readable list of the
locations of their data centers or availability zones available. If
there _is_ any, please let the author know.

Fixes: #12594

Signed-off-by: Peter Müller <peter.mueller@ipfire.org>
---
 src/python/location-importer.in | 110 ++++++++++++++++++++++++++++++++
 1 file changed, 110 insertions(+)

Comments

Michael Tremer April 12, 2021, 9:57 a.m. UTC | #1
Hello Peter,

Thanks for this, I guess this would affect quite a few people out there…

However, is it a good idea to use the overrides table for this? Should that not be reserved for the pure overrides?

There is no way to view these changes. Is that something we can live with?

-Michael

> On 10 Apr 2021, at 13:28, Peter Müller <peter.mueller@ipfire.org> wrote:
> 
> Amazon publishes information regarding some of their IP networks
> primarily used for AWS cloud services in a machine-readable format. To
> improve libloc lookup results for these, we have little choice other
> than importing and parsing them.
> 
> Unfortunately, there seems to be no machine-readable list of the
> locations of their data centers or availability zones available. If
> there _is_ any, please let the author know.
> 
> Fixes: #12594
> 
> Signed-off-by: Peter Müller <peter.mueller@ipfire.org>
> ---
> src/python/location-importer.in | 110 ++++++++++++++++++++++++++++++++
> 1 file changed, 110 insertions(+)
> 
> diff --git a/src/python/location-importer.in b/src/python/location-importer.in
> index 1e08458..5be1d61 100644
> --- a/src/python/location-importer.in
> +++ b/src/python/location-importer.in
> @@ -19,6 +19,7 @@
> 
> import argparse
> import ipaddress
> +import json
> import logging
> import math
> import re
> @@ -931,6 +932,10 @@ class CLI(object):
> 				TRUNCATE TABLE network_overrides;
> 			""")
> 
> +			# Update overrides for various cloud providers big enough to publish their own IP
> +			# network allocation lists in a machine-readable format...
> +			self._update_overrides_for_aws()
> +
> 			for file in ns.files:
> 				log.info("Reading %s..." % file)
> 
> @@ -998,6 +1003,111 @@ class CLI(object):
> 						else:
> 							log.warning("Unsupported type: %s" % type)
> 
> +	def _update_overrides_for_aws(self):
> +		# Download Amazon AWS IP allocation file to create overrides...
> +		downloader = location.importer.Downloader()
> +
> +		try:
> +			with downloader.request("https://ip-ranges.amazonaws.com/ip-ranges.json", return_blocks=False) as f:
> +				aws_ip_dump = json.load(f.body)
> +		except Exception as e:
> +			log.error("unable to preprocess Amazon AWS IP ranges: %s" % e)
> +			return
> +
> +		# XXX: Set up a dictionary for mapping a region name to a country. Unfortunately,
> +		# there seems to be no machine-readable version available of this other than
> +		# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html
> +		# (worse, it seems to be incomplete :-/ ); https://www.cloudping.cloud/endpoints
> +		# was helpful here as well.
> +		aws_region_country_map = {
> +				"af-south-1": "ZA",
> +				"ap-east-1": "HK",
> +				"ap-south-1": "IN",
> +				"ap-south-2": "IN",
> +				"ap-northeast-3": "JP",
> +				"ap-northeast-2": "KR",
> +				"ap-southeast-1": "SG",
> +				"ap-southeast-2": "AU",
> +				"ap-southeast-3": "MY",
> +				"ap-northeast-1": "JP",
> +				"ca-central-1": "CA",
> +				"eu-central-1": "DE",
> +				"eu-central-2": "CH",
> +				"eu-west-1": "IE",
> +				"eu-west-2": "GB",
> +				"eu-south-1": "IT",
> +				"eu-south-2": "ES",
> +				"eu-west-3": "FR",
> +				"eu-north-1": "SE",
> +				"me-south-1": "BH",
> +				"sa-east-1": "BR"
> +				}
> +
> +		# Fetch all valid country codes to check parsed networks aganist...
> +		rows = self.db.query("SELECT * FROM countries ORDER BY country_code")
> +		validcountries = []
> +
> +		for row in rows:
> +			validcountries.append(row.country_code)
> +
> +		with self.db.transaction():
> +			for snetwork in aws_ip_dump["prefixes"] + aws_ip_dump["ipv6_prefixes"]:
> +				try:
> +					network = ipaddress.ip_network(snetwork.get("ip_prefix") or snetwork.get("ipv6_prefix"), strict=False)
> +				except ValueError:
> +					log.warning("Unable to parse line: %s" % snetwork)
> +					continue
> +
> +				# Sanitize parsed networks...
> +				if not self._check_parsed_network(network):
> +					continue
> +
> +				# Determine region of this network...
> +				region = snetwork["region"]
> +				cc = None
> +				is_anycast = False
> +
> +				# Any region name starting with "us-" will get "US" country code assigned straight away...
> +				if region.startswith("us-"):
> +					cc = "US"
> +				elif region.startswith("cn-"):
> +					# ... same goes for China ...
> +					cc = "CN"
> +				elif region == "GLOBAL":
> +					# ... funny region name for anycast-like networks ...
> +					is_anycast = True
> +				elif region in aws_region_country_map:
> +					# ... assign looked up country code otherwise ...
> +					cc = aws_region_country_map[region]
> +				else:
> +					# ... and bail out if we are missing something here
> +					log.warning("Unable to determine country code for line: %s" % snetwork)
> +					continue
> +
> +				# Skip networks with unknown country codes
> +				if not is_anycast and validcountries and cc not in validcountries:
> +					log.warning("Skipping Amazon AWS network with bogus country '%s': %s" % \
> +						(cc, network))
> +					return
> +
> +				# Conduct SQL statement...
> +				self.db.execute("""
> +					INSERT INTO network_overrides(
> +						network,
> +						country,
> +						is_anonymous_proxy,
> +						is_satellite_provider,
> +						is_anycast
> +					) VALUES (%s, %s, %s, %s, %s)
> +					ON CONFLICT (network) DO NOTHING""",
> +					"%s" % network,
> +					cc,
> +					None,
> +					None,
> +					is_anycast,
> +				)
> +
> +
> 	@staticmethod
> 	def _parse_bool(block, key):
> 		val = block.get(key)
> -- 
> 2.26.2
Peter Müller April 12, 2021, 5:48 p.m. UTC | #2
Hello Michael,

thanks for your reply.

Frankly, the longer I think about this patches' approach, the more I become unhappy with it:

(a) We are processing the Amazon AWS IP range feed overcredulous: It comes without being digitally signed
    in any way over a HTTPS connection - at least _I_ don't trust PKI, and should probably finally write that
    blog post about it planned for quite some time now :-/ - from a CDN. ip-ranges.amazonaws.com is not even
    DNSSEC-signed, not to mention DANE for their web service.

    Worse, my patch lacks additional safeguards. At the moment, the feeds' content is only checked for too big
    to too small prefixes, or anything not globally routable, and similar oddities. Amazon, however, must not
    publish any information regarding IP space they do not own - and if they do, we should not process it.

    While this does not eliminate the possible attack of somebody tampering with their feed on their server(s),
    the CDN, or anywhere in between, it would prevent a hostile actor to abuse that feed for arbitrarily spoofing
    the contents of a libloc database generated by us.

    Unfortunately, I have no elegant idea how to do this at the moment. A most basic approach would consist in
    rejecting any network not announced by ASNs we know are owned or maintained by Amazon - not sure how volatile
    this list would be.

    Only accepting information for networks whose RIR data proof ownership or maintenance by Amazon would be a
    more thorough approach, though. However, that involves bulk queries to the Whois, as a decent chunk of their
    IP space is assigned by ARIN. In case of RIPE et al., we might parse our way through the databases we already
    have, but this is laborious, and we have no routines for enumerating maintainer data yet.

(b) I honestly dislike intransparent changes here. Since we fill the override SQL table on demand every time,
    retracing content of generated location databases will be quite tricky if they did not originate from our own
    override files.

    On the other hand, we do not store the contents of the RIR databases downloaded, either. Simply dumping the
    Amazon AWS IP range feed into our Git repository would solve the transparency issue, but results in unnecessary
    bloat - unless we really need it someday.

    Do you have a particular idea about how to solve this issue in mind?

Regarding (a), the RIRs' FTP server FQDNs are at least DNSSEC-signed, but we do not enforce this. While I vaguely
remember to have seen signatures for the RIPE database, we currently do not validate it, either. Although this
would increase complexity and affects performance when generating a database at our end, I would propose to do so
whenever possible. Thoughts?

Sorry for this length and not very optimistic answer. If you ask me, you'll always get the worst-case scenario. :-)

After all, we are doing security here...

Thanks, and best regards,
Peter Müller


> Hello Peter,
> 
> Thanks for this, I guess this would affect quite a few people out there…
> 
> However, is it a good idea to use the overrides table for this? Should that not be reserved for the pure overrides?
> 
> There is no way to view these changes. Is that something we can live with?
> 
> -Michael
> 
>> On 10 Apr 2021, at 13:28, Peter Müller <peter.mueller@ipfire.org> wrote:
>>
>> Amazon publishes information regarding some of their IP networks
>> primarily used for AWS cloud services in a machine-readable format. To
>> improve libloc lookup results for these, we have little choice other
>> than importing and parsing them.
>>
>> Unfortunately, there seems to be no machine-readable list of the
>> locations of their data centers or availability zones available. If
>> there _is_ any, please let the author know.
>>
>> Fixes: #12594
>>
>> Signed-off-by: Peter Müller <peter.mueller@ipfire.org>
>> ---
>> src/python/location-importer.in | 110 ++++++++++++++++++++++++++++++++
>> 1 file changed, 110 insertions(+)
>>
>> diff --git a/src/python/location-importer.in b/src/python/location-importer.in
>> index 1e08458..5be1d61 100644
>> --- a/src/python/location-importer.in
>> +++ b/src/python/location-importer.in
>> @@ -19,6 +19,7 @@
>>
>> import argparse
>> import ipaddress
>> +import json
>> import logging
>> import math
>> import re
>> @@ -931,6 +932,10 @@ class CLI(object):
>> 				TRUNCATE TABLE network_overrides;
>> 			""")
>>
>> +			# Update overrides for various cloud providers big enough to publish their own IP
>> +			# network allocation lists in a machine-readable format...
>> +			self._update_overrides_for_aws()
>> +
>> 			for file in ns.files:
>> 				log.info("Reading %s..." % file)
>>
>> @@ -998,6 +1003,111 @@ class CLI(object):
>> 						else:
>> 							log.warning("Unsupported type: %s" % type)
>>
>> +	def _update_overrides_for_aws(self):
>> +		# Download Amazon AWS IP allocation file to create overrides...
>> +		downloader = location.importer.Downloader()
>> +
>> +		try:
>> +			with downloader.request("https://ip-ranges.amazonaws.com/ip-ranges.json", return_blocks=False) as f:
>> +				aws_ip_dump = json.load(f.body)
>> +		except Exception as e:
>> +			log.error("unable to preprocess Amazon AWS IP ranges: %s" % e)
>> +			return
>> +
>> +		# XXX: Set up a dictionary for mapping a region name to a country. Unfortunately,
>> +		# there seems to be no machine-readable version available of this other than
>> +		# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html
>> +		# (worse, it seems to be incomplete :-/ ); https://www.cloudping.cloud/endpoints
>> +		# was helpful here as well.
>> +		aws_region_country_map = {
>> +				"af-south-1": "ZA",
>> +				"ap-east-1": "HK",
>> +				"ap-south-1": "IN",
>> +				"ap-south-2": "IN",
>> +				"ap-northeast-3": "JP",
>> +				"ap-northeast-2": "KR",
>> +				"ap-southeast-1": "SG",
>> +				"ap-southeast-2": "AU",
>> +				"ap-southeast-3": "MY",
>> +				"ap-northeast-1": "JP",
>> +				"ca-central-1": "CA",
>> +				"eu-central-1": "DE",
>> +				"eu-central-2": "CH",
>> +				"eu-west-1": "IE",
>> +				"eu-west-2": "GB",
>> +				"eu-south-1": "IT",
>> +				"eu-south-2": "ES",
>> +				"eu-west-3": "FR",
>> +				"eu-north-1": "SE",
>> +				"me-south-1": "BH",
>> +				"sa-east-1": "BR"
>> +				}
>> +
>> +		# Fetch all valid country codes to check parsed networks aganist...
>> +		rows = self.db.query("SELECT * FROM countries ORDER BY country_code")
>> +		validcountries = []
>> +
>> +		for row in rows:
>> +			validcountries.append(row.country_code)
>> +
>> +		with self.db.transaction():
>> +			for snetwork in aws_ip_dump["prefixes"] + aws_ip_dump["ipv6_prefixes"]:
>> +				try:
>> +					network = ipaddress.ip_network(snetwork.get("ip_prefix") or snetwork.get("ipv6_prefix"), strict=False)
>> +				except ValueError:
>> +					log.warning("Unable to parse line: %s" % snetwork)
>> +					continue
>> +
>> +				# Sanitize parsed networks...
>> +				if not self._check_parsed_network(network):
>> +					continue
>> +
>> +				# Determine region of this network...
>> +				region = snetwork["region"]
>> +				cc = None
>> +				is_anycast = False
>> +
>> +				# Any region name starting with "us-" will get "US" country code assigned straight away...
>> +				if region.startswith("us-"):
>> +					cc = "US"
>> +				elif region.startswith("cn-"):
>> +					# ... same goes for China ...
>> +					cc = "CN"
>> +				elif region == "GLOBAL":
>> +					# ... funny region name for anycast-like networks ...
>> +					is_anycast = True
>> +				elif region in aws_region_country_map:
>> +					# ... assign looked up country code otherwise ...
>> +					cc = aws_region_country_map[region]
>> +				else:
>> +					# ... and bail out if we are missing something here
>> +					log.warning("Unable to determine country code for line: %s" % snetwork)
>> +					continue
>> +
>> +				# Skip networks with unknown country codes
>> +				if not is_anycast and validcountries and cc not in validcountries:
>> +					log.warning("Skipping Amazon AWS network with bogus country '%s': %s" % \
>> +						(cc, network))
>> +					return
>> +
>> +				# Conduct SQL statement...
>> +				self.db.execute("""
>> +					INSERT INTO network_overrides(
>> +						network,
>> +						country,
>> +						is_anonymous_proxy,
>> +						is_satellite_provider,
>> +						is_anycast
>> +					) VALUES (%s, %s, %s, %s, %s)
>> +					ON CONFLICT (network) DO NOTHING""",
>> +					"%s" % network,
>> +					cc,
>> +					None,
>> +					None,
>> +					is_anycast,
>> +				)
>> +
>> +
>> 	@staticmethod
>> 	def _parse_bool(block, key):
>> 		val = block.get(key)
>> -- 
>> 2.26.2
>
Michael Tremer April 14, 2021, 9:21 a.m. UTC | #3
Hello,

> On 12 Apr 2021, at 18:48, Peter Müller <peter.mueller@ipfire.org> wrote:
> 
> Hello Michael,
> 
> thanks for your reply.
> 
> Frankly, the longer I think about this patches' approach, the more I become unhappy with it:

Oh no. Don’t overthink it :)

> (a) We are processing the Amazon AWS IP range feed overcredulous: It comes without being digitally signed
>    in any way over a HTTPS connection - at least _I_ don't trust PKI, and should probably finally write that
>    blog post about it planned for quite some time now :-/ - from a CDN. ip-ranges.amazonaws.com is not even
>    DNSSEC-signed, not to mention DANE for their web service.

There would be no other way how we can authenticate this data. We do exactly the same with data from the RIRs.

>    Worse, my patch lacks additional safeguards. At the moment, the feeds' content is only checked for too big
>    to too small prefixes, or anything not globally routable, and similar oddities. Amazon, however, must not
>    publish any information regarding IP space they do not own - and if they do, we should not process it.

Do we not automatically filter those out later? Should we apply the same DELETE FROM … statements to the overrides table that we apply to the imported RIR data?

  https://git.ipfire.org/?p=location/libloc.git;a=blob;f=src/python/location-importer.in;h=1e08458223bad810d133c2f08703c7b3ee84fc72;hb=HEAD#l744

>    While this does not eliminate the possible attack of somebody tampering with their feed on their server(s),
>    the CDN, or anywhere in between, it would prevent a hostile actor to abuse that feed for arbitrarily spoofing
>    the contents of a libloc database generated by us.
> 
>    Unfortunately, I have no elegant idea how to do this at the moment. A most basic approach would consist in
>    rejecting any network not announced by ASNs we know are owned or maintained by Amazon - not sure how volatile
>    this list would be.

Probably single networks won’t be moved at all, but at the size of AWS I assume that new networks are added very often.

>    Only accepting information for networks whose RIR data proof ownership or maintenance by Amazon would be a
>    more thorough approach, though. However, that involves bulk queries to the Whois, as a decent chunk of their
>    IP space is assigned by ARIN. In case of RIPE et al., we might parse our way through the databases we already
>    have, but this is laborious, and we have no routines for enumerating maintainer data yet.

That would be a rather complicated process and I am not sure if it is worth it.

IP address space that has been acquired and is transitioning to AWS might not show up as owned by the right entity/entities and we might reject it. We simply cannot check this automatically as we cannot check any other IP network being owned by who ever it says.

> (b) I honestly dislike intransparent changes here. Since we fill the override SQL table on demand every time,
>    retracing content of generated location databases will be quite tricky if they did not originate from our own
>    override files.

I am a little bit unhappy with this as well. The overrides table also takes precedence. That is why I would have expected this in the networks table.

In a way, the RIRs are not transparent to us and we just import their data, do something with it and put it into our database. AWS is just another source of data just like the RIRs.

Although it isn’t perfect, I could live a lot better with this solution.

>    On the other hand, we do not store the contents of the RIR databases downloaded, either. Simply dumping the
>    Amazon AWS IP range feed into our Git repository would solve the transparency issue, but results in unnecessary
>    bloat - unless we really need it someday.
> 
>    Do you have a particular idea about how to solve this issue in mind?

See above.

> Regarding (a), the RIRs' FTP server FQDNs are at least DNSSEC-signed, but we do not enforce this. While I vaguely
> remember to have seen signatures for the RIPE database, we currently do not validate it, either. Although this
> would increase complexity and affects performance when generating a database at our end, I would propose to do so
> whenever possible. Thoughts?

Yes, we *should* do this, but I currently do not have any free time to work on it. Would be happy to support you on this.

> Sorry for this length and not very optimistic answer. If you ask me, you'll always get the worst-case scenario. :-)
> 
> After all, we are doing security here...

:)

-Michael

> 
> Thanks, and best regards,
> Peter Müller
> 
> 
>> Hello Peter,
>> 
>> Thanks for this, I guess this would affect quite a few people out there…
>> 
>> However, is it a good idea to use the overrides table for this? Should that not be reserved for the pure overrides?
>> 
>> There is no way to view these changes. Is that something we can live with?
>> 
>> -Michael
>> 
>>> On 10 Apr 2021, at 13:28, Peter Müller <peter.mueller@ipfire.org> wrote:
>>> 
>>> Amazon publishes information regarding some of their IP networks
>>> primarily used for AWS cloud services in a machine-readable format. To
>>> improve libloc lookup results for these, we have little choice other
>>> than importing and parsing them.
>>> 
>>> Unfortunately, there seems to be no machine-readable list of the
>>> locations of their data centers or availability zones available. If
>>> there _is_ any, please let the author know.
>>> 
>>> Fixes: #12594
>>> 
>>> Signed-off-by: Peter Müller <peter.mueller@ipfire.org>
>>> ---
>>> src/python/location-importer.in | 110 ++++++++++++++++++++++++++++++++
>>> 1 file changed, 110 insertions(+)
>>> 
>>> diff --git a/src/python/location-importer.in b/src/python/location-importer.in
>>> index 1e08458..5be1d61 100644
>>> --- a/src/python/location-importer.in
>>> +++ b/src/python/location-importer.in
>>> @@ -19,6 +19,7 @@
>>> 
>>> import argparse
>>> import ipaddress
>>> +import json
>>> import logging
>>> import math
>>> import re
>>> @@ -931,6 +932,10 @@ class CLI(object):
>>> 				TRUNCATE TABLE network_overrides;
>>> 			""")
>>> 
>>> +			# Update overrides for various cloud providers big enough to publish their own IP
>>> +			# network allocation lists in a machine-readable format...
>>> +			self._update_overrides_for_aws()
>>> +
>>> 			for file in ns.files:
>>> 				log.info("Reading %s..." % file)
>>> 
>>> @@ -998,6 +1003,111 @@ class CLI(object):
>>> 						else:
>>> 							log.warning("Unsupported type: %s" % type)
>>> 
>>> +	def _update_overrides_for_aws(self):
>>> +		# Download Amazon AWS IP allocation file to create overrides...
>>> +		downloader = location.importer.Downloader()
>>> +
>>> +		try:
>>> +			with downloader.request("https://ip-ranges.amazonaws.com/ip-ranges.json", return_blocks=False) as f:
>>> +				aws_ip_dump = json.load(f.body)
>>> +		except Exception as e:
>>> +			log.error("unable to preprocess Amazon AWS IP ranges: %s" % e)
>>> +			return
>>> +
>>> +		# XXX: Set up a dictionary for mapping a region name to a country. Unfortunately,
>>> +		# there seems to be no machine-readable version available of this other than
>>> +		# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html
>>> +		# (worse, it seems to be incomplete :-/ ); https://www.cloudping.cloud/endpoints
>>> +		# was helpful here as well.
>>> +		aws_region_country_map = {
>>> +				"af-south-1": "ZA",
>>> +				"ap-east-1": "HK",
>>> +				"ap-south-1": "IN",
>>> +				"ap-south-2": "IN",
>>> +				"ap-northeast-3": "JP",
>>> +				"ap-northeast-2": "KR",
>>> +				"ap-southeast-1": "SG",
>>> +				"ap-southeast-2": "AU",
>>> +				"ap-southeast-3": "MY",
>>> +				"ap-northeast-1": "JP",
>>> +				"ca-central-1": "CA",
>>> +				"eu-central-1": "DE",
>>> +				"eu-central-2": "CH",
>>> +				"eu-west-1": "IE",
>>> +				"eu-west-2": "GB",
>>> +				"eu-south-1": "IT",
>>> +				"eu-south-2": "ES",
>>> +				"eu-west-3": "FR",
>>> +				"eu-north-1": "SE",
>>> +				"me-south-1": "BH",
>>> +				"sa-east-1": "BR"
>>> +				}
>>> +
>>> +		# Fetch all valid country codes to check parsed networks aganist...
>>> +		rows = self.db.query("SELECT * FROM countries ORDER BY country_code")
>>> +		validcountries = []
>>> +
>>> +		for row in rows:
>>> +			validcountries.append(row.country_code)
>>> +
>>> +		with self.db.transaction():
>>> +			for snetwork in aws_ip_dump["prefixes"] + aws_ip_dump["ipv6_prefixes"]:
>>> +				try:
>>> +					network = ipaddress.ip_network(snetwork.get("ip_prefix") or snetwork.get("ipv6_prefix"), strict=False)
>>> +				except ValueError:
>>> +					log.warning("Unable to parse line: %s" % snetwork)
>>> +					continue
>>> +
>>> +				# Sanitize parsed networks...
>>> +				if not self._check_parsed_network(network):
>>> +					continue
>>> +
>>> +				# Determine region of this network...
>>> +				region = snetwork["region"]
>>> +				cc = None
>>> +				is_anycast = False
>>> +
>>> +				# Any region name starting with "us-" will get "US" country code assigned straight away...
>>> +				if region.startswith("us-"):
>>> +					cc = "US"
>>> +				elif region.startswith("cn-"):
>>> +					# ... same goes for China ...
>>> +					cc = "CN"
>>> +				elif region == "GLOBAL":
>>> +					# ... funny region name for anycast-like networks ...
>>> +					is_anycast = True
>>> +				elif region in aws_region_country_map:
>>> +					# ... assign looked up country code otherwise ...
>>> +					cc = aws_region_country_map[region]
>>> +				else:
>>> +					# ... and bail out if we are missing something here
>>> +					log.warning("Unable to determine country code for line: %s" % snetwork)
>>> +					continue
>>> +
>>> +				# Skip networks with unknown country codes
>>> +				if not is_anycast and validcountries and cc not in validcountries:
>>> +					log.warning("Skipping Amazon AWS network with bogus country '%s': %s" % \
>>> +						(cc, network))
>>> +					return
>>> +
>>> +				# Conduct SQL statement...
>>> +				self.db.execute("""
>>> +					INSERT INTO network_overrides(
>>> +						network,
>>> +						country,
>>> +						is_anonymous_proxy,
>>> +						is_satellite_provider,
>>> +						is_anycast
>>> +					) VALUES (%s, %s, %s, %s, %s)
>>> +					ON CONFLICT (network) DO NOTHING""",
>>> +					"%s" % network,
>>> +					cc,
>>> +					None,
>>> +					None,
>>> +					is_anycast,
>>> +				)
>>> +
>>> +
>>> 	@staticmethod
>>> 	def _parse_bool(block, key):
>>> 		val = block.get(key)
>>> -- 
>>> 2.26.2
>>

Patch

diff --git a/src/python/location-importer.in b/src/python/location-importer.in
index 1e08458..5be1d61 100644
--- a/src/python/location-importer.in
+++ b/src/python/location-importer.in
@@ -19,6 +19,7 @@ 
 
 import argparse
 import ipaddress
+import json
 import logging
 import math
 import re
@@ -931,6 +932,10 @@  class CLI(object):
 				TRUNCATE TABLE network_overrides;
 			""")
 
+			# Update overrides for various cloud providers big enough to publish their own IP
+			# network allocation lists in a machine-readable format...
+			self._update_overrides_for_aws()
+
 			for file in ns.files:
 				log.info("Reading %s..." % file)
 
@@ -998,6 +1003,111 @@  class CLI(object):
 						else:
 							log.warning("Unsupported type: %s" % type)
 
+	def _update_overrides_for_aws(self):
+		# Download Amazon AWS IP allocation file to create overrides...
+		downloader = location.importer.Downloader()
+
+		try:
+			with downloader.request("https://ip-ranges.amazonaws.com/ip-ranges.json", return_blocks=False) as f:
+				aws_ip_dump = json.load(f.body)
+		except Exception as e:
+			log.error("unable to preprocess Amazon AWS IP ranges: %s" % e)
+			return
+
+		# XXX: Set up a dictionary for mapping a region name to a country. Unfortunately,
+		# there seems to be no machine-readable version available of this other than
+		# https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/using-regions-availability-zones.html
+		# (worse, it seems to be incomplete :-/ ); https://www.cloudping.cloud/endpoints
+		# was helpful here as well.
+		aws_region_country_map = {
+				"af-south-1": "ZA",
+				"ap-east-1": "HK",
+				"ap-south-1": "IN",
+				"ap-south-2": "IN",
+				"ap-northeast-3": "JP",
+				"ap-northeast-2": "KR",
+				"ap-southeast-1": "SG",
+				"ap-southeast-2": "AU",
+				"ap-southeast-3": "MY",
+				"ap-northeast-1": "JP",
+				"ca-central-1": "CA",
+				"eu-central-1": "DE",
+				"eu-central-2": "CH",
+				"eu-west-1": "IE",
+				"eu-west-2": "GB",
+				"eu-south-1": "IT",
+				"eu-south-2": "ES",
+				"eu-west-3": "FR",
+				"eu-north-1": "SE",
+				"me-south-1": "BH",
+				"sa-east-1": "BR"
+				}
+
+		# Fetch all valid country codes to check parsed networks aganist...
+		rows = self.db.query("SELECT * FROM countries ORDER BY country_code")
+		validcountries = []
+
+		for row in rows:
+			validcountries.append(row.country_code)
+
+		with self.db.transaction():
+			for snetwork in aws_ip_dump["prefixes"] + aws_ip_dump["ipv6_prefixes"]:
+				try:
+					network = ipaddress.ip_network(snetwork.get("ip_prefix") or snetwork.get("ipv6_prefix"), strict=False)
+				except ValueError:
+					log.warning("Unable to parse line: %s" % snetwork)
+					continue
+
+				# Sanitize parsed networks...
+				if not self._check_parsed_network(network):
+					continue
+
+				# Determine region of this network...
+				region = snetwork["region"]
+				cc = None
+				is_anycast = False
+
+				# Any region name starting with "us-" will get "US" country code assigned straight away...
+				if region.startswith("us-"):
+					cc = "US"
+				elif region.startswith("cn-"):
+					# ... same goes for China ...
+					cc = "CN"
+				elif region == "GLOBAL":
+					# ... funny region name for anycast-like networks ...
+					is_anycast = True
+				elif region in aws_region_country_map:
+					# ... assign looked up country code otherwise ...
+					cc = aws_region_country_map[region]
+				else:
+					# ... and bail out if we are missing something here
+					log.warning("Unable to determine country code for line: %s" % snetwork)
+					continue
+
+				# Skip networks with unknown country codes
+				if not is_anycast and validcountries and cc not in validcountries:
+					log.warning("Skipping Amazon AWS network with bogus country '%s': %s" % \
+						(cc, network))
+					return
+
+				# Conduct SQL statement...
+				self.db.execute("""
+					INSERT INTO network_overrides(
+						network,
+						country,
+						is_anonymous_proxy,
+						is_satellite_provider,
+						is_anycast
+					) VALUES (%s, %s, %s, %s, %s)
+					ON CONFLICT (network) DO NOTHING""",
+					"%s" % network,
+					cc,
+					None,
+					None,
+					is_anycast,
+				)
+
+
 	@staticmethod
 	def _parse_bool(block, key):
 		val = block.get(key)