Skip to content

Make PersonalAccessToken#token routable

What does this MR do and why?

This builds upon !168833 (closed) / !172708 (merged) and allow Personal Access Tokens to be routable (i.e. the Cells HTTP Router will be able to decode it and use the information to route a request to a particular cell).

The change is behind the routable_pat feature flag, which can be enabled for specific users.

Another global auth_finder_no_token_length_detection feature flag is introduced to switch to a new logic to check tokens in lib/gitlab/auth/auth_finders.rb: when the feature flag is enabled, token length won't be used to detect PAT that are present in a Bearer header. Without this feature flag, any token between 20 and 40 characters would be assumed to be a PAT when set in the Bearer header. Now that routable PATs can be between 43 characters (i.e. glpat-<37 characters>, see the specification from !172708 (merged)) and 316 characters (i.e. glpat-<310 characters>, see the specification from !172708 (merged)), we cannot assume a 20-40 token is a PAT anymore (in fact, routable PAT minimum size is 43 characters).

See https://handbook.gitlab.com/handbook/engineering/architecture/design-documents/cells/routable_tokens/ for more information on Routable Tokens.

MR acceptance checklist

The change being behind a feature flag, it should be safe to deploy it, and I will perform various checks with my user before enabling it further.

Steps to reproduce locally

  1. Checkout the branch
  2. Open a Rails console:
# Ensure the feature flag is disabled
> Feature.disable(:routable_pat)

# Create a non-routable token
> pat1 = User.first.personal_access_tokens.create!(name: "My non-routable token", scopes: [:api], expires_at: 1.year.from_now)

# Check the token shape
> pat1.token
=> "glpat-ts8y8Mk2gxi-xvXVsxfN"

# Base-64 decode the payload, it doesn't contain routing information
> Base64.urlsafe_decode64(pat1.token.delete_prefix('glpat-'))
=> "\xB6\xCF2\xF0\xC96\x83\x18\xBE\xC6\xF5\xD5\xB3\x17\xCD"

# Enable the feature flag for User.first
> Feature.enable(:routable_pat, User.first)

# Create a routable token
> pat2 = User.first.personal_access_tokens.create!(name: "My routable token", scopes: [:api], expires_at: 1.year.from_now)

# Check the token shape
> pat2.token
=> "glpat-YzowCm86MQp1OjFSDdqkLmVS337oi4qlKHnlEA.121e8269o"

# Base-64 decode the payload, it contains routing information
> decoded_payload = Base64.urlsafe_decode64(pat2.token.delete_prefix('glpat-')[...-10])
=> "c:0\no:1\nu:1R\r\xDA\xA4.eR\xDF~\xE8\x8B\x8A\xA5(y\xE5\x10"

> routing_payload_length = decoded_payload.size - decoded_payload[-1].unpack1('C') - 1
=> 11

> decoded_payload[0, routing_payload_length].lines(chomp: true).map { |l| l.split(':') }.to_h
=> {"c"=>"0", "o"=>"1", "u"=>"1"}

Related to #487009 (closed).

Edited by Lin Jen-Shin

Merge request reports

Loading