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.

A New Blog

A New Blog

I’ve finally taken the plunge and committed to sharing my DevOps experiences through this new blog. After years of wrestling with pipelines, debugging deployment issues, and celebrating those sweet moments when everything just clicks, I figured it’s time to give something back to the community that has taught me so much. I’ll be sharing both the wins and the “learning opportunities” (aka the times things went spectacularly wrong) that come with working in DevOps.

Simulating GitLab Activity

Simulating GitLab Activity

Self-hosted GitLab instances are critical infrastructure for many organizations. While setting up GitLab is straightforward, operating it at scale requires deep understanding of its behavior under real-world conditions. This is where user activity simulation can become invaluable.