Token-Zero: When you need a token to create a token

Token-Zero: When you need a token to create a token

Table of Contents

When automating a GitLab installation, you’ll often need to create “token zero” - the first access token used to bootstrap your automation processes. Unlike the initial root password, this token is specifically scoped for API interactions during setup. This creates a chicken-and-egg situation: you need a token to make API calls, but you need to make an API call to create a token.

Understanding Access Tokens in Automated GitLab Installations

During automated deployments, multiple configuration steps require API authentication to create resources, set up runners, or configure integrations. The challenge lies in creating that very first access token when your automation needs to make its first API call.

A common solution is to manually create an initial access token with minimal required scopes (like api and read_user) through the GitLab UI or API after the first installation step. This token can then be securely stored and used by your automation tools to perform subsequent configuration tasks. Unlike the root password, this token should have carefully limited permissions aligned with the principle of least privilege.

Ideally, practices for handling this initial token include:

  • Create it with only the necessary scopes for your automation
  • Store it securely in your configuration management system or secrets manager
  • Consider it a temporary bootstrap token and rotate it once your installation is complete
  • Document its creation and usage as part of your installation process

The problem lies with the fact that this token is generally created using the GitLab UI, by a human, meaning that the token is immediately compromised from a visibility perspective - at least one human being has seen it.

However - it is possible to create a token using ruby:

begin
  user = User.find_by_username('root')
  token = PersonalAccessToken.create!(
    user: user,
    name: 'token-zero',
    scopes: ['admin_mode','api'],
    expires_at: '2025-12-31'
  )
  puts token.token
rescue => e
  puts 'Error: ' + e.message
  exit 1
end

You can wrap a script like this up in a bit of bash and include it somewhere in your userdata (or similar), here’s what works for me:

#!/bin/bash -ex

USER_NAME="root"
TOKEN_NAME="gitlab-admin-token"
EXPIRES_AT=$(date -u -d "+1 year" +"%Y-%m-%d")
SCOPES="'admin_mode','sudo','api','create_runner','manage_runner'"
TOKEN=$(sudo gitlab-rails runner "
begin
  user = User.find_by_username('$USER_NAME')
  token = PersonalAccessToken.create!(
    user: user,
    name: '$TOKEN_NAME',
    scopes: [$SCOPES],
    expires_at: '$EXPIRES_AT'
  )
  puts token.token
rescue => e
  puts 'Error: ' + e.message
  exit 1
end
")

if [ -z "$TOKEN" ]; then
    echo "Failed to generate GitLab token"
    exit 1
fi

SECRET_STRING=$(cat <<EOF
{
    "gitlab_token": "$TOKEN",
    "created_at": "$(date -u +"%Y-%m-%dT%H:%M:%SZ")",
    "expires_at": "$EXPIRES_AT",
    "scopes": "$SCOPES"
}
EOF
)

echo "Storing api key..."
aws secretsmanager put-secret-value \
    --secret-id "${token_secret_id}" \
    --secret-string "$SECRET_STRING" \
    --region ${aws_region}

I store the token in AWS Secrets Manager, from where it can be picked up in subsequent deployment stages.

Having a clear process for handling this initial access token is crucial for maintaining security while enabling automated deployments.

Related Posts

Navigating the GitLab repository

Navigating the GitLab repository

If you’ve ever needed to debug a GitLab issue or understand how a particular feature works, you’re in luck – GitLab’s open-source nature means all the answers are right there in the code. But with over 2 million lines of code spread across thousands of files, finding those answers can feel like searching for a needle in a particularly large and complex haystack.

Managing Multiple GitLab Identities for Home and Work

Managing Multiple GitLab Identities for Home and Work

Many developers need to interact with GitLab using different identities – a personal account for passion projects and a work account for professional endeavors. Juggling these can be cumbersome, leading to accidental commits with the wrong email or access issues. This guide provides a step-by-step approach to configuring your system to seamlessly manage multiple GitLab profiles, automatically selecting the correct SSH keys and user configurations based on the repository’s location.

ClickHouse: It's fast

ClickHouse: It's fast

In an era of substantial data generation, conventional database systems frequently encounter challenges with the scale and demand for rapid query responses. ClickHouse is an open-source, column-oriented Database Management System (DBMS) for Online Analytical Processing (OLAP), facilitating real-time analytical reports from SQL queries. Initially developed by Yandex, it was engineered for exceptionally fast processing of large-scale analytical queries.3 ClickHouse’s high performance is attributed to an innovative storage design that makes processing massive datasets more manageable.