Running a single python script across multiple AWS Account - amazon-web-services

I want to run a python script that will access different AWS Account(stg,qa,dev). How can I achieve this without violating any security best practices?

I agree with John with using iam roles to manage the access. You can extend this further by only using roles instead of users if you run your script from lambda.
To elaborate further, you will need to have two types of iam roles: target roles and a source role.
Target Role
The target roles will live in each account. This role will need to have permissions to be assumed by the source role.
Source Role
The source role will live in an account that you want to have access to all other accounts. This role will need to have the permission to assume other roles. The corresponding iam action is sts:AssumeRole.
Code
Below I have provided the code I have used in the past. Normally I would place this code in a lambda that is in an account that can control other accounts, such as a SharedServices or a DevOps account. You may want to refer to the boto3 documentation for the role assumption.
import boto3
def role_arn_to_session(**args):
client = boto3.client('sts')
response = client.assume_role(**args)
return boto3.Session(
aws_access_key_id=response['Credentials']['AccessKeyId'],
aws_secret_access_key=response['Credentials']['SecretAccessKey'],
aws_session_token=response['Credentials']['SessionToken'])
# This decides what role to use, a name of the session you will start, and potentially an external id.
# The external id can be used as a passcode to protect your role.
def set_boto3_clients(accountId):
return role_arn_to_session(
RoleArn='arn:aws:iam::' + accountId + ':role/TargetRole',
RoleSessionName='AssumeTargetRole',
ExternalId=os.environ['ExternalId']
)
def handler(event, context):
for accountId in accountList:
boto3InDifferentAccount = setup_client(accountId)
# You can then use this boto3InDifferentAccount as if you are using boto in another account
# For example: s3 = boto3InDifferentAccount.client('s3')
runScriptInAccount(boto3InDifferentAccount)
Note: when using this kind of role assumption you will need to configure how long you may want to assume the role. The default is 3600 secs, but you can edit this by changing the target role confiruagtion and the assumerole call parameters.

A 'clean' way to do this would be:
Create an IAM User in one account that will be used by the Python app
Create an IAM Role in each target account (with the same name), with permissions such that the role can be assumed by the IAM User
Load the list of the accounts into your app. Have it loop through each account, assume the role and access the account.

Related

Using my user account to create sns and cloudwatch resources in root account using terraform

I am trying to create sns billing alarm using cloudwatch when the cost reaches a particular threshold, I can do this manually but I'm trying to use terraform. Hello, I'm a NEWBIE to terraform, when I create this using terraform it's been created in the user account, I tried using the root Access keys but it continues to create them in my user account. Now, i'm not sure maybe i'm assuming wrong, when i create the billing alarm on the management console i do it using root account.
Here is my code:
provider "aws" {
shared_config_files = ["/mnt/c/Users/{user}/.aws/config"]
shared_credentials_files = ["/mnt/c/Users/{user}/.aws/credentials"]
profile = "root"
# region = "us-east-2"
}
module "sns_topic" {
source = "/mnt/c/terraform-ansible-automate/sns"
aws_account_id = var.aws_account_id
aws_env = var.aws_env
email = var.email
}
module "cloudwatch" {
source = "/mnt/c/terraform-ansible-automate/cloudwatch"
# source = "/cloudwatch"
monthly_billing_threshold = var.monthly_billing_threshold
sns_topic_arn = [module.sns_topic.sns_cost_alert_topic_arn]
aws_account_id = var.aws_account_id
aws_env = var.aws_env
}
Could you please verify in which account are you authenticated while running your terraform code?
Use the below command:
## It assumes that you have aws CLI pre-installed.
aws sts get-caller-identity
If you are already logged in the root account (desired account) it should work normally but if you are in any other account (user account in your case).
It could be because of the IAM user/role being used for the terraform authentication being in that account.
And then you have two choices in general.
Either assume a role in the desired account from the current signed/logged-in user. The role must have a trust relationship(policy) between your logged-in User. (your User should be allowed to assume that role from another account). The role being assumed must also have the required permissions(policies attached) on the desired account to make the required changes.
Please look into Hashicorp Documentation on provision-resource-across-aws-accounts
From the code perspective, you can refer to https://github.com/ishuar/stackoverflow-terraform/tree/main/aws/user_with_programmatic_access_assume_role/user_assuming_role_with_policies_attached as an example on How to Use IAM User to Assume Role with Required Access.
This is not a cross-account example but it is similar only need to adjust the role_arn in the provider block of instance_and_sg_creation
Use an IAM user that already exists in the desired account with having required permissions. Use the secrets for that user in your terraform authentication and make the changes. This is like any normal terraform code execution.

As a management account/delegated admin in a AWS Organization, how to call AWS services for each member account in AWS SDK?

Say I set up a AWS organization from account 111111111111, and then I create/invite 2 accounts, 222222222222 and 33333333333. As soon as I enabled SCP, I see a FullAWSAccess Policy attached to all members. I am trying to update each account programmatically in AWS SDK, and not having to switch roles on Console each time. For example:
AWSOrganizations client = AWSOrganizationsClientBuilder.standard().build();
ListAccountsResult result = client.listAccounts(new ListAccountsRequest().withMaxResults(10))
result.getAccounts()
.stream()
.forEach(account -> {
// I am not sure what to do with below data
// account.getArn()
// account.getId()
})
Say I want each member to put a s3 object like so:
s3.putObject(..)
Do I need to assume a role (AWS creates a OrganizationAccountAccessRole role by default) for each member account and call AWS service? Or am I missing something?
Your assumption is correct, in order to execute actions in other member accounts you need to assume a role in that account first. AWS Organizations creates OrganizationAccountAccessRole in each newly created account, this role has a trust policy to trust the master account. So as long as you're authenticated to the master account with any role that has sts:AssumeRole action you can assume OrganizationAccountAccessRole in the target account and do the "needfuls".
As the best practise you should your own automation role in each account and a dedicated automation account. This automation role lets say "pipeline-role" will have limited permissions that can be assumed only from your automation account.
This way you're reducing the need to utilise your master account and also making this automation role only as powerful as your automation needs instead of using the full AdministratorAccess policy.

Retrieve Role ARNs of sub accounts from an AWS lambda function running in the master account, in AWS organizations

I have a lambda function which assumes roles of sub accounts in AWS organizations. The lambda resides in the master account. I need to pass the Role ARN of the role to be assumed to this lambda. How can I achieve this? How can I retrieve role ARNs of roles residing in sub accounts from the master account using AWS Lambda ?
Is the role name different in every account? If the name of the role is the same, you can use essentially the same ARN and just insert the account ID into the string for whichever account is calling the Lambda function. I'm not sure if this answers your question, but if this is what you're trying to do your Lambda function code might look something like this (using Python and boto3):
def lambda_handler(event, context):
roleARN = 'arn:aws:iam::'+event['accountId']+':role/your_role_name'
sts_connection = boto3.client('sts')
# Assume the role in member accounts
acct_b = sts_connection.assume_role(
RoleArn=roleARN,
RoleSessionName="session_name"
)
Keep in mind that you will need to have the permissions set up correctly for both roles. The Lambda execution role needs to have permission to assume a role with that common name in any account, and the member account roles need to allow your Lambda execution role to assume them in the trust policy.

How to create recordset in Account2 from Account1

I am trying to create a route53 recordset from Account1 in Account2.
From reading other posts and online search I am thinking of doing something like this:
from boto3 import Session
session = Session(aws_access_key_id=*****,aws_secret_access_key=****,region_name='us-east-1')
r53_client = session.client('route53')
r53_resource = session.resource('route53')
Want to know from someone experienced if this is the right way to do this? Or is there a better way to achieve above?
Here is updated code:
def lambda_handler(event, context):
sts = boto3.client('sts')
response = sts.assume_role(
RoleArn='arn:aws:iam::***123:role/lambda',
RoleSessionName='my-random-session-name',
DurationSeconds= 900 # how many seconds these credentials will work
)
tempAccessKeyId = response['Credentials']['AccessKeyId']
tempSecretAccessKey = response['Credentials']['SecretAccessKey']
tempSessionToken = response['Credentials']['SessionToken']
client = boto3.client('route53',
region_name = 'us-west-2',
aws_access_key_id=tempAccessKeyId,
aws_secret_access_key=tempSecretAccessKey,
aws_session_token=tempSessionToken)
response = client.list_resource_record_sets(
HostedZoneId='***',
StartRecordName='test.example.com.',
StartRecordType='A'
)
print(response)
Based on the fact that you are doing this from an AWS Lambda function, the most secure way to do it would be:
In Account 1:
Create an IAM Role (Role 1) that will be used by the Lambda function
Assign permissions to the role that allows it to assume Role-2
Also assign any other permissions the Lambda function requires (you would normally add the AWSLambdaBasicExecutionRole managed policy to allow logging)
Assign Role 1 to the Lambda function
In Account 2:
Create an IAM Role (Role 2) with trust permissions that allows Role 1 in Account 1 to assume it
Grant Role 2 appropriate permissions to use Amazon Route 53
In your Lambda code, you would call AssumeRole() on Role 2. This will provide a set of temporary credentials that can be used to access Account 2 (as per your code, above).
See: Switching to an IAM Role (AWS API) - AWS Identity and Access Management
To make an API call to an AWS account, you either need credentials from that AWS account (eg credentials associated with an IAM User), or you need the ability to assume an IAM Role in that account.
So, in your example, if the credentials being provided belong to Account2, then you will be able to make API calls to Account2 (if that IAM User has been granted the necessary Route 53 permissions).
If you are frequently moving between accounts, you can instead specify a profile, which retrieves a different set of credential from the credentials file.
See: python - How to choose an AWS profile when using boto3 to connect to CloudFront - Stack Overflow

What is exactly "Assume" a role in AWS?

Question
What does exactly "Assume" a role mean in AWS and where is the definitive definition provided?
Background
Assuming a role is frequently used and trying to understand the definition and what it actually means.
I suppose when a principal (IAM user, application running in an EC2 instance, etc which invokes an action to access AWS resource(s)) needs to invoke an action to access an AWS resource:
AWS (API? or some Authorisation runtime in AWS?) identifies the roles which the principal can be granted. e.g. if an EC2 user is specified to execute the assume-role API call and run an application which accesses an AWS resources in an EC2 instance to which IAM profile is attached, then:
All the IAM roles from the EC2 IAM profile
IAM roles and policies requested in the assume-role call
IAM roles which the EC2 user is granted
AWS finds a role from the roles which has the policy (action, resource) that allows the principle to do the action on the resource.
AWS switches the role of the principle to the role identified.
When the step 3 has happened, it is said "the principal has assumed the role". Is this correct?
Research
Using IAM Roles
Before an IAM user, application, or service can use a role that you created, you must grant permissions to switch to the role. You can use any policy attached to one of an IAM user's groups or to the user itself to grant the necessary permissions.
Assuming a Role
AssumeRole
Using IAM Roles
Using an IAM Role to Grant Permissions to Applications Running on Amazon EC2 Instances
Assuming a role means asking Security Token Service (STS) to provide you with a set of temporary credentials -- role credentials -- that are specific to the role you want to assume. (Specifically, a new "session" with that role.)
You can optionally include a policy with this request, which will serve to limit the permissions of the temporary credentials to only a subset of what the role's policies would have allowed.
You then use these credentials to make further requests. These credentials look similar to IAM user credentials with an access-key-id and secret, but the access key begins with ASIA instead of AKIA and there's a third element, called the security token, which must be included in requests signed with the temporary credentials.
When you make requests with these temporary credentials, you have the permissions associated with the role, and not your own (if you have one) because you have taken on a new identity. CloudTrail can be used to trace the role credentials back to the user who assumed the role, but otherwise the service is unaware of who is using the credentials.
tl;dr: Assuming a role means obtaining a set of temporary credentials which are associated with the role and not with the entity that assumed the role.
AWS (API? or some Authorisation runtime in AWS?) identifies the roles which the principal can be granted.
No. You specify the role you want to assume.
When "you" are code running on an EC2 instance, and the instance has an instance role, the EC2 infrastructure actually calls assume-role on behalf of the instance, and you can fetch the temporary credentials from the instance metadata service. These credentials are accessible only from within the instance, but they are not stored on the instance.
When running a Lambda function, the Lambda infrastructure contacts STS and places your temporary credentials in environment variables. Again, these credentials are accessible to the function, without being stored inside the function.
In either case, you could call assume role with these credentials and assume a different role, but that should not be necessary in most environments.
e.g. if an EC2 user is specified to execute the assume-role API call and run an application which accesses an AWS resources in an EC2 instance to which IAM profile is attached, then:
AWS has no awareness of EC2 users. Instance roles are accessible to everything running on the instance.
All the IAM roles from the EC2 IAM profile
An instance profile can only include one role.
IAM roles and policies requested in the assume-role call
You request to assume exactly one role. You do not need to request a policy -- you only specify a policy if you want the temporary credentials to have fewer privileges than the role credentials would allow. This might be something you would do if you needed code running in an untrusted place -- such as code in a browser or an app -- to be able to sign requests with credentials.
AWS finds a role from the roles which has the policy (action, resource) that allows the principle to do the action on the resource.
No. As noted above, you ask for a specific role when you call assume-role.
AWS switches the role of the principle to the role identified.
No. You make the switch by using the temporary credentials provided.
I have created the following diagram for myself to understand what is exactly assume a role in AWS. Hopefully, you will also find it helpful.
In the diagram, I put it in 3 steps:
Prepare the roles (ExecutionRole and AssumedRole)
Create a Lambda Function on Account A (in your case it is EC2)
Execute the LambdaFunction.
The diagram uses cross-account as an example, if it is within the same account step 1.3 is not required.
Typically, you use AssumeRole within your account or for cross-account access.
...
Users in the same account as the role do not need explicit permission to assume the role. Source: https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
When step 3 has happened, it is said: "the principal has assumed the
role". Is this correct?
The steps you mentioned in assuming a role are correct.
Here the important point is the IAM role's Trust Relationship configuration where you grant each of the IAM user, application, or service to assume the role. That is where you grant the permission to assume the particular role.
This is important in many aspects, where it controls who can assume the role and it is important to provide not only least access to the role but also grant the least amount of entities who can assume the role.