0
votes

I'm working on a project with Python(3.6) & Django(1.10) in which I'm using aws apis but I'm new to aws and don't know how to authenticate a user.

My scenario is: I need to access user's aws resources like projects list, buckets list etc, for that, I need to authenticate the user when making a request to a particular API.

How can I do that in python? I'm new to aws.So, please don't mind my question.

Update:

Here's what i have tried: From views.py:

def boto3_with_role(role_arn, session_prefix, external_id, **kwargs):
"""
Create a partially applied session to assume a role with an external id.
A unique session_name will be generated by {session_prefix}_{time}
`session` can be passed, otherwise the default sesion will be used
see: http://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_use_switch-role-api.html
"""
sts = boto3.client('sts')
res = sts.assume_role(
    RoleArn=role_arn,
    RoleSessionName='{}_{}'.format(session_prefix, int(time.time())),
    ExternalId=external_id,
)
creds = res['Credentials']
return partial(boto3.session.Session,
               aws_access_key_id=creds['AccessKeyId'],
               aws_secret_access_key=creds['SecretAccessKey'],
               aws_session_token=creds['SessionToken']
               )

class AwsAuthentication(LoginRequiredMixin, CreateView):
def post(self, request, *args, **kwargs):
    AwsSession = boto3_with_role('ARN_LINK_FROM_CUSTOMER', 'MyPrefix',
                                 'EXTERNAL_ID_FROM_CUSTOMER')
    my_session = AwsSession()
    client = my_session.resource('s3')
    for bucket in client.buckets.all():
        print(bucket.name)
        return HttpResponse('Your Bucket is: {}'.format(bucket.name))

Now, it returns: botocore.exceptions.ClientError: An error occurred (InvalidClientTokenId) when calling the AssumeRole operation: The security token included in the request is invalid.

Help me, please!

Thanks in advance!

3
you can use boto3 or awscli to get access to those aws resource. but i didnt understand the user you talking about is your application user or third party user who trying to access resource of another user is it the case? - Manoj Jadhav
The user will authenticate itself from my application to aws, then my application will be able to access his resources! - Abdul Rehman
Yes, you can but you have to have aws credential of that user. - Manoj Jadhav
Using boto3 python library, the 'official' aws library. You can store some environment variables in settings.py, then you should authenticate for any request you make. - noes1s
the flow I want to build is as: I have a button in my app which says Authorize with aws when the user clicks on this button, he should be redirected to another browser tab where he needs to login to his aws account. - Abdul Rehman

3 Answers

2
votes

I'm not sure why some others are suggesting that you cannot do this. This is completely normal and well-supported - many AWS account users allow third-parties to operate in the user's AWS account on behalf of the user. Any third-party monitoring service or cost analysis service, for example.

[Bad way] The first way is to simply ask the user for IAM credentials (an access key and a secret key). Your app can use those to access the user's AWS account. This is simple, but don't do it.

[Good way] A better way to do this is via cross-account access. You have an AWS account, and your user has an AWS account. You instruct the user to create an IAM role in his account with an appropriate IAM policy (the minimum set of permissions that your app needs) and you instruct the user how to allow your account to assume this role in the user's account - to do this you advertize the IAM policy needed and your AWS account number. The user can then create an IAM policy, create a role with that policy, then establish a trust relationship between your accounts so that you can assume that IAM role and operate in his account. For more, see here and here.

0
votes

The functionality to have a user log into their AWS account to allow your application to access their AWS resources currently does not exist. A possible work around to consider is to provide the user with a direct link to creating an IAM role with a specific managed policy allowing a specific AWS account. You can then include that in your signup flow, and assume the role when you need to access resources in the user's account.

Example link:

https://console.aws.amazon.com/iam/home?region=us-west-2#/roles$new?step=review&roleType=crossAccount&accountID=123412341234&policies=arn:aws:iam::aws:policy%2FAdministratorAccess&roleName=AdminAccessForServiceName

0
votes

Note that the credentials you obtain from sts.AssumeRole call do expire after some time (in 15 minutes by default, but you can set it longer when doing the AssumeRole call, up to your current session max API session duration time, by default -- 1 hour) unless you refresh them.

If you need to automatically refresh the credentials, here I'm sharing my code I wrote after several hours of studying boto's code and trying to work around boto's dependency on file system and profiles.

Here I just used built-in boto's mechanism of caching and regularly refreshing the assumed credentials without touching any files:

from datetime import datetime

import boto3
from botocore.credentials import (
    AssumeRoleProvider,
    AssumeRoleCredentialFetcher,
    DeferredRefreshableCredentials,
    CredentialResolver
)
from dateutil.tz import tzlocal


class CustomAssumeRoleProvider(AssumeRoleProvider):
    """
    Overrides default AssumeRoleProvider to not use profiles from filesystem.
    """

    def __init__(self,
                 source_session: boto3.Session,
                 assume_role_arn: str,
                 expiry_window_seconds: int):
        super().__init__(
            load_config=lambda: source_session._session.full_config,
            client_creator=source_session._session.create_client,
            cache={},
            profile_name='not-used'
        )
        self.expiry_window_seconds = expiry_window_seconds
        self.source_session = source_session
        self.assume_role_arn = assume_role_arn
        assert assume_role_arn, "assume_role_arn is required"

    def load(self):
        fetcher = AssumeRoleCredentialFetcher(
            client_creator=self.source_session._session.create_client,
            source_credentials=self.source_session.get_credentials(),
            role_arn=self.assume_role_arn,
            expiry_window_seconds=self.expiry_window_seconds,
            cache=self.cache,
        )

        return DeferredRefreshableCredentials(
            method=self.METHOD,
            refresh_using=fetcher.fetch_credentials,
            time_fetcher=lambda: datetime.now(tzlocal())
        )


def get_assume_role_session(
    source_session: boto3.Session,
    assume_role_arn: str,
    expiry_window_seconds=15 * 60
) -> boto3.Session:
    """
    Creates a new boto3 session that will operate as of another user.

    Source session must have permission to call sts:AssumeRole on the provided ARN,
    and that ARN role must have been trusted to be assumed from this account (where source_session is from).

    See https://docs.aws.amazon.com/IAM/latest/UserGuide/tutorial_cross-account-with-roles.html

    Uses internal session._session to hack it together, as I haven't found another way.
    """
    # must have .load() method to be used in CredentialsResolver.
    provider = CustomAssumeRoleProvider(
        source_session=source_session,
        assume_role_arn=assume_role_arn,
        expiry_window_seconds=expiry_window_seconds
    )

    # must have .load_credentials() method to be used in register_component()
    resolver = CredentialResolver([provider])
    new_session = boto3.Session()
    new_session._session.register_component('credential_provider', resolver)
    return new_session