Tip
Akeyless now has an action availiable! You can find it here https://github.com/akeyless-community/akeyless-github-action. It has many more authentication mechanisms. Note that I will continue to maintain this repo, at least until Akeyless fixing a bug with AAD dynamic secrets.
This action will login to AKeyless using JWT or IAM authentication and then fetch secrets and/or provision AWS access via a dynamic producer.
Workflow | Status |
---|---|
Static Secrets | |
Azure AD Dynamic Secrets | |
SQL Server Dynamic Secrets | |
GitHub Dynamic Secrets | |
Custom Producer Dynamic Secrets | |
AWS Dynamic Secrets |
Name | Required | Type | Value |
---|---|---|---|
access-id | Yes | string |
The access id for your auth method. |
access-type | No | string |
Default: jwt . The login method to use, must be jwt or aws_iam . |
api-url | No | string |
Default: https://api.akeyless.io . The API endpoint to use. |
producer-for-aws-access | No | string |
Path to an AWS dynamic producer. If provided, AWS credentials will be fetched from it and exported to the environment |
static-secrets | No | string |
A JSON object as a string, with a list of static secrets to fetch/export. The key should be the path to the secret and the value should be the name of the environment variable/output to save it to. |
dynamic-secrets | No | string |
A JSON object as a string, with a list of dynamic secrets to fetch/export. The key should be the path to the secret and the value should be the name of the environment variable/output to save it to. |
export-secrets-to-outputs | No | boolean |
Default: true . True/False to denote if static/dynamic secrets should be exported as environment variables. |
export-secrets-to-environment | No | boolean |
Default: true . True/False to denote if static/dynamic secrets should be exported as action outputs. |
parse-dynamic-secrets | No | boolean |
Default: false . True/False to denote if dynamic secrets will be broken up into individual outputs/env vars, see the parsed dynamic secrets demos. |
It is important that you follow the instructions in the AKeyless Setup and Job Permissions Requirement sections before using this Action.
The job outputs are determined by the values set in your static-secrets
and dynamic-secrets
inputs, as well as whether or not the export-secrets-to-outputs
is set to true (which it is by default).
The default behavior will create a single output/env variable that uses the name you set for the output.
Name | Value |
---|---|
outputs | use ${{ steps.JOB_NAME.outputs.SECRET_NAME }} |
environment variables | use ${{ env.SECRET_NAME }} |
If you enabled parse-dynamic-secrets: true
, you'll get each of the values in their own output/env variable automatically. For example, if your dynamic secret is { "id": "", "username": "", "password": "" }
the outputs will be:
Name | Value |
---|---|
job outputs | ${{ steps.job-name.outputs.id }} |
${{ steps.job-name.outputs.username }} |
|
${{ steps.job-name.outputs.password }} |
|
environment variables | ${{ env.id }} |
${{ env.username }} |
|
${{ env.password }} |
See the parsed dynamic secrets example for a better explanation.
The default usage relies on using the GitHub JWT to login to AKeyless. To make this available, you have to configure it in your job workflow:
jobs:
my_job:
#---------Required---------#
permissions:
id-token: write
contents: read
#--------------------------#
If this is not present, the akeyless-action step will fail with the following error
Failed to login to AKeyless: Error: Failed to fetch Github JWT: Error message: Unable to get ACTIONS\_ID\_TOKEN\_REQUEST\_URL env variable
Although this repository's workflows use placeholder values, it is still a real AKeyless account and real providers. The approaches demonstrated are still valid as-is for real implementations. Use these to your advantage!
- Static Secrets
- Dynamic Secrets (AWS provider)
- Dynamic Secrets (Database provider)
Static secrets are the easiest to use. Just define the secret's path and the secret's output.
jobs:
fetch_secrets:
runs-on: ubuntu-latest
permissions: # IMPORTANT - both of these are required
id-token: write
contents: read
name: Fetch some static secrets
steps:
- name: Fetch secrets from AKeyless
id: fetch-secrets
uses: LanceMcCarthy/akeyless-action@v4
with:
access-id: auth-method-access-id # (ex: 'p-iwt13fd19ajd') We recommend storing this as a GitHub Actions secret
static-secrets: '{"/path/to/static/secret":"my_first_secret","/path/to/another/secret":"my_second_secret"}'
- name: Use Outputs
run: |
echo "Step Outputs"
echo "my_first_secret: ${{ steps.fetch-secrets.outputs.my_first_secret }}"
echo "my_second_secret: ${{ steps.fetch-secrets.outputs.my_second_secret }}"
echo "my_dynamic_secret: ${{ steps.fetch-secrets.outputs.my_dynamic_secret }}"
echo "Environment Variables"
echo "my_first_secret: ${{ env.my_first_secret }}"
echo "my_second_secret: ${{ env.my_second_secret }}"
echo "my_dynamic_secret: ${{ env.my_dynamic_secret }}"
The key difference with dynamic secrets is the output value is typically a JSON object. there are two ways you can handle this; default output or parsed outputs
If you want those secrets as separate environment variables, there's one extra step to take. See the KEY TAKEAWAY
section in the following example.
fetch_aws_dynamic_secrets:
runs-on: ubuntu-latest
name: Fetch AWS dynamic secrets
permissions:
id-token: write
contents: read
steps:
- name: Fetch dynamic secrets from AKeyless
id: fetch-dynamic-secrets
uses: LanceMcCarthy/akeyless-action@v4
with:
access-id: ${{ secrets.AKEYLESS_ACCESS_ID }} # Looks like p-fq3afjjxv839
dynamic-secrets: '{"/path/to/dynamic/aws/secret":"aws_dynamic_secrets"}'
# ********* KEY TAKEAWAY ********* #
# STEP 1 - EXPORT DYNAMIC SECRET's KEYS TO ENV VARS
- name: Export Secrets to Environment
run: |
echo '${{ steps.fetch-dynamic-secrets.outputs.aws_dynamic_secrets }}' | jq -r 'to_entries|map("AWS_\(.key|ascii_upcase)=\(.value|tostring)")|.[]' >> $GITHUB_ENV
# STEP 2 - You can now access each secret separately as environment variables
- name: Verify Vars
run: |
echo "access_key_id: ${{ env.AWS_ACCESS_KEY_ID }}"
echo "id: ${{ env.AWS_ID }}"
echo "secret_access_key: ${{ env.AWS_SECRET_ACCESS_KEY }}"
echo "security_token: ${{ env.AWS_SECURITY_TOKEN }}"
echo "ttl_in_minutes: ${{ env.AWS_TTL_IN_MINUTES }}"
echo "type: ${{ env.AWS_TYPE }}"
echo "user: ${{ env.AWS_USER }}"
If you set parse-dynamic-secrets: true
, the job will automatically create a a separate output for every key in the dynamic secret. This is extremely useful if you do not want to manually parse it, or if you need to immediately use an output's value in a subsequent step.
For example, a SQL server dynamic secret will provide id, user, ttl_in_minutes and password values.
- name: Fetch dynamic secrets from AKeyless (NO PREFIX)
id: get-secrets
uses: LanceMcCarthy/akeyless-action@v4
with:
access-id: ${{ secrets.AKEYLESS_ACCESS_ID }}
dynamic-secrets: '{"/DevTools/my-sqlsrv-secret":""}' # no prefix, use an empty string for output var
parse-dynamic-secrets: true
Then the outputs/vars will be directly generated for each key: id
, user
, ttl_in_minutes
, and password
values.
Step Outputs:
echo ${{ steps.get-secrets.outputs.id }}
echo ${{ steps.get-secrets.outputs.user }}
echo ${{ steps.get-secrets.outputs.ttl_in_minutes }}
echo ${{ steps.get-secrets.outputs.password }}
Environment Variables:
echo ${{ env.id }}
echo ${{ env.user }}
echo ${{ env.ttl_in_minutes }}
echo ${{ env.password }}
Sometimes you might want to prefix the variable name. This is easily done by setting an output name, that value will be used to prefix all the output keys.
For example, using "SQL" for the output path:
- name: Fetch dynamic secrets from AKeyless ('SQL' prefix)
uses: LanceMcCarthy/akeyless-action@v4
id: job-name
with:
access-id: ${{ secrets.AKEYLESS_ACCESS_ID }}
dynamic-secrets: '{"/DevTools/my-sqlsrv-secret":"SQL"}' # uses 'SQL' for prefix
parse-dynamic-secrets: true
The action will prefix SQL_
prefix to all the automatically parsed outputs:
# notice the extra "SQL_" prefix
echo ${{ env.SQL_user }}
echo ${{ steps.job-name.outputs.SQL_user }}
This action only supports authenticating to AKeyless via JWT auth (using the GitHub OIDC token) or via IAM Auth (using a role attached to a cloud-hosted GitHub runner). I don't plan to support additional authentication methods because there isn't much point (with the possible exception of Universal Identity). After all, any runner can login to AKeyless using OIDC without storing permanent access credentials. IAM auth is also supported in case you are using a runner hosted in your cloud account and so are already using IAM auth anyway - this will also give your runner access to AKeyless without storing permanent access credentials.
To configure AKeyless and grant your repositories the necessary permissions to execute this action:
- Create a GitHub JWT Auth method in AKeyless if you don't have one (you can safely share the auth method between repositories)
- In AKeyless go to "Auth Methods" -> "+ New" -> "OAuth 2.0/JWT".
- Specify a name (e.g. "GitHub JWT Auth") and location of your choice.
- For the JWKS Url, specify
https://token.actions.githubusercontent.com/.well-known/jwks
- For the unique identifier use
repository
. See note (1) below for more details. - You MUST click "Require Sub Claim on role association". This will prevent you from attaching this to a role without any additional checks. If you accidentally forgot to set subclaim checks, then any GitHub runner owned by anyone would be able to authenticate to AKeyless and access your resources... that make this a critical checkbox. See the GitHub docs for more details.
- Create an appropriate access role (if you don't already have one)
- In AKeyless go to "Access Roles" -> "+ New"
- Give it a name and location, and create it.
- Find your new access role and click on it to edit it.
- On the right side, under "Secrets & Keys", click the "Add" button to configure read access to any static or dynamic secrets you will fetch from your pipeline.
- Attach your GitHub JWT Auth method to your role
- Once again, find the access role you created in step #2 above and click on it to edit it.
- Hit the "+ Associate" button to associate your "GitHub JWT Auth" method with the role.
- In the list, find the auth method you created in Step #1 above.
- Add an appropriate sub claim, based on the claims available in the JWT. See note (2) below for more details.
- Save!
After following these steps, you'll be ready to use JWT Auth from your GitHub runners!
(1) Note: The unique identifier is mainly used for auditing/billing purposes, so there isn't one correct answer here. repository
is a sensible default but if you are uncertain, talk to AKeyless for more details.
(2) Note: Subclaim checks allow AKeyless to grant access to specific workflows, based on the claims that GitHub provides in the JWT. Using the example JWT from the documentation, you could set a subclaim check in AKeyless (using example below) to limit access to workflows that were triggered from the main branch in the octo-org/octo-repo
repository.:
repository=octo-org/octo-repo
ref=refs/heads/main
Although forked from cmancone/akeyless-action, this repo is primary source of feature development and maintenance for the akeyless-action
published to the GitHub Marketplace.
If you have any problems or would like to see a new feature, please open Feature Requests and report Issues here instead of the upstream repo. Thank you!
If you're coming form an older version of this action before it was published in the GitHub Marketplace (1.0 and earlier), you may notice a difference in behavior. I will attempt to briefly mention what you need to be aware of in this table.
Version Range | Comment |
---|---|
1.0-1.1 | Original implementation from cmancone/akeyless-action |
1.1-2.x | Introduction of masking sensitive values in output. |
3.0 | Introduction of parsed outputs, no breaking changes if the defaults are not changed. |