Enhancing Deployment Security through the Integration of IAM Roles and GitHub Actions
Tags: webdev, aws, githubactions, security
Published: 2023-11-03 00:58:02

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.

```json
{
    "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.

```json
{
    "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.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iixpg3cmbvhn6coly459.png)

### 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.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/cw9ognqbujj70fai8lry.png)

### 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.

```yml
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:

```typescript
  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
        }
      })
    }

    return new S3Client(options)
  }
```

## 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.