Until now, I faced challenges regarding the management of credentials (IAM user's access key and secret access key) when deploying the AWS SDK to Lambda.
Using the method of reading the access key of an IAM user with strong resource permissions from environment variables posed a significant security risk in case of key leakage. As a result, I shifted to using IAM roles.
In this article, I'll outline the steps I took for this transition, serving as a reference for future reference.
Procedure
In the GitHub Actions deployment workflow, I loaded the access key and secret access key of an IAM user with AssumeRole permissions from GitHub Secrets. This allowed me to assume the IAM role's permissions through AssumeRole.
Creation of IAM User
Using the console or IaC tools, create an IAM user and associate the following custom policy. For the Resource
field, copy and paste the ARN after creating the IAM role.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"sts:AssumeRole",
"sts:TagSession"
],
"Resource": "arn:aws:iam::<Account ID>:role/<Role Name>"
}
]
}
Not just sts:AssumeRole
, but sts:TagSession
is also essential.
Next, navigate to 'Security Credentials > Access keys' to issue an access key (and secret access key).
This access key is necessary when assuming the IAM role's permissions in the GitHub Actions deployment workflow.
This IAM user does not have permissions to operate resources. Even if the key leaks to a third party, they won't be able to perform significant resource operations. Thus, the security risk is low. Nevertheless, precautions (like key rotation) are essential to prevent leakage.
Creating an IAM Role
Similar to creating an IAM user, you can create an IAM role using the console or IaC tools.
IAM Role for Deployment Workflow
This policy grants permissions necessary for deployment operations such as CloudFormation, API Gateway, Lambda, ECR, IAM, and others.
Next, navigate to the 'Trust relationships' tab of the IAM role and select 'Edit trust policy'. The trust policy defines which entities can assume the role.
For the Principal
's AWS
, paste the ARN of the IAM user you created earlier.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::<Account ID>:user/<User Name>"
},
"Action": [
"sts:AssumeRole",
"sts:TagSession"
],
"Condition": {}
}
]
}
IAM Role for Lambda
This policy grants permissions necessary for application operations like S3, SES, and others.
From the Lambda details screen, under the 'Settings tab > Permissions', associate the execution role you created as the IAM role. This eliminates the need to load credentials in your application code.
Registering Environment Variables in GitHub Secrets
From the GitHub repository's Settings tab, select 'Secrets and variables > Actions' and add the environment variables (AWS_ACCESS_KEY_ID
, AWS_SECRET_ACCESS_KEY
, AWS_IAM_ROLE_TO_ASSUME
) for the workflow using 'New repository secret'.
For AWS_IAM_ROLE_TO_ASSUME
, enter the ARN of the IAM role.
Modifying the GitHub Actions Deployment Workflow
Below is a part of the deployment workflow. In the configure aws
step, it loads the environment variables you set earlier.
name: Lambda Deploy
on:
push:
branches:
- develop
paths:
- "**"
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- name: install dependencies
run: yarn install
working-directory: ./
- name: configure aws
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
role-to-assume: ${{ secrets.AWS_IAM_ROLE_TO_ASSUME }}
role-duration-seconds: 3600
Ensure the role-duration-seconds: 3600
value is set to less than or equal to the IAM role's maximum session duration.
Development in the Local Environment
After deploying to Lambda, you can access AWS resources using the Lambda execution role. However, in a local environment (like Docker), since there's no execution role, you'll need to load credentials from environment variables as before.
Thus, you'll need to modify your application code as follows:
private createS3Client(): S3Client {
const options: S3ClientConfig = {
region: 'ap-northeast-1',
...(process.env.ENV === 'local' && {
credentials: {
accessKeyId: process.env.ACCESS_KEY_ID as string,
secretAccessKey: process.env.SECRET_ACCESS_KEY as string
}
})
}
<span class="k">return</span> <span class="k">new</span> <span class="nc">S3Client</span><span class="p">(</span><span class="nx">options</span><span class="p">)</span>
}
Conclusion
The process is largely similar when deploying to ECS. By specifying the task role in the ECS task definition, containers running on EC2 instances or Fargate executing the ECS task can access AWS resources. This task role can be customized by attaching an IAM policy that defines access permissions to specific AWS services or resources.
Top comments (0)