Tagging in Gitlab CI Pipeline using Deploy Keys

Often when you are compiling your sourcecode in Gitlab you will want to push some changes back to the repository, for instance a created commit or tag . By default Gitlab does not allow the gitlab runner to write back to the repository. Adding that behaviour has been on the backlog of Gitlab for a long time. But there exists a feature called Deploy Keys in gitlab which we can leverage to fix this 🎉

This process involves some shuffling with sensitive files so be mindful where you store these. We are going to generate a private SSH key (+public key), upload that pair into gitlab and use it from within our pipeline to have a trust between the cicd pipeline to be able to push code.

One time setup: generate SSH key

We assume that the following directory exists and is empty: ~/development/gitlab-deploy-token/ (MacOS).

To generate the public/private key pair we use ssh-keygen with the following command:

ssh-keygen -t rsa -b 4096 -C "your@email.address" -f ~/development/gitlab-deploy-token/id_rsa

This will give you a id_rsa file (private key!) and id_rsa.pub which is the public key. the your@emaill.address is only a comment added to the public key, tweak as you desire.

Upload private SSH key to Gitlab

To have the Private SSH key available in your build pipeline we need to add it as a CI/CD variable. You can add it to every project as you require it but to centralise your secrets its better to set them at the root level of your organisation/team/group/...

Because you want to keep your secrets secure it is also wise to think about the layering of permissions that you utilise within your organisation:

  • Variables can only be updated or viewed by project members with maintainer permissions (source: Gitlab).

To upload the SSH key as CI / CD variable:

  • Navigate to Settings → CI / CD → Variables
  • Add key with name SSH_PRIVATE_KEY_TOOLKIT and paste the contents of your private key as the value. (trick: cat id_rsa | pbcopy to copy the file contents directly to your clipboard on MacOS)

All projects underneath the place where you have declared the variable SSH_PRIVATE_KEY_TOOLKIT will now have access to this variable during the build pipeline:

Create Deploy Token in your project

To start trusting the uploaded SSH key we need to upload its public key as 'Deploy Key' into your project.

  • Navigate to Settings → Repository → Deploy Keys within your project
  • Add a new Deploy Key and add the public key as contents (cat id_rsa.pub | pbcopy)
  • ⚠️  Do not forget to tick the box Write access allowed or you will not be able to use your Deploy key to push back to the repository.

After you have added this Deploy Token you can now push to your repository from your build pipeline with the SSH key configured.

Use deploy token in your CI/CD pipeline

We are using node:12 as our docker container which comes pre-loaded with git installed. If your runner does not have git installed you will have to do this manually yourself in your .gitlab-ci.yml

We are going to configure git to be able to push back to the origin with our SSH key as credentials in our build stage as well as configure git to use the original committers email address  to improve traceability.

image: node:12

"Tag version":
  stage: "Build"
    before_script:
      # put SSH key in `.ssh and make it accessible
      - mkdir -p ~/.ssh
      - echo "$SSH_PRIVATE_KEY_TOOLKIT" > ~/.ssh/id_rsa; chmod 0600 ~/.ssh/id_rsa
      - echo "StrictHostKeyChecking no " > /root/.ssh/config
      # configure git to use email of commit-owner
      - git config --global http.sslVerify false
      - git config --global pull.ff only
      - git config --global user.email "$GITLAB_USER_EMAIL"
      - git config --global user.name "🤖 GitLab CI/CD"
      - echo "setting origin remote to 'git@$CI_SERVER_HOST:$CI_PROJECT_PATH.git'"
      # first cleanup any existing named remotes called 'origin' before re-setting the url
      - git remote rm origin
      - git remote add origin git@$CI_SERVER_HOST:$CI_PROJECT_PATH.git
      # have the gitlab runner checkout be linked to the branch we are building
      - git checkout -B "$CI_BUILD_REF_NAME"

There are some magic variables in here: CI_BUILD_REF_NAME (branch name), CI_PROJECT_PATH  (repository), CI_SERVER_HOST (gitlab.com unless self-hosted) GITLAB_USER_EMAIL (email of commit owner) which are all made available as environment variables by default by the gitlab build pipeline. For a complete list you can find them here https://docs.gitlab.com/ee/ci/variables/

Now we are able to tag our commit and push that back to the origin:

script:
    - VERSION=1.2.3
    - git tag v$VERSION -m "🤖 Tagged by Gitlab CI/CD Pipeline" -m "For further reference see $CI_PIPELINE_URL" -m "[skip ci]"
    - git push origin v$VERSION --no-verify

When doing 🧙‍♀️ magic ✨ I always like to add as much clarity and traceability as possible. This is why the commit contains the robot emoji to indicate that it was automation and also why the commit contains a traceback to the gitlab pipeline ( "For further reference see $CI_PIPELINE_URL") which leverages the CI_PIPELINE_URL environment variable to create a bi-directional relationship between the commit and the point of origin where it was created.

⚠️  We use [skip ci] in the tags commit message to prevent a recursive build pipeline which tags itself to build and tag itself and.. well, that wouldn't be enjoyable...

You are now able to push commits/tags/.. to your repository from within your gitlab-ci pipeline! 🎉  The only thing which now remains is to add the deploy token to every repository where you want to leverage this same solution and adapt your .gitlab-ci.yml build pipeline.

🧐 Downsides to this approach

  • You are uploading a SSH key which has write access to the repositories which have uploaded its accompanying public key as Deploy Token in Gitlab. This token is retrievable by everyone within the scope where you define it who has enough permissions (maintainer+) to manipulate the CICD settings of gitlab.
  • It is possible to leak the private SSH key by mistake by echo'ing the contents in your .gitlab-ci.yml script. It will then end up in the logs of your build pipeline.
  • In theory you could generate a SSH key pair per project to scope down the impact when a key gets leaked. Although that would add a lot of manual labor.