AWS Secrets manager is a widely used managed service to store confidential secrets/environment variables such as API keys, database credentials, etc. In this blog we will see how to leverage the secrets from Secret Manager into Github actions pipeline and use them securely as environment variables for deployments.
This can be achieved with minimal exposure to sensitive data into the pipeline. Sensitive credentials like aws access key
and aws secret access key
are often stored into a an external .env
file or github secrets
and then pulled into the CI which maybe exposed with unauthorized access if not handled properly.
We can eliminate this risk by running a self hosted Github actions runner and IAM role which will allow the authentication of your AWS account inside the CI without the use of AWS credentials. Let us now see how we can achieve this.
- A self hosted github runner on an AWS EC2
- Create and store confidential key/values as a secret in the AWS secrets manager
- Create an IAM policy which provides read-only access to AWS Secret Manager and apply this policy to an IAM Role
- Attach the IAM role to the EC2 server to enable read access to the secrets created
- Setup an actions.yaml file in github and pull these secrets into the CI and print out the values on the console as proof of concept for the solution
Create A Self-Hosted Github Runner On An Aws EC2
Follow the steps and setup the runner on your EC2 server
Create A Secret In Aws Secret Manager
- Go to Secrets Manager Console, and click on
Store a new secret
- Select
Other
as secret type - Enter the confidential values in form of key/value pairs (which can later be converted to .env file format if needed)
- Give your secret a name, for eg:
apicreds
and store it
For this example I have created a secret apicreds
with the following key:value
respectively.
url: api.explample.com
key: 123456
secret: mycomplexsecret
Note: For this example we are relying on your secrets that are encrypted using the default AWS managed encryption key. If your secrets are encrypted using a customer managed AWS Key Management Service (KMS) key, then the IAM policy (below) will be different.
Create An IAM Role And Attach A Policy To It
For this example, we will create role SecretManagerReadOnlyEC2Role
and a policy secrets-manager-read-only-policy
with the following configuration with least privilege best practice which gives read-only access to aws secrets
{
"Version": "2012-10-17",
"Statement": [
{
"Action": [
"secretsmanager:GetSecretValue",
"sts:AssumeRole",
"sts:TagSession"
],
"Effect": "Allow",
"Resource": "*"
}
]
}
Note: If you get an error The requested DurationSeconds exceeds the MaxSessionDuration set for this role
while running the CI, try setting the session duration to max 43200 seconds using the instructions here
Attach This Role To The EC2 instance to allow access to secrets from the Secrets Manager into the EC2 instance
In the EC2 instances console, go to the instance security configuration, under security details attach the role created in the previous step.
Setting Up Github Actions
Setup github actions.yaml
to fetch secrets from AWS Secret Manager and display them on console as proof of concept for the solution.
name: Self-hosted runner test for secrets injection
on:
push:
workflow_dispatch:
jobs:
build:
runs-on: self-hosted
steps:
- uses: actions/checkout@v2
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v1
with:
role-to-assume: arn:aws:iam::xxxxxxxxxxxx:role/SecretManagerReadOnlyEC2Role
aws-region: ap-south-1
role-session-name: github-runner-session-1
- name: Read secrets from AWS Secrets Manager into environment variables
uses: abhilash1in/aws-secrets-manager-action@v2.1.0
with:
secrets: apicreds
parse-json: true
#Optional Steps
- name: Check if env variable is set after fetching secrets
run: |
if [ -z ${APICREDS_URL+x} ]; then echo "APICREDS_URL is unset"; else echo "APICREDS_URL is set to '$APICREDS_URL'"; fi
if [ -z ${APICREDS_KEY+x} ]; then echo "APICREDS_KEY is unset"; else echo "APICREDS_KEY is set to '$APICREDS_KEY'"; fi
if [ -z ${APICREDS_SECRET+x} ]; then echo "APICREDS_SECRET is unset"; else echo "APICREDS_SECRET is set to '$APICREDS_SECRET'"; fi
If your secret name contains any characters other than upper case letters, digits and underscores, it will not be used directly as the environment variable name. Rather, it will be transformed into a string that only contains upper case letters, digits and underscores.
For example:
- If your secret name is dev.foo, the injected environment variable name will be DEV_FOO.
- If your secret name is 1/dev/foo, the injected environment variable name will be _1_DEV_FOO.
- If your secret name is dev/foo, value is
{ "bar": "baz" }
and parse-json is set to true, the injected environment variable name will be DEV_FOO_BAR (and value will be baz).
Optional step: To convert the env vars into project specific env configuration, create an env.txt file and source the file into the CI.
export URL = $APICREDS_URL
export KEY = $APICREDS_KEY
export SECRET = $APICREDS_SECRET
- name: Fetch project specific env config from env.txt
run: |
source env.txt
echo $URL
echo $KEY
echo $SECRET
For more info on how env vars are generated inside the CI
Conclusion
We have now injected secrets from AWS secrets manager into the self-hosted github CI without using aws access key
or aws secret access key
instead by using only IAM role. We can now use these environment variables in subsequent steps of your workflow securely. By using the IAM role and policy mechanism we have eliminated the potential risk factor of storing access keys for AWS authentication in the pipeline.