In this blog post, I’ll share how to recover a secret from a CI/CD service, such as GitHub Actions.
If you’re here, then you already know that secrets are hidden from CI/CD logs with ***
, for example:
jobs:
openssl:
name: Recover With OpenSSL
runs-on: ubuntu-20.04
steps:
- env:
MY_CLIENT_SECRET: ${{ secrets.MY_CLIENT_SECRET }}
run: |
echo "MY_CLIENT_SECRET (***) = ${MY_CLIENT_SECRET}"
# Output
MY_CLIENT_SECRET (***) = ***
The above isn’t very helpful since this is the situation you’re probably in right now.
For private repositories, it’s possible to use base64 to encode a secret before printing it to the CI/CD service logs; this way, GitHub Actions won’t hide the secret with ***
. Then, copy the encoded value and decode it locally.
name: Recovering secrets
# Assumption:
# You've created the following GitHub secrets in your repository:
# MY_CLIENT_ID - encode/decode with base64 - useful for private repositories
on:
push:
workflow_dispatch:
jobs:
base64:
name: Recover With Base64
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- env:
MY_CLIENT_ID: ${{ secrets.MY_CLIENT_ID }}
run: |
echo "MY_CLIENT_ID (***) = ${MY_CLIENT_ID}"
echo "MY_CLIENT_ID (base64) = $(echo ${MY_CLIENT_ID} | base64)"
echo "Copy the above value, and then execute locally:"
echo "echo PASTE_HERE | base64 -D"
The above method is hazardous as anyone can decode the secret, so for public repositories, this is a no-go. And here’s proof why it is super dangerous, assuming the printed encoded value is c29tZS1jbGllbnQtaWQtdmFsdWUK
…
echo c29tZS1jbGllbnQtaWQtdmFsdWUK | base64 -D
# some-client-id-value
I’ve just exposed MY_CLIENT_ID
to the whole world … I’m terrified.
The best way to recover a secret from a CICD system without exposing it to the outside world is to encrypt the secret before printing it to the CI/CD logs.
name: Recovering secrets
# Assumption:
# You've created the following GitHub secrets in your repository:
# MY_CLIENT_SECRET - encrypt/decrypt with openssl - useful for public and public repositories
# MY_OPENSSL_PASSWORD - used to protect secrets
# MY_OPENSSL_ITER - Use a number of iterations on the password to derive the encryption key.
# High values increase the time required to brute-force the resulting file.
# This option enables the use of PBKDF2 algorithm to derive the key.
on:
push:
workflow_dispatch:
jobs:
openssl:
name: Recover With OpenSSL
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- env:
MY_CLIENT_SECRET: ${{ secrets.MY_CLIENT_SECRET }}
MY_OPENSSL_PASSWORD: ${{ secrets.MY_OPENSSL_PASSWORD }}
MY_OPENSSL_ITER: ${{ secrets.MY_OPENSSL_ITER }}
run: |
echo "MY_CLIENT_SECRET (***) = ${MY_CLIENT_SECRET}"
echo "MY_CLIENT_SECRET (openssl) = $(echo "${MY_CLIENT_SECRET}" | openssl enc -e -aes-256-cbc -a -pbkdf2 -iter ${MY_OPENSSL_ITER} -k "${MY_OPENSSL_PASSWORD}")"
echo "Copy the above value, and then execute locally:"
echo "echo PASTE_HERE | openssl base64 -d | openssl enc -d -pbkdf2 -iter \$MY_OPENSSL_ITER -aes-256-cbc -k \$MY_OPENSSL_PASSWORD"
The only way to decrypt the above string U2FsdGVkX1+6/+7bvNG/Ga7siAI994FkMUn5Njzn4zyNwvf8qM3MY0MMmd9sCFvz
is to use the right number of iter
and password
, otherwise you’ll have to use a brute-force attack, good luck with that :)
Here’s how I decrypted the above value on my local machine:
echo U2FsdGVkX1+CeN0/ScQLZGU8f0ix86fh1oLJg/1M+o2lbCM+pBA8BIUCbkHMCjRZ | openssl base64 -d | openssl enc -d -pbkdf2 -iter $MY_OPENSSL_ITER -aes-256-cbc -k $MY_OPENSSL_PASSWORD
I suggest creating a separate workflow for recovering CI/CD (GitHub) secrets, like .github/workflows/recover-github-secrets.yml
, followed by running the workflow and then deleting its logs once you’re done recovering the secret.