mtnr

A tech blog with fries on the side

Tag: ed25519

  • Mask private SSH key in GitLab CI/CD

    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:

  • Upgrading from RSA to ED25519

    Just out of curiosity, ascertain what keys you have on your machine by issuing the following command:

    for key in ~/.ssh/id_*; do ssh-keygen -l -f "${key}"; done | uniq

    Generate new ED25519 key pair

    ssh-keygen -o -a 256 -t ed25519 -C "$(hostname)-$(date +'%d-%m-%Y')"

    Executing the command above will generate a new pair of Ed25519 keys. When asked, provide a strong password for the key pair.

    $ ~/.ssh/id_ed25519     #Private key
    $ ~/.ssh/id_ed25519.pub #Public key

    Let’s have a brief look at each option.

    -o will use OpenSSH format for the new keys
    -a specifies the number (amount) of key derivation rounds (KDF)
    -t specifies the type; in this case Ed25519
    -C adds an optional comment that helps with identifying the key

    Using the new keys

    Now, simply add the public key to the authorized keys of the machine you would like to login to. In order to retrieve the public key, use the following command and copy & paste the output of said command.

    cat ~/.ssh/id_ed25519.pub

    Sprinkle a bit of convenience on top

    Now if you’re like me and are using a Mac, you may use the Keychain to store your password, so you don’t have to always type it out when logging in to your server via ssh.

    I added the following to ~/.ssh/config:

    Host mtnr
        HostName mtnr.cloud
        UseKeychain yes
        IdentityFile ~/.ssh/id_ed25519

    Now, when calling ssh mtnr, I can ssh into my server without specifying anything extra like e.g. which pair of keys to use for authentication and, I only have to type out the password once. All subsequent attempts will use the password stored in my Keychain.

    Neat!

    Further reading/sources: