view configmix/extras/aws.py @ 766:a90273abc8a4

Make any notes regarding IMDSv2 and IPv4/IPv6
author Franz Glasner <f.glasner@feldmann-mg.com>
date Mon, 19 Feb 2024 15:49:58 +0100
parents f454889e41fa
children 538a579cf6fe
line wrap: on
line source

# -*- coding: utf-8 -*-
# :-
# :Copyright: (c) 2015-2022, Franz Glasner. All rights reserved.
# :License:   BSD-3-Clause. See LICENSE.txt for details.
# :-
"""AWS namespace implementation.

.. see:: - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html
         - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instancedata-data-retrieval.html
         - https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-identity-documents.html
         . IMDSv2: https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/instance-metadata-v2-how-it-works.html

"""

from __future__ import division, absolute_import, print_function


__all__ = []


import requests
import requests.exceptions
import requests.adapters
import urllib3


_MARKER = object()


#
# These are for IMDSv1 (Instance Metadata Service)
#
# There is a new session-oriented IMDSv2 implementation using
# IPv4 (169.254.169.254) and IPv6 ([fd00:ec2::254]).
#
URL_META_INSTANCEID = "http://169.254.169.254/latest/meta-data/instance-id"
URL_META_REGION = "http://169.254.169.254/latest/meta-data/placement/region"
URL_META_AVAILABILITY_ZONE = "http://169.254.169.254/latest/meta-data/availability-zone"
URL_DYN_INSTANCE_IDENTITY_DOC = "http://169.254.169.254/latest/dynamic/instance-identity/document"
TIMEOUT = 2
# See https://gist.github.com/doublenns/7e3e4b72df4aaeccbeabf87ba767f44e
RETRIES = urllib3.Retry(total=3, backoff_factor=0.3)

_meta_instanceid = None
_meta_region = None
_meta_availability_zone = None
_dyn_instance_identity_doc = None


def _get_text_req(url):
    with requests.Session() as sess:
        try:
            a = requests.adapters.HTTPAdapter(max_retries=RETRIES)
            sess.mount("http://", a)
            resp = sess.get(url, timeout=TIMEOUT)
            resp.raise_for_status()
            return resp.text
        except requests.exceptions.RequestException:
            return _MARKER


def _get_json_req(url):
    with requests.Session() as sess:
        try:
            a = requests.adapters.HTTPAdapter(max_retries=RETRIES)
            sess.mount("http://", a)
            resp = sess.get(url, timeout=TIMEOUT)
            resp.raise_for_status()
            return resp.json()
        except requests.exceptions.RequestException:
            return _MARKER
        except ValueError:
            # JSON error
            return _MARKER


def _get_meta_instanceid():
    global _meta_instanceid
    if _meta_instanceid is None:
        _meta_instanceid = _get_text_req(URL_META_INSTANCEID)
    return _meta_instanceid


def _get_meta_region():
    global _meta_region
    if _meta_region is None:
        _meta_region = _get_text_req(URL_META_REGION)
    return _meta_region


def _get_meta_avzone():
    global _meta_availability_zone
    if _meta_availability_zone is None:
        _meta_availability_zone = _get_text_req(URL_META_AVAILABILITY_ZONE)
    return _meta_availability_zone


def _get_dyn_instance_identity_doc():
    global _dyn_instance_identity_doc
    if _dyn_instance_identity_doc is None:
        _dyn_instance_identity_doc = _get_json_req(
            URL_DYN_INSTANCE_IDENTITY_DOC)
    return _dyn_instance_identity_doc


def _awslookup(name, default=_MARKER):
    if name == "metadata.instance-id":
        v = _get_meta_instanceid()
    elif name == "metadata.placement.region":
        v = _get_meta_region()
    elif name == "metadata.placement.availability-zone":
        v = _get_meta_avzone()
    elif name.startswith("dynamic.instance-identity."):
        idoc = _get_dyn_instance_identity_doc()
        if idoc is _MARKER:
            v = _MARKER
        else:
            v = idoc.get(name[26:], _MARKER)
    else:
        v = _MARKER
    if v is _MARKER:
        if default is _MARKER:
            raise KeyError("key %r not found in the AWS namespace" % name)
        else:
            return default
    return v