Package: aws-assume-role-lib
aws-assume-role-libAssumed role session chaining (with credential refreshing) for boto3The typical way to use boto3 when programmatically assuming a role is to explicitly callsts.AssumeRoleand use the returned credentials to create a newboto3.Sessionor client.
It looks like this mess of code:role_arn="arn:aws:iam::123456789012:role/MyRole"session=boto3.Session()sts=session.client("sts")response=sts.assume_role(RoleArn=role_arn,RoleSessionName="something_you_have_to_think_about")credentials=response["Credentials"]assumed_role_session=boto3.Session(aws_access_key_id=credentials["AccessKeyId"],aws_secret_access_key=credentials["SecretAccessKey"],aws_session_token=credentials["SessionToken"])# use the sessionprint(assumed_role_session.client("sts").get_caller_identity())This code is verbose, requires specifying a role session name even if you don't care what it is, and must explicitly handle credential expiration and refreshing if needed (in a Lambda function, this is typically handled by callingAssumeRolein every invocation).Withaws-assume-role-lib, all that collapses down to a single line. The assumed role session automatically refreshes expired credentials and generates a role session name if one is not provided.role_arn="arn:aws:iam::123456789012:role/MyRole"session=boto3.Session()assumed_role_session=aws_assume_role_lib.assume_role(session,role_arn)# use the sessionprint(assumed_role_session.client("sts").get_caller_identity())In a Lambda function that needs to assume a role, you can create the assumed role session during initialization and use it for the lifetime of the execution environment, withAssumeRolecalls only being made when necessary, not on every invocation.Note that in~/.aws/config,you have the option to have profiles that assume a role based on another profile, and this automatically handles refreshing expired credentials as well.If you've only usedboto3.client()and are not familiar with boto3 sessions,here's an explainer.Installationpipinstall--useraws-assume-role-libOr just addaws_assume_role_lib.pyto your project.View the release historyhere.Usageimportboto3fromaws_assume_role_libimportassume_role# Get a sessionsession=boto3.Session()# or with a profile:# session = boto3.Session(profile_name="my-profile")# Assume the sessionassumed_role_session=assume_role(session,"arn:aws:iam::123456789012:role/MyRole")# do stuff with the original credentialsprint(session.client("sts").get_caller_identity()["Arn"])# do stuff with the assumed roleprint(assumed_role_session.client("sts").get_caller_identity()["Arn"])In Lambda, initialize the sessions outside the handler, andAssumeRolewill only get called when necessary, rather than on every invocation:importosimportboto3fromaws_assume_role_libimportassume_role,generate_lambda_session_name# Get the Lambda sessionSESSION=boto3.Session()# Get the configROLE_ARN=os.environ["ROLE_ARN"]ROLE_SESSION_NAME=generate_lambda_session_name()# see below for details# Assume the sessionASSUMED_ROLE_SESSION=assume_role(SESSION,ROLE_ARN,RoleSessionName=ROLE_SESSION_NAME)defhandler(event,context):# do stuff with the Lambda role using SESSIONprint(SESSION.client("sts").get_caller_identity()["Arn"])# do stuff with the assumed role using ASSUMED_ROLE_SESSIONprint(ASSUMED_ROLE_SESSION.client("sts").get_caller_identity()["Arn"])Learn more about the benefits ofaws-assume-role-libin Lambda functions in thedemo.Interfaceassume_role(
    # required arguments
    session:           boto3.Session,
    RoleArn:           str,

    *,
    # keyword-only arguments for AssumeRole
    RoleSessionName:   str                                    = None,
    PolicyArns:        Union[list[dict[str, str]], list[str]] = None,
    Policy:            Union[str, dict]                       = None,
    DurationSeconds:   Union[int, datetime.timedelta]         = None,
    Tags:              list[dict[str, str]]                   = None,
    TransitiveTagKeys: list[str]                              = None,
    ExternalId:        str                                    = None,
    SerialNumber:      str                                    = None,
    TokenCode:         str                                    = None,
    SourceIdentity:    str                                    = None,
    additional_kwargs: dict                                   = None,

    # keyword-only arguments for returned session
    region_name:       Union[str, bool]                       = None,

    # keyword-only arguments for assume_role() itself
    validate:          bool                                   = True,
    cache:             dict                                   = None,
)assume_role()takes a session and a role ARN, and optionallyother keyword arguments forsts.AssumeRole.Unlike theAssumeRoleAPI call itself,RoleArnis required, butRoleSessionNameis not.
TheRoleSessionNameis set for you if it is not provided; it will use theSourceIdentityif that is provided, otherwise it will generated.
If you want this generated value forRoleSessionNamewhenSourceIdentityis provided (the behavior in v2.8 and before), setRoleSessionNameto the special valueaws_assume_role_lib.AUTOMATIC_ROLE_SESSION_NAME.Note that unlike the boto3 sts client method, you can provide thePolicyparameter (theinline session policy) as adictinstead of as a serialized JSON string,PolicyArnsas a list of ARNs, andDurationSecondsas adatetime.timedeltainstead of as an integer.By default, the session returned byassume_role()links its region configuration to the input session.
If you would like to set the region explicitly, pass it in theregion_nameparameter.Note that if the parent session was created without a region passed in to theSessionconstructor, it has an implicit region, based on searching potential configuration locations.
This means that the region used by the session can change (for example, if you set or changeos.environ["AWS_DEFAULT_REGION"]).
By default, the child session region is linked to the parent session, so if the parent session has an implicit region, or if the parent session's region is changed directly, they would both change.
If you would like to fix the child session region to be explicitly the current value, passregion_name=True.
If, for some reason, you have an explicit region set on the parent, and want the child to have implicit region config, passregion_name=False.By default,assume_role()checks if the parameters are invalid.
Without this validation, errors for these issues are more confusingly raised when the child session is first used to make an API call (boto3 doesn't make the call to retrieve credentials until they are needed).
However, this incurs a small time penalty, so parameter validation can be disabled by passingvalidate=False.If any new arguments are added toAssumeRolein the future and this library is not updated to allow them directly, they can be passed in as a dict via theadditional_kwargsargument.The parent session is available on the child session in theassume_role_parent_sessionproperty.
Note this property is added by this library; ordinary boto3 sessions do not have it.Patching boto3You can make theassume_role()function available directly in boto3 by callingpatch_boto3().
This creates aboto3.assume_role(RoleArn, ...)function (note that it does not take a session, it uses the same default session asboto3.client()), and adds aboto3.Session.assume_role()method.
So usage for that looks like:importboto3importaws_assume_role_libaws_assume_role_lib.patch_boto3()assumed_role_session=boto3.assume_role("arn:aws:iam::123456789012:role/MyRole")# the above is basically equivalent to:# aws_assume_role_lib.assume_role(boto3.Session(), "arn:aws:iam::123456789012:role/MyRole")session=boto3.Session(profile_name="my-profile")assumed_role_session=session.assume_role("arn:aws:iam::123456789012:role/MyRole")Role session names for Lambda functionsLearn more about the benefits ofaws-assume-role-libin Lambda functions in thedemo.If you don't provide a role session name, but you provide aSourceIdentity, this value is used for the role session name as well.
IfSourceIdentityis not provided either, the underlyingbotocorelibrarygenerates one using a timestamp.
That's the best it can do, because it doesn't have any other context.But in a Lambda function, we do have additional context, the Lambda function itself.
If you callgenerate_lambda_session_name()inside an instance of a Lambda function, it returns a session name that corresponds to the function instance, which you can use when assuming a role in the Lambda function (either with this library'sassume_role()or any other method).
The purpose of this is to simplify tracing usage of the session back to the function instance.The returned value is in one of the following forms, depending on the length of the values, to keep the session name within the maximum of 64 characters:{function_name}{function_name}.{identifier}{function_name}.{function_version}.{identifier}The function version is never included if it is$LATEST.The maximum role session name length is 64 characters. To ensure this, and
to provide at least 4 characters of the identifier when it is used, the
following rules apply, in order:If the function name is longer than 59 characters, the session name is the truncated function name.If the function name plus the function version is longer than 59 characters, the session name is the function name plus the identifier, truncated.Otherwise, the session name is the function name plus the version (if one is found and not $LATEST) plus the identifier, truncated.The identifier is the function instance's unique ID taken from the CloudWatch log stream name; if this cannot be found, it's a timestamp if the identifier can be at least 14 characters long (to provide for second-level precision), otherwise it is a random string.
The identifier will not be included unless at least 4 charactersThe values are automatically extracted fromthe relevant environment variables; you can override any of them by providing them as arguments to the function.ARN formattingassume_role()requires a role ARN, and if you know the role name and account id but have trouble remembering theexact format of role ARNs, there'sget_role_arn()for you.
There's additionally aget_assumed_role_session_arn()for formatting assumed role session ARNs.get_role_arn(
    account_id: Union[str, int],
    role_name:  str,
    path:       str             = "",
    partition:  str             = "aws",
)

get_assumed_role_session_arn(
    account_id:        Union[str, int],
    role_name:         str,
    role_session_name: str,
    partition:         str             = "aws",
)Forget_role_arn(), if the role name has a path, it can be provided as part of the name or as the separatepathargument (but not both).
Assumed role session ARNs do not include the role path; if it is used in the role name it is removed.CachingIf you would like to cache the credentials on the file system, you can use theJSONFileCacheclass, which will create files under the directory you provide in the constructor (which it will create if it doesn't exist).
Use it like:assumed_role_session=assume_role(session,"arn:aws:iam::123456789012:role/MyRole",cache=JSONFileCache("path/to/dir"))You can also use anydict-like object for the cache (supporting__getitem__/__setitem__/__contains__).Command line useaws-assume-role-libhas basic support for retrieving assumed role credentials from the command line.
In general, it's better to make profiles in~/.aws/configfor role assumption, like this:# this is a pre-existing profile you already have[profile profile-to-call-assume-role-with]# maybe it's IAM User credentials# or AWS SSO config# or whatever else you may have[profile my-assumed-role]role_arn=arn:aws:iam::123456789012:role/MyRole# optional: role_session_name = MyRoleSessionNamesource_profile=profile-to-call-assume-role-with# or instead of source_profile, you can tell it to# use external credentials. one of:# credential_source = Environment# credential_source = Ec2InstanceMetadata# credential_source = EcsContainerYou can usemy-assumed-rolelike any other profile.
It uses the AWS SDKs' built-in support for role assumption, rather than relying on this third party library.
It also gets you credential refreshing from the SDKs, where getting the credentials in the manner below cannot refresh them when they expire.But if you absolutely must have ad hoc role assumption on the command line, use the module invocation syntaxpython -m aws_assume_role_lib ROLE_ARN [OPTIONS].The options are:--profile: use a specific configuration profile.--env: print the credentials as environment variables (the default), suitable forexport $(python -m aws_assume_role_lib ...).--json: print the credentials incredential_process-formatted JSON format. Note that you don't normally need to use this as acredential_processin a profile, because you can just directly make the profile do role assumption as shown above.The remaining options are the arguments toassume_role():--RoleSessionName--PolicyArns: must be a comma-separated list of ARNs, a JSON list of ARNs, or a JSON object per the API--Policy: must be a JSON object--DurationSeconds--Tags: must be formatted asKey1=Value1,Key2=Value2, or a JSON object.--TransitiveTagKeys: must be a comma-separated list or a JSON list.--ExternalId--SerialNumber--TokenCode--SourceIdentity--additional-kwargs: must be a JSON object.