Intro
AWS is one of the most popular cloud service providers nowadays.
During the writing of this blog post, AWS has ~33% of the market, so it's very probably you will work with AWS, or you're already working with them 😉.
In today's blog post, I will show you how to assume an IAM role using Python and boto3
library to access another AWS account.
Sometimes you need to access something from a different AWS account. It is called cross account access.
For example, you may want to get a secret from the secrets manager.
By default, it is not possible, but AWS comes with a solution.
You can assume the role that has permission to the actions you want to do.
Once you do this, you can act on the other account.
You can read more about it here.
This blog post will be different,
I'm not going to go into details about what boto3 is,
or what iam role is.
Instead, I will jump straight to the code.
Code
I will use the example from the intro
section. We want to delete a secret from the secrets manager on a different AWS account.
The code together with the tests
is also available on my GitHub here 🚀.
# blog/examples/src/assume_iam_role/delete_secret.py
import os
from typing import Any
import structlog
from boto3 import Session
from botocore.exceptions import ClientError
from src.assume_iam_role import LambdaEvent, SecretsManagerSdkClient, STSSdkClient
from src.assume_iam_role.errors import SecretsDeletionError
LOGGER = structlog.get_logger()
def handle(event: LambdaEvent, context: Any, sts_sdk_client: STSSdkClient) -> LambdaEvent:
if "ROLE_ARN_TO_ASSUME" not in os.environ:
return event
try:
secret_id = event["secret_id"]
response = sts_sdk_client.assume_role(
RoleArn=os.environ["ROLE_ARN_TO_ASSUME"],
RoleSessionName="blogpost-delete-secret",
)
session = Session(
aws_access_key_id=response["Credentials"]["AccessKeyId"],
aws_secret_access_key=response["Credentials"]["SecretAccessKey"],
aws_session_token=response["Credentials"]["SessionToken"],
)
client: SecretsManagerSdkClient = session.client("secretsmanager")
LOGGER.info(f"Deleting secret from secretsmanager! secret_id={secret_id}")
response = client.delete_secret(SecretId=secret_id, ForceDeleteWithoutRecovery=True) # type: ignore
LOGGER.info(f"Successful request! Response = {response}")
return event
except ClientError as error:
if error.response["Error"]["Code"] == "ResourceNotFoundException":
return event
LOGGER.error(f"An error occurred during request to SecretsManager service. Error = {error}")
raise SecretsDeletionError
except Exception as error:
LOGGER.error(f"An unknown error occurred during `delete_secret.handle` function execution. Error = {error}")
raise SecretsDeletionError
and the test:
# blog/examples/tests/test_assume_iam_role/conftest.py
import os
from typing import Generator
import boto3
import pytest
from _pytest.monkeypatch import MonkeyPatch
from moto import mock_secretsmanager, mock_sts
from src.assume_iam_role import SecretsManagerSdkClient, STSSdkClient
@pytest.fixture(autouse=True)
def aws_envs() -> None:
os.environ["AWS_REGION"] = "eu-west-1"
os.environ["AWS_DEFAULT_REGION"] = "eu-west-1"
os.environ["AWS_ACCESS_KEY_ID"] = "XXX"
os.environ["AWS_SECRET_ACCESS_KEY"] = "XXX"
@pytest.fixture()
def different_account_id() -> str:
return "111111111111"
@pytest.fixture()
def secret_id() -> str:
return "/test/secret/blogpost/szymonmiks"
@pytest.fixture()
def mock_sts_client() -> Generator[STSSdkClient, None, None]:
with mock_sts():
client: STSSdkClient = boto3.client("sts")
yield client
@pytest.fixture()
def mock_secrets_manager_client(
secret_id: str, monkeypatch: MonkeyPatch, different_account_id: str
) -> Generator[SecretsManagerSdkClient, None, None]:
with mock_secretsmanager():
# http://docs.getmoto.org/en/latest/docs/multi_account.html#configure-an-account-using-sts
monkeypatch.setenv("MOTO_ACCOUNT_ID", different_account_id)
client: SecretsManagerSdkClient = boto3.client("secretsmanager")
client.create_secret(Name=secret_id)
yield client
from _pytest.monkeypatch import MonkeyPatch
from src.assume_iam_role import LambdaEvent, SecretsManagerSdkClient, STSSdkClient
from src.assume_iam_role.delete_secret import handle
def test_can_delete_secret_using_assumed_role(
mock_sts_client: STSSdkClient,
mock_secrets_manager_client: SecretsManagerSdkClient,
monkeypatch: MonkeyPatch,
secret_id: str,
different_account_id: str,
) -> None:
# given
event = LambdaEvent({"secret_id": secret_id})
monkeypatch.setenv("ROLE_ARN_TO_ASSUME", f"arn:aws:iam::{different_account_id}:role/test_role_to_assume")
# when
result = handle(event, None, mock_sts_client)
# then
assert result == event
assert mock_secrets_manager_client.list_secrets()["SecretList"] == []
That’s it. As simple as that 😄.
Happy coding 😉 .
Top comments (0)