69

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 devops@myserver.com 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?

Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
Stan
  • 25,744
  • 53
  • 164
  • 242
  • I think this is more to do with github stopping the logging of secrets, rather than it not actually being there. – BenKoshy Aug 03 '21 at 00:25

8 Answers8

97

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.

bk2204
  • 64,793
  • 6
  • 84
  • 100
  • 8
    I've struggled for a while and noticed that I'm missing the double quotes. `echo "$SSH_KEY"`. It was important to escape multiline characters. – Shinebayar G Sep 05 '20 at 21:00
  • 4
    I can't verify that. I updated the `run` to be `'echo "$SSH_KEY" > key && tail key'` and it still shows `***`. How can the action runner know the file should be still encrypted? – Dida Oct 13 '20 at 03:05
  • @Dida The action runner knows all secrets. In case the secrets should be output, it replaces it by stars. What it cannot do is detecting variants of the secret. Thus, a ROT13 helps to output the secret for debugging or emergency recovery. – koppor Feb 28 '21 at 12:49
18

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.

Daniel Dimitrov
  • 1,848
  • 21
  • 35
3

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

GLM
  • 59
  • 2
3

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

Moro
  • 781
  • 4
  • 14
1

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"
    }
0

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 user@example.com "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"
William Entriken
  • 37,208
  • 23
  • 149
  • 195
-1

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

Stan
  • 25,744
  • 53
  • 164
  • 242
  • 2
    like mentioned on the linked page this is just intended as a workaround for large secrets and does not protect you from exposing secrets in logs. IMHO @bk2204 should be the accepted answer. – Stefan Haberl Feb 12 '20 at 12:33
  • If you do it like this, your secret isn't protected from being printed into logs. This is not "the good solution", this is a somewhat dangerous workaround if your secret happens to be larger than 64kb. – René Roth Aug 01 '22 at 13:05
-1
echo ${{secrets.AWS_ACCESS_KEY_ID}} | sed 's/./& /g'
General Grievance
  • 4,555
  • 31
  • 31
  • 45
Gaurav
  • 19
  • 2