Mask private SSH key in GitLab CI/CD

by

in

When using Ansible for automating deployment from a GitLab CI/CD pipeline, you must provide a private SSH key via a GitLab CI/CD variable.

GitLab CI/CD variables can be setup to be masked which prevents them from appearing in log files. Furthermore, they can be configured to be hidden so you have no way of accessing its value manually.

Masked in job logs, and can never be revealed in the CI/CD settings after the variable is saved.

This makes it perfect for storing secrets like the aforementioned private SSH key.

However, the functionality comes with a flaw, as it is not possible to use it, when the secret value contains forbidden characters (e.g., blank spaces).

Private SSH keys start and end with a comment that contains blank spaces and must not be altered or deleted as this would render the key useless.

-----BEGIN OPENSSH PRIVATE KEY-----
...
-----END OPENSSH PRIVATE KEY-----

To mitigate the issue, you can encode the key with Base64. First create a new key pair and save it wherever you like. We will not keep the keys on our local system.

$ ssh-keygen -t ed25519 -b 4096 -C "GitLab CI/CD"

Don’t provide a passphrase when asked.

For the sake of this article, we’ll assume the generated files will reside in ~/gitlab/id_ed25519 and ~/gitlab/id_ed25519.pub.

Now to encode the private key you can use the following command.

$ base64 -w0 -i ~/gitlab/id_ed25519

base64 uses -i to specify the input file. In our case, the private SSH key. But what does -w0 signify?

By default, the base64 command wraps lines at 76 characters when encoding data. The -w0 option is used to output the encoded result in a single line without any line breaks.

Copy the output of the command and add it to a new masked and hidden CI/CD variable named SSH_PRIVATE_KEY in your GitLab project.

Let’s have a look at a simple pipeline script on how to use the private SSH key.

# Excerpt
  before_script:
    # Make sure that the image used provides an ssh-agent, install one, otherwise.
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval $(ssh-agent -s)
    # Read the private SSH key from the variable, decode it, remove line breaks,
    # and add it to the SSH agent.
    - echo "$SSH_PRIVATE_KEY" | base64 -d | tr -d '\r' | ssh-add - > /dev/null
  script:
    - # Ansible can now use the provided private SSH key

Provided, that you added the public key to the ~/.ssh/authorized_keys file on the target system(s), there is only one thing left to do.

Gather the SSH public keys from the target system(s) and make them available to your scripts known hosts.

$ ssh-keyscan example.com

Replace example.com with the actual fully qualified domain name or the IP address of your target system and add the output to a new GitLab CI/CD variable called SSH_KNOWN_HOSTS.

Revisiting our script from earlier, we can now add the final piece of the puzzle.

# Excerpt
  before_script:
    # Make sure that the image used provides an ssh-agent, install one, otherwise.
    - 'which ssh-agent || ( apt-get update -y && apt-get install openssh-client -y )'
    - eval $(ssh-agent -s)
    # Read the private SSH key from the variable, decode it, remove line breaks,
    # and add it to the SSH agent.
    - echo "$SSH_PRIVATE_KEY" | base64 -d | tr -d '\r' | ssh-add - > /dev/null
    # Make sure the target system is known
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
    - chmod 644 ~/.ssh/known_hosts
  script:
    - # Ansible can now use the provided private SSH key

Happy deploying!

Further reading:


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *