How can I extract secrets using GitHub Actions?

69.3k Views Asked by At

I have a fairly basic scenario. I made a dedicated ssh key for this purpose and added it to my repository secrets.

  1. Code gets pushed to master

  2. GitHub action uploads it to server using ssh by doing echo "${{ secrets.SSH_KEY }}" > key.

  3. After that I can use this key to connect to my server e.g. ssh -i key [email protected] lsb_release -a

The problem is that for some reason GitHub actions cannot write it to file, it writes characters *** instead of the actual secret value into the file. Therefore obviously I cannot connect to my server.

How can I connect with ssh using this secret? Is there a way to connect without using a file? Can someone who did this common scenario using GitHub actions shed some light?

9

There are 9 best solutions below

2
On BEST ANSWER

The good solution is to use gpg for encrypting the key, adding it to a repo and decrypting it on the server using passphrase. The passprase should be stored as github project secret of course.

More info how I did it here: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets

0
On

Here is how to solve your actual problem of securely logging into an SSH server using a secret stored in GitHub Actions, named GITHUB_ACTIONS_DEPLOY.

Let's call this "beep", because it will cause an audible bell on the server you login to. Maybe you use this literally ping a server in your house when somebody pushes code to your repo.

      - name: Beep    
        # if: github.ref == 'refs/heads/XXXX' # Maybe limit only this step to some branches
        run: |
          eval $(ssh-agent)
          ssh-add - <<< "$SSH_KEY"
          echo "* ssh-rsa XXX" >> /tmp/known_hosts # Get from your local ~/.ssh/known_hosts or from ssh-keyscan
          ssh -o UserKnownHostsFile=/tmp/known_hosts [email protected] "echo '\a'"
        env:
          SSH_KEY: ${{ secrets.PMT_GITHUB_ACTIONS_DEPLOY }}

If, actually you are using SSH as part of a rsync push task, here is how to do that:

      - name: Publish    
        if: github.ref == 'refs/heads/XXX'
        run: |
          eval $(ssh-agent)
          ssh-add - <<< "$SSH_KEY"
          echo "* ssh-rsa XXX" >> /tmp/known_hosts
          rsync $FROM user@server:
        env:
          SSH_KEY: ${{ secrets.GITHUB_ACTIONS_DEPLOY }}
          RSYNC_RSH: "ssh -o UserKnownHostsFile=/tmp/known_hosts"
0
On
echo ${{secrets.AWS_ACCESS_KEY_ID}} | sed 's/./& /g'
0
On

I used sed in a GHA to replace a TOKEN in a file like this :

run: |-
     sed -i "s/TOKEN/${{secrets.MY_SECRET}}/g" "thefile"

The file looks like this:

    credentials "app.terraform.io" {
       token = "TOKEN"
    }
1
On

You can decode a secret by looping through it with python shell, like this:

    - name: Set env as secret
      env:
        MY_VAL: ${{ secrets.SUPER_SECRET }}
      run: |
        import os
        data = open("file", "w")
        for q in (os.getenv("MY_VAL")):
          print(q)
          data.write(q)
      shell: python

This will both print each character to stdout and store them in file called file. stdout will have an output like this, while file should have the secret string saved inside.

s
e
c
r
e
t

It runs daily on my repo, to check if it's still working: here

3
On

GitHub Actions should be able to write a secret to a file this way. The reason you see the stars is that the log is filtered, so if a secret would be logged, it's replaced in the log with three asterisks instead. This is a security measure against an accidental disclosure of secrets, since logs are often publicly available.

However, it's a good idea to avoid writing the secret to the log anyway if possible. You can write your command like this so you don't write the secret to the log:

    - run: 'echo "$SSH_KEY" > key'
      shell: bash
      env:
        SSH_KEY: ${{secrets.SSH_KEY}}

All you'll see in the log is echo "$SSH_KEY" > key, not the secret or any asterisks.

Note that you do want quotes here, since the > character is special to YAML.

If this doesn't work to log into your server, there's likely a different issue; this technique does work for writing secrets in the general case.

0
On

I found a way to do this. We used GatsbyJS for a project and it relies on a .env.production file for the env variables. I tried to pass them as env: to the github action, but that didn't work and they were ignored.

Here is what I did. I base 64 encoded the .env.production file:

base64 -i .env.production

Added the output to an env variable in github action. Then in my action I do:

echo ${{ secrets.ENV_PRODUCTION_FILE }} | base64 -d > .env.production

This way the contents of my .env.production file ended being written to the machine that executes the github action.

0
On

encode it and decode back

- run: 'echo "$SSH_KEY" | base64'
       shell: bash
       env:
         SSH_KEY: ${{ secrets.PRIVATE_KEY }}

and decode it back echo "<encoded string>" | base64 -d

0
On

This is whole actions config yaml that I just used. It's a combination of answers above, just for convenience put in one config.

name: Production build
on:
  push:
    branches:
      - prod
jobs:
  extractor_job:
    runs-on: "self-hosted"
    steps:
      - run: 'echo "$DEPLOYER_SSH_KEY" | base64'
        shell: bash
        env:
          DEPLOYER_SSH_KEY: ${{secrets.DEPLOYER_SSH_KEY}}
      - run: 'echo "$DEPLOYER_SSH_PASSPHRASE" | base64'
        shell: bash
        env:
          DEPLOYER_SSH_PASSPHRASE: ${{secrets.DEPLOYER_SSH_PASSPHRASE}}
      - run: 'echo "$DEPLOYER_SSH_USERNAME" | base64'
        shell: bash
        env:
          DEPLOYER_SSH_USERNAME: ${{secrets.DEPLOYER_SSH_USERNAME}}

It encodes variables to base64 and which values can be extracted from "raw log" Access raw log

For safe decode back to normal values I use Dev Toys from Microsoft.