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:
- https://www.programonaut.com/how-to-mask-an-ssh-private-key-in-gitlab-ci/
- https://about.gitlab.com/blog/2018/08/02/using-the-gitlab-ci-slash-cd-for-smart-home-configuration-management/#preparing-the-server-and-gitlab-for-ssh-access
- https://docs.gitlab.com/ee/ci/variables/index.html#mask-a-cicd-variable
- https://stackoverflow.com/questions/64699458/storing-ssh-private-key-in-gitlab-repository-variables
Leave a Reply