AWS
===

.. contents::

IAM Authentication
------------------

Source reference: :py:meth:`hvac.api.auth_methods.Aws.iam_login`

Static Access Key Strings
`````````````````````````

Various examples of authenticating with static access key strings:

.. code:: python

    import hvac

    client = hvac.Client()

    client.auth.aws.iam_login('MY_AWS_ACCESS_KEY_ID', 'MY_AWS_SECRET_ACCESS_KEY')
    client.auth.aws.iam_login('MY_AWS_ACCESS_KEY_ID', 'MY_AWS_SECRET_ACCESS_KEY', 'MY_AWS_SESSION_TOKEN')
    client.auth.aws.iam_login('MY_AWS_ACCESS_KEY_ID', 'MY_AWS_SECRET_ACCESS_KEY', role='MY_ROLE')


Boto3 Session
`````````````

Retrieving credentials from a boto3 Session object:

.. code:: python

    import boto3
    import hvac

    session = boto3.Session()
    credentials = session.get_credentials()

    client = hvac.Client()
    client.auth.aws.iam_login(credentials.access_key, credentials.secret_key, credentials.token)

EC2 Metadata Service
````````````````````

Retrieving static instance role credentials within an EC2 instance using the EC2 metadata service (the EC2 auth method is probably a better fit for this case, which is outlined below under `EC2 Authentication`_):

.. code:: python

    import logging
    import requests
    from requests.exceptions import RequestException
    import hvac

    logger = logging.getLogger(__name__)

    EC2_METADATA_URL_BASE = 'http://169.254.169.254'

    def load_aws_ec2_role_iam_credentials(role_name, metadata_url_base=EC2_METADATA_URL_BASE):
        """
        Requests an ec2 instance's IAM security credentials from the EC2 metadata service.
        :param role_name: Name of the instance's role.
        :param metadata_url_base: IP address for the EC2 metadata service.
        :return: dict, unmarshalled JSON response of the instance's security credentials
        """
        metadata_pkcs7_url = '{base}/latest/meta-data/iam/security-credentials/{role}'.format(
            base=metadata_url_base,
            role=role_name,
        )
        logger.debug("load_aws_ec2_role_iam_credentials connecting to %s" % metadata_pkcs7_url)
        response = requests.get(url=metadata_pkcs7_url)
        response.raise_for_status()
        security_credentials = response.json()
        return security_credentials

    credentials = load_aws_ec2_role_iam_credentials('some-instance-role')

    client = hvac.Client()
    client.auth.aws.iam_login(credentials['AccessKeyId'], credentials['SecretAccessKey'], credentials['Token'])

Lambda and/or EC2 Instance
``````````````````````````

.. code:: python

    import os
    import hvac


    def infer_credentials_from_iam_role(iam_role):
        on_lambda = 'AWS_LAMBDA_FUNCTION_NAME' in os.environ
        if on_lambda:
            return os.environ['AWS_ACCESS_KEY_ID'], os.environ['AWS_SECRET_ACCESS_KEY'], os.environ['AWS_SESSION_TOKEN']
        else:
            security_credentials = load_aws_ec2_role_iam_credentials(iam_role)
            return security_credentials['AccessKeyId'], security_credentials['SecretAccessKey']


    access_key_id, secret_access_key, session_token = infer_credentials_from_iam_role('some-role')

    client = hvac.Client()
    client.auth.aws.iam_login(access_key_id, secret_access_key, session_token)

Caveats For Non-Default AWS Regions
```````````````````````````````````

I.e., calling :py:meth:`hvac.api.auth_methods.Aws.iam_login` with a `region` argument other than its default of "**us-east-1**". For additional background / context on this matter, see the comments at `hvac#251`_ and/or `vault-ruby#161`_.

The following code snippets are for authenticating hosts in the **us-west-1** region:

.. note::
    In order to authenticate to various regions, the AWS auth method configuration needs to be set up with an "endpoint URL" corresponding to the region in question. E.g.: "**https://sts.us-west-1.amazonaws.com**" in the case of this example. Vault defaults to an endpoint of "**https://sts.amazonaws.com**" if not configured with a different endpoint URL.

.. code:: python

    import boto3
    import hvac

    VAULT_ADDR = os.environ["VAULT_ADDR"]
    VAULT_HEADER_VALUE = os.environ["VAULT_HEADER_VALUE"]

    client = hvac.Client(url=VAULT_ADDR)

    # One-time setup of the credentials / configuration for the Vault server to use.
    # Note the explicit region subdomain bit included in the endpoint argument.
    client.auth.aws.configure(
        access_key='SOME_ACCESS_KEY_FOR_VAULTS_USE',
        secret_key='SOME_ACCESS_KEY_FOR_VAULTS_USE',
        endpoint='https://sts.us-west-1.amazonaws.com',
    )

    session = boto3.Session()
    creds = session.get_credentials().get_frozen_credentials()
    client.auth.aws.iam_login((
        access_key=creds.access_key,
        secret_key=creds.secret_key,
        session_token=creds.token,
        header_value=VAULT_HEADER_VALUE,
        role='some-role,
        use_token=True,
        region='us-west-1',
    )


EC2 Authentication
------------------

Source reference: :py:meth:`hvac.api.auth_methods.Aws.ec2_login`

EC2 Metadata Service
````````````````````

Authentication using EC2 instance role credentials and the EC2 metadata service

.. code:: python

    #!/usr/bin/env python
    import logging.handlers
    import os

    import hvac
    import requests
    from requests.exceptions import RequestException


    logger = logging.getLogger(__name__)

    VAULT_URL = os.getenv('VAULT_ADDR', 'https://127.0.0.1:8200')
    VAULT_CERTS = ('/etc/vault.d/ssl/bundle.crt', '/etc/vault.d/ssl/vault.key')
    TOKEN_NONCE_PATH = os.getenv('WP_VAULT_TOKEN_NONCE_PATH', '/root/.vault-token-meta-nonce')
    EC2_METADATA_URL_BASE = 'http://169.254.169.254'


    def load_aws_ec2_pkcs7_string(metadata_url_base=EC2_METADATA_URL_BASE):
        """
        Requests an ec2 instance's pkcs7-encoded identity document from the EC2 metadata service.
        :param metadata_url_base: IP address for the EC2 metadata service.
        :return: string, pkcs7-encoded identity document from the EC2 metadata service
        """
        metadata_pkcs7_url = '{base}/latest/dynamic/instance-identity/pkcs7'.format(base=metadata_url_base)
        logger.debug("load_aws_ec2_pkcs7_string connecting to %s" % metadata_pkcs7_url)

        response = requests.get(url=metadata_pkcs7_url)
        response.raise_for_status()

        pcks7 = response.text.replace('\n', '')

        return pcks7


    def load_aws_ec2_nonce_from_disk(token_nonce_path=TOKEN_NONCE_PATH):
        """
        Helper method to load a previously stored "token_meta_nonce" returned in the
        initial authorization AWS EC2 request from the current instance to our Vault service.
        :param token_nonce_path: string, the full filesystem path to a file containing the instance's
            token meta nonce.
        :return: string, a previously stored "token_meta_nonce"
        """
        logger.debug("Attempting to load vault token meta nonce from path: %s" % token_nonce_path)
        try:
            with open(token_nonce_path, 'rb') as nonce_file:
                nonce = nonce_file.readline()
        except IOError:
            logger.warning("Unable to load vault token meta nonce at path: %s" % token_nonce_path)
            nonce = None

        logger.debug("Nonce loaded: %s" % nonce)
        return nonce


    def write_aws_ec2_nonce_to_disk(token_meta_nonce, token_nonce_path=TOKEN_NONCE_PATH):
        """
        Helper method to store the current "token_meta_nonce" returned from authorization AWS EC2 request
        from the current instance to our Vault service.
        :return: string, a previously stored "token_meta_nonce"
        :param token_meta_nonce: string, the actual nonce
        :param token_nonce_path: string, the full filesystem path to a file containing the instance's
            token meta nonce.
        :return: None
        """
        logger.debug('Writing nonce "{0}" to file "{1}".'.format(token_meta_nonce, token_nonce_path))
        with open(token_nonce_path, 'w') as nonce_file:
            nonce_file.write(token_meta_nonce)


    def auth_ec2(vault_client, pkcs7=None, nonce=None, role=None, mount_point='aws', store_nonce=True):
        """
        Helper method to authenticate to vault using the "auth_ec2" backend.
        :param vault_client: hvac.Client
        :param pkcs7: pkcs7-encoded identity document from the EC2 metadata service
        :param nonce: string, the nonce retruned from the initial AWS EC2 auth request (if applicable)
        :param role: string, the role/policy to request. Defaults to the current instance's AMI ID if not provided.
        :param mount_point: string, the path underwhich the AWS EC2 auth backend is provided
        :param store_nonce: bool, if True, store the nonce received in the auth_ec2 response on disk for later use.
            Especially useful for automated secure introduction.
        :param kwargs: dict, remaining arguments blindly passed through by this lookup module class
        :return: None
        """
        if pkcs7 is None:
            logger.debug('No pkcs7 argument provided to auth_ec2 backend.')
            logger.debug('Attempting to retrieve information from EC2 metadata service.')
            pkcs7 = load_aws_ec2_pkcs7_string()

        if nonce is None:
            logger.debug('No nonce argument provided to auth_ec2 backend.'
            logger.debug('Attempting to retrieve information from disk.')
            nonce = load_aws_ec2_nonce_from_disk()

        auth_ec2_resp = vault_client.auth.aws.ec2_login(
            pkcs7=pkcs7,
            nonce=nonce,
            role=role,
            use_token=False,
            mount_point=mount_point
        )

        if store_nonce and 'metadata' in auth_ec2_resp.get('auth', dict()):
            token_meta_nonce = auth_ec2_resp['auth']['metadata'].get('nonce')
            if token_meta_nonce is not None:
                logger.debug('token_meta_nonce received back from auth_ec2 call: %s' % token_meta_nonce)
                write_aws_ec2_nonce_to_disk(token_meta_nonce)
            else:
                logger.warning('No token meta nonce returned in auth response.')

        return auth_ec2_resp


    def get_vault_client(vault_url=VAULT_URL, certs=VAULT_CERTS, verify_certs=True, ec2_role=None):
        """
        Instantiates a hvac / vault client.
        :param vault_url: string, protocol + address + port for the vault service
        :param certs: tuple, Optional tuple of self-signed certs to use for verification with hvac's requests
        :param verify_certs: bool, if True use the provided certs tuple for verification with hvac's requests.
            If False, don't verify SSL with hvac's requests (typically used with local development).
        :param ec2_role: str, Name of the Vault AWS auth backend role to use when retrieving a token (if applicable)
        :return: hvac.Client
        """
        logger.debug('Retrieving a vault (hvac) client...')
        if verify_certs:
            # We use a self-signed certificate for the vault service itself, so we need to include our
            # local ca bundle here for the underlying requests module.
            os.environ['REQUESTS_CA_BUNDLE'] = '/etc/ssl/certs/ca-certificates.crt'
            vault_client = hvac.Client(
                url=vault_url,
                cert=certs,
            )
        else:
            vault_client = hvac.Client(
                url=vault_url,
                verify=False,
            )

        vault_client.token = load_vault_token(vault_client, ec2_role=ec2_role)

        if not vault_client.is_authenticated():
            raise hvac.exceptions.Unauthorized('Unable to authenticate to the Vault service')

        return vault_client


    authenticated_vault_client = get_vault_client()

Methods
-------

Configure
`````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.configure`


Read Config
```````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.read_config`


Delete Config
`````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.delete_config`


Configure Identity Integration
``````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.configure_identity_integration`


Read Identity Integration
`````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.read_identity_integration`


Create Certificate Configuration
````````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.create_certificate_configuration`


Read Certificate Configuration
``````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.read_certificate_configuration`


Delete Certificate Configuration
````````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.delete_certificate_configuration`


List Certificate Configurations
```````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.list_certificate_configurations`


Create Sts Role
```````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.create_sts_role`


Read Sts Role
`````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.read_sts_role`


List Sts Roles
``````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.list_sts_roles`


Delete Sts Role
```````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.delete_sts_role`


Configure Identity Whitelist Tidy
`````````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.configure_identity_whitelist_tidy`


Read Identity Whitelist Tidy
````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.read_identity_whitelist_tidy`


Delete Identity Whitelist Tidy
``````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.delete_identity_whitelist_tidy`


Configure Role Tag Blacklist Tidy
`````````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.configure_role_tag_blacklist_tidy`


Read Role Tag Blacklist Tidy
````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.read_role_tag_blacklist_tidy`


Delete Role Tag Blacklist Tidy
``````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.delete_role_tag_blacklist_tidy`


Create Role
```````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.create_role`


Read Role
`````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.read_role`


List Roles
``````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.list_roles`


Delete Role
```````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.delete_role`


Create Role Tags
````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.create_role_tags`


IAM Login
`````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.iam_login`


EC2 Login
`````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.ec2_login`


Place Role Tags In Blacklist
````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.place_role_tags_in_blacklist`


Read Role Tag Blacklist
```````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.read_role_tag_blacklist`


List Blacklist Tags
```````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.list_blacklist_tags`


Delete Blacklist Tags
`````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.delete_blacklist_tags`


Tidy Blacklist Tags
```````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.tidy_blacklist_tags`


Read Identity Whitelist
```````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.read_identity_whitelist`


List Identity Whitelist
```````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.list_identity_whitelist`


Delete Identity Whitelist Entries
`````````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.delete_identity_whitelist_entries`


Tidy Identity Whitelist Entries
```````````````````````````````

Source reference: :py:meth:`hvac.api.auth_methods.Aws.tidy_identity_whitelist_entries`



.. _hvac#251: https://github.com/hvac/hvac/issues/251
.. _vault-ruby#161: https://github.com/hashicorp/vault-ruby/pull/161#issuecomment-355723269
