Skip to content
Snippets Groups Projects
Unverified Commit 38423b5e authored by John Skarbek's avatar John Skarbek Committed by GitLab
Browse files

Merge branch 'smriti-rake-task-analyze-tokens-16-3' into '16-3-stable-ee'

Backport add Rake task to show token expiration info

See merge request https://gitlab.com/gitlab-org/gitlab/-/merge_requests/159383



Merged-by: default avatarJohn Skarbek <jskarbek@gitlab.com>
Approved-by: default avatarJon Glassman <jglassman@gitlab.com>
Approved-by: default avatarRichard Chong <rchong@gitlab.com>
Approved-by: default avatarImre Farkas <ifarkas@gitlab.com>
Reviewed-by: default avatarImre Farkas <ifarkas@gitlab.com>
Co-authored-by: default avatarsmriti <sgarg@gitlab.com>
Co-authored-by: default avatarStan Hu <stanhu@gmail.com>
Co-authored-by: default avatarJon Glassman <jglassman@gitlab.com>
parents 2b3cf55d 5cff20e0
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -370,6 +370,8 @@ gem 'gettext', '~> 3.3', require: false, group: :development
 
gem 'batch-loader', '~> 2.0.1'
 
gem 'tty-prompt', '~> 0.23', require: false
# Perf bar
gem 'peek', '~> 1.1'
 
Loading
Loading
Loading
Loading
@@ -2022,6 +2022,7 @@ DEPENDENCIES
timfel-krb5-auth (~> 0.8)
toml-rb (~> 2.2.0)
truncato (~> 0.7.12)
tty-prompt (~> 0.23)
typhoeus (~> 1.4.0)
undercover (~> 0.4.4)
unf (~> 0.1.4)
Loading
Loading
# frozen_string_literal: true
class ChangePersonalAccessTokensRemoveNotNullExpiresAt < Gitlab::Database::Migration[2.1]
disable_ddl_transaction!
CONSTRAINT_NAME = 'check_b8d60815eb'
def up
remove_not_null_constraint :personal_access_tokens, :expires_at
end
def down
add_not_null_constraint :personal_access_tokens, :expires_at, validate: false, constraint_name: CONSTRAINT_NAME
end
end
38dcfa54fa7da63c1fbceb842e277b27bd90b1b0ef31fc82db8f80e6ba286047
\ No newline at end of file
---
stage: Govern
group: Authentication
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://handbook.gitlab.com/handbook/product/ux/technical-writing/#assignments
---
# Access token Rake tasks
DETAILS:
**Tier:** Free, Premium, Ultimate
**Offering:** Self-managed
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/467416) in GitLab 17.2.
## Analyze token expiration dates
In GitLab 16.0, a [background migration](https://gitlab.com/gitlab-org/gitlab/-/issues/369123)
gave all non-expiring personal, project, and group access tokens an expiration date set at one
year after those tokens were created.
To identify which tokens might have been affected by this migration, you can run a
Rake task that analyses all access tokens and displays the top ten most common expiration dates:
::Tabs
:::TabTitle Linux package (Omnibus)
```shell
gitlab-rake gitlab:tokens:analyze
```
:::TabTitle Helm chart (Kubernetes)
```shell
# Find the toolbox pod
kubectl --namespace <namespace> get pods -lapp=toolbox
kubectl exec -it <toolbox-pod-name> -- sh -c 'cd /srv/gitlab && bin/rake gitlab:tokens:analyze'
```
:::TabTitle Docker
```shell
sudo docker exec -it <container_name> /bin/bash
gitlab-rake gitlab:tokens:analyze
```
:::TabTitle Self-compiled (source)
```shell
sudo RAILS_ENV=production -u git -H bundle exec rake gitlab:tokens:analyze
```
::EndTabs
This task analyzes all the access tokens and groups them by expiration date.
The left column shows the expiration date, and the right column shows how many tokens
have that expiration date. Example output:
```plaintext
======= Personal/Project/Group Access Token Expiration Migration =======
Started at: 2023-06-15 10:20:35 +0000
Finished : 2023-06-15 10:23:01 +0000
===== Top 10 Personal/Project/Group Access Token Expiration Dates =====
| Expiration Date | Count |
|-----------------|-------|
| 2024-06-15 | 1565353 |
| 2017-12-31 | 2508 |
| 2018-01-01 | 1008 |
| 2016-12-31 | 833 |
| 2017-08-31 | 705 |
| 2017-06-30 | 596 |
| 2018-12-31 | 548 |
| 2017-05-31 | 523 |
| 2017-09-30 | 520 |
| 2017-07-31 | 494 |
========================================================================
```
In this example, you can see that over 1.5 million access tokens have an
expiration date of 2024-06-15, one year after the migration was run
on 2023-06-15. This suggests that most of these tokens were assigned by
the migration. However, there is no way to know for sure whether other
tokens were created manually with the same date.
## Update expiration dates in bulk
Prerequisites:
You must:
- Be an administrator.
- Have an interactive terminal.
Run the following Rake task to extend or remove expiration dates from tokens in bulk:
1. Run the tool:
::Tabs
:::TabTitle Linux package (Omnibus)
```shell
gitlab-rake gitlab:tokens:edit
```
:::TabTitle Helm chart (Kubernetes)
```shell
# Find the toolbox pod
kubectl --namespace <namespace> get pods -lapp=toolbox
kubectl exec -it <toolbox-pod-name> -- sh -c 'cd /srv/gitlab && bin/rake gitlab:tokens:edit'
```
:::TabTitle Docker
```shell
sudo docker exec -it <container_name> /bin/bash
gitlab-rake gitlab:tokens:edit
```
:::TabTitle Self-compiled (source)
```shell
sudo RAILS_ENV=production -u git -H bundle exec rake gitlab:tokens:edit
```
::EndTabs
After the tool starts, it shows the output from the [analyze step](#analyze-token-expiration-dates)
plus an additional prompt about modifying the expiration dates:
```plaintext
======= Personal/Project/Group Access Token Expiration Migration =======
Started at: 2023-06-15 10:20:35 +0000
Finished : 2023-06-15 10:23:01 +0000
===== Top 10 Personal/Project/Group Access Token Expiration Dates =====
| Expiration Date | Count |
|-----------------|-------|
| 2024-05-14 | 1565353 |
| 2017-12-31 | 2508 |
| 2018-01-01 | 1008 |
| 2016-12-31 | 833 |
| 2017-08-31 | 705 |
| 2017-06-30 | 596 |
| 2018-12-31 | 548 |
| 2017-05-31 | 523 |
| 2017-09-30 | 520 |
| 2017-07-31 | 494 |
========================================================================
What do you want to do? (Press ↑/↓ arrow or 1-3 number to move and Enter to select)
‣ 1. Extend expiration date
2. Remove expiration date
3. Quit
```
### Extend expiration dates
To extend expiration dates on all tokens matching a given expiration date:
1. Select option 1, `Extend expiration date`:
```plaintext
What do you want to do?
‣ 1. Extend expiration date
2. Remove expiration date
3. Quit
```
1. The tool asks you to select one of the expiration dates listed. For example:
```plaintext
Select an expiration date (Press ↑/↓/←/→ arrow to move and Enter to select)
‣ 2024-05-14
2017-12-31
2018-01-01
2016-12-31
2017-08-31
2017-06-30
```
Use the arrow keys on your keyboard to select a date. To abort,
scroll all the way down and select `--> Abort`. Press <kbd>Enter</kbd> to confirm
your selection:
```plaintext
Select an expiration date
2017-06-30
2018-12-31
2017-05-31
2017-09-30
2017-07-31
‣ --> Abort
```
If you select a date, the tool prompts you for a new expiration date:
```plaintext
What would you like the new expiration date to be? (2025-05-14) 2024-05-14
```
The default is one year from the selected date. Press <kbd>Enter</kbd>
to use the default, or manually enter a date in `YYYY-MM-DD` format.
1. After you have entered a valid date, the tool asks one more time for confirmation:
```plaintext
Old expiration date: 2024-05-14
New expiration date: 2025-05-14
WARNING: This will now update 1565353 token(s). Are you sure? (y/N)
```
If you enter `y`, the tool extends the expiration date
for all the tokens with the selected expiration date.
If you enter `N`, the tool aborts the update task and return to the
original analyze output.
### Remove expiration dates
To remove expiration dates on all tokens matching
a given expiration date:
1. Select option 2, `Remove expiration date`:
```plaintext
What do you want to do?
1. Extend expiration date
‣ 2. Remove expiration date
3. Quit
```
1. The tool asks you to select the expiration date from the table. For example:
```plaintext
Select an expiration date (Press ↑/↓/←/→ arrow to move and Enter to select)
‣ 2024-05-14
2017-12-31
2018-01-01
2016-12-31
2017-08-31
2017-06-30
```
Use the arrow keys on your keyboard to select a date. To abort,
scroll all the way down and select `--> Abort`. Press <kbd>Enter</kbd> to confirm
your selection:
```plaintext
Select an expiration date
2017-06-30
2018-12-31
2017-05-31
2017-09-30
2017-07-31
‣ --> Abort
```
1. After selecting a date, the tool prompts you to confirm the selection:
```plaintext
WARNING: This will remove the expiration for tokens that expire on 2024-05-14.
This will affect 1565353 tokens. Are you sure? (y/N)
```
If you enter `y`, the tool removes the expiration date for all the
tokens with the selected expiration date.
If you enter `N`, the tool aborts the update task and returns to the first menu.
Loading
Loading
@@ -20,6 +20,7 @@ The following Rake tasks are available for use with GitLab:
 
| Tasks | Description |
|:------------------------------------------------------|:------------|
| [Access token expiration tasks](../administration/raketasks/tokens/index.md) | Bulk extend or remove expiration dates for access tokens. |
| [Back up and restore](../administration/backup_restore/index.md) | Back up, restore, and migrate GitLab instances between servers. |
| [Clean up](cleanup.md) | Clean up unneeded items from GitLab instances. |
| [Development](../development/rake_tasks.md) | Tasks for GitLab contributors. |
Loading
Loading
Loading
Loading
@@ -195,3 +195,433 @@ This table shows available scopes per token. Scopes can be limited further on to
- Trigger tokens.
- Runner registration tokens.
- Any other sensitive secrets etc.
## Expired access tokens
If an existing access token is in use and reaches the `expires_at` value, the token
expires and:
- Can no longer be used for authentication.
- Is not visible in the UI.
Requests made using this token return a `401 Unauthorized` response. Too many
unauthorized requests in a short period of time from the same IP address
result in `403 Forbidden` responses from GitLab.com.
For more information on authentication request limits, see [Git and container registry failed authentication ban](../user/gitlab_com/index.md#git-and-container-registry-failed-authentication-ban).
### Identify expired access tokens from logs
> [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/464652) in GitLab 17.2.
Prerequisites:
You must:
- Be an administrator.
- Have access to the [`api_json.log`](../administration/logs/index.md#api_jsonlog) file.
To identify which `401 Unauthorized` requests are failing due to
expired access tokens, use the following fields in the `api_json.log` file:
|Field name|Description|
|----------|-----------|
|`meta.auth_fail_reason`|The reason the request was rejected. Possible values: `token_expired`, `token_revoked`, `insufficient_scope`, and `impersonation_disabled`.|
|`meta.auth_fail_token_id`|A string describing the type and ID of the attempted token.|
When a user attempts to use an expired token, the `meta.auth_fail_reason`
is `token_expired`. The following shows an excerpt from a log
entry:
```json
{
"status": 401,
"method": "GET",
"path": "/api/v4/user",
...
"meta.auth_fail_reason": "token_expired",
"meta.auth_fail_token_id": "PersonalAccessToken/12",
}
```
`meta.auth_fail_token_id` indicates that an access token of ID 12 was used.
To find more information about this token, use the [personal access token API](../api/personal_access_tokens.md#get-single-personal-access-token).
You can also use the API to [rotate the token](../api/personal_access_tokens.md#rotate-a-personal-access-token).
### Replace expired access tokens
To replace the token:
1. Check where this token may have been used previously, and remove it from any
automation might still use the token.
- For personal access tokens, use the [API](../api/personal_access_tokens.md#list-personal-access-tokens)
to list tokens that have expired recently. For example, go to `https://gitlab.com/api/v4/personal_access_tokens`,
and locate tokens with a specific `expires_at` date.
- For project access tokens, use the
[project access tokens API](../api/project_access_tokens.md#list-project-access-tokens)
to list recently expired tokens.
- For group access tokens, use the
[group access tokens API](../api/group_access_tokens.md#list-group-access-tokens)
to list recently expired tokens.
1. Create a new access token:
- For personal access tokens, [use the UI](../user/profile/personal_access_tokens.md#create-a-personal-access-token)
or [Users API](../api/users.md#create-a-personal-access-token).
- For a project access token, [use the UI](../user/project/settings/project_access_tokens.md#create-a-project-access-token)
or [project access tokens API](../api/project_access_tokens.md#create-a-project-access-token).
- For a group access token, [use the UI](../user/group/settings/group_access_tokens.md#create-a-group-access-token-using-ui)
or [group access tokens API](../api/group_access_tokens.md#create-a-group-access-token).
1. Replace the old access token with the new access token. This process varies
depending on how you use the token, for example if configured as a secret or
embedded within an application. Requests made from this token should no longer
return `401` responses.
## Troubleshooting
### Identify personal, project, and group access tokens expiring on a certain date
Access tokens that have no expiration date are valid indefinitely, which is a
security risk if the access token is divulged.
To manage this risk, when you upgrade to GitLab 16.0 and later, any
[personal](../user/profile/personal_access_tokens.md),
[project](../user/project/settings/project_access_tokens.md), or
[group](../user/group/settings/group_access_tokens.md) access
token that does not have an expiration date automatically has an expiration
date set at one year from the date of upgrade.
If you are not aware of when your tokens expire because the dates have changed,
you might have unexpected authentication failures when trying to sign into GitLab
on that date.
To manage this issue, you can use this [tool that assists with analyzing, extending, or remove token expiration dates](../administration/raketasks/tokens/index.md).
If you cannot run the tool, you can also run scripts in self-managed instances to identify
tokens that either:
- Expire on a specific date.
- Have no expiration date.
You run these scripts from your terminal window in either:
- A [Rails console session](../administration/operations/rails_console.md#starting-a-rails-console-session).
- Using the [Rails Runner](../administration/operations/rails_console.md#using-the-rails-runner).
The specific scripts you run differ depending on if you have upgraded to GitLab 16.0
and later, or not:
- If you have not yet upgraded to GitLab 16.0 or later, [identify tokens that do not have an expiration date](#find-tokens-with-no-expiration-date).
- If you have upgraded to GitLab 16.0 or later, use scripts to identify any of
the following:
- [Tokens expiring on a specific date](#find-all-tokens-expiring-on-a-specific-date).
- [Tokens expiring in a specific month](#find-tokens-expiring-in-a-given-month).
- [Dates when many tokens expire](#identify-dates-when-many-tokens-expire).
After you have identified tokens affected by this issue, you can run a final script
to [extend the lifetime of specific tokens](#extend-token-lifetime) if needed.
These scripts return results in the following format:
```plaintext
Expired Group Access Token in Group ID 25, Token ID: 8, Name: Example Token, Scopes: ["read_api", "create_runner"], Last used:
Expired Project Access Token in Project ID 2, Token ID: 9, Name: Test Token, Scopes: ["api", "read_registry", "write_registry"], Last used: 2022-02-11 13:22:14 UTC
```
For more information on this, see [incident 18003](https://gitlab.com/gitlab-com/gl-infra/production/-/issues/18003).
#### Find all tokens expiring on a specific date
This script finds tokens that expire on a specific date.
Prerequisites:
- You must know the exact date your instance was upgraded to GitLab 16.0.
To use it:
::Tabs
:::TabTitle Rails console session
1. In your terminal window, connect to your instance.
1. Start a Rails console session with `sudo gitlab-rails console`.
1. Depending on your needs, copy either the entire [`expired_tokens.rb`](#expired_tokensrb)
or [`expired_tokens_date_range.rb`](#expired_tokens_date_rangerb) script below, and paste it into the console.
Change the `expires_at_date` to the date one year after your instance was upgraded to GitLab 16.0.
1. Press <kbd>Enter</kbd>.
:::TabTitle Rails Runner
1. In your terminal window, connect to your instance.
1. Depending on your needs, copy either the entire [`expired_tokens.rb`](#expired_tokensrb)
or [`expired_tokens_date_range.rb`](#expired_tokens_date_rangerb) script below, and save it
as a file on your instance:
- Name it `expired_tokens.rb`.
- Change the `expires_at_date` to the date one year after your instance was upgraded to GitLab 16.0.
- The file must be accessible to `git:git`.
1. Run this command, changing the path to the _full_ path to your `expired_tokens.rb` file:
```shell
sudo gitlab-rails runner /path/to/expired_tokens.rb
```
For more information, see the [Rails Runner troubleshooting section](../administration/operations/rails_console.md#troubleshooting).
::EndTabs
##### `expired_tokens.rb`
This script requires you to know the exact date your GitLab instance
was upgraded to GitLab 16.0.
```ruby
# Change this value to the date one year after your GitLab instance was upgraded.
expires_at_date = "2024-05-22"
# Check for expiring personal access tokens
PersonalAccessToken.owner_is_human.where(expires_at: expires_at_date).find_each do |token|
puts "Expired Personal Access Token ID: #{token.id}, User Email: #{token.user.email}, Name: #{token.name}, Scopes: #{token.scopes}, Last used: #{token.last_used_at}"
end
# Check for expiring project and group access tokens
PersonalAccessToken.project_access_token.where(expires_at: expires_at_date).find_each do |token|
token.user.members.each do |member|
type = member.is_a?(GroupMember) ? 'Group' : 'Project'
puts "Expired #{type} access token in #{type} ID #{member.source_id}, Token ID: #{token.id}, Name: #{token.name}, Scopes: #{token.scopes}, Last used: #{token.last_used_at}"
end
end
```
#### Find tokens expiring in a given month
This script finds tokens that expire in a particular month. You don't need to know
the exact date your instance was upgraded to GitLab 16.0. To use it:
::Tabs
:::TabTitle Rails console session
1. In your terminal window, start a Rails console session with `sudo gitlab-rails console`.
1. Paste in the entire [`tokens_with_no_expiry.rb`](#tokens_with_no_expiryrb) script below.
If desired, change the `date_range` to a different range.
1. Press <kbd>Enter</kbd>.
:::TabTitle Rails Runner
1. In your terminal window, connect to your instance.
1. Copy this entire [`tokens_with_no_expiry.rb`](#tokens_with_no_expiryrb) script below, and save it as a file on your instance:
- Name it `expired_tokens_date_range.rb`.
- If desired, change the `date_range` to a different range.
- The file must be accessible to `git:git`.
1. Run this command, changing `/path/to/expired_tokens_date_range.rb`
to the _full_ path to your `expired_tokens_date_range.rb` file:
```shell
sudo gitlab-rails runner /path/to/expired_tokens_date_range.rb
```
For more information, see the [Rails Runner troubleshooting section](../administration/operations/rails_console.md#troubleshooting).
::EndTabs
##### `expired_tokens_date_range.rb`
```ruby
# This script enables you to search for tokens that expire within a
# certain date range (like 1.month) from the current date. Use it if
# you're unsure when exactly your GitLab 16.0 upgrade completed.
date_range = 1.month
# Check for personal access tokens
PersonalAccessToken.owner_is_human.where(expires_at: Date.today .. Date.today + date_range).find_each do |token|
puts "Expired Personal Access Token ID: #{token.id}, User Email: #{token.user.email}, Name: #{token.name}, Scopes: #{token.scopes}, Last used: #{token.last_used_at}"
end
# Check for expiring project and group access tokens
PersonalAccessToken.project_access_token.where(expires_at: Date.today .. Date.today + date_range).find_each do |token|
token.user.members.each do |member|
type = member.is_a?(GroupMember) ? 'Group' : 'Project'
puts "Expired #{type} access token in #{type} ID #{member.source_id}, Token ID: #{token.id}, Name: #{token.name}, Scopes: #{token.scopes}, Last used: #{token.last_used_at}"
end
end
```
#### Identify dates when many tokens expire
This script identifies dates when most of tokens expire. You can use it in combination with other scripts on this page to identify and extend large batches of tokens that may be approaching their expiration date, in case your team has not yet set up token rotation.
The script returns results in this format:
```plaintext
42 Personal Access Tokens will expire at 2024-06-27
17 Personal Access Tokens will expire at 2024-09-23
3 Personal Access Tokens will expire at 2024-08-13
```
To use it:
::Tabs
:::TabTitle Rails console session
1. In your terminal window, start a Rails console session with `sudo gitlab-rails console`.
1. Paste in the entire [`dates_when_most_of_tokens_expire.rb`](#dates_when_most_of_tokens_expirerb) script.
1. Press <kbd>Enter</kbd>.
:::TabTitle Rails Runner
1. In your terminal window, connect to your instance.
1. Copy this entire [`dates_when_most_of_tokens_expire.rb`](#dates_when_most_of_tokens_expirerb)
script, and save it as a file on your instance:
- Name it `dates_when_most_of_tokens_expire.rb`.
- The file must be accessible to `git:git`.
1. Run this command, changing `/path/to/dates_when_most_of_tokens_expire.rb`
to the _full_ path to your `dates_when_most_of_tokens_expire.rb` file:
```shell
sudo gitlab-rails runner /path/to/dates_when_most_of_tokens_expire.rb
```
For more information, see the [Rails Runner troubleshooting section](../administration/operations/rails_console.md#troubleshooting).
::EndTabs
##### `dates_when_most_of_tokens_expire.rb`
```ruby
PersonalAccessToken
.select(:expires_at, Arel.sql('count(*)'))
.where('expires_at >= NOW()')
.group(:expires_at)
.order(Arel.sql('count(*) DESC'))
.limit(10)
.each do |token|
puts "#{token.count} Personal Access Tokens will expire at #{token.expires_at}"
end
```
#### Find tokens with no expiration date
This script finds tokens that lack an expiration date: `expires_at` is `NULL`. For users
who have not yet upgraded to GitLab version 16.0 or later, the token `expires_at`
value is `NULL`, and can be used to identify tokens to add an expiration date to.
You can use this script in either the [Rails console](../administration/operations/rails_console.md)
or the [Rails Runner](../administration/operations/rails_console.md#using-the-rails-runner):
::Tabs
:::TabTitle Rails console session
1. In your terminal window, connect to your instance.
1. Start a Rails console session with `sudo gitlab-rails console`.
1. Paste in the entire [`tokens_with_no_expiry.rb`](#tokens_with_no_expiryrb) script below.
1. Press <kbd>Enter</kbd>.
:::TabTitle Rails Runner
1. In your terminal window, connect to your instance.
1. Copy this entire [`tokens_with_no_expiry.rb`](#tokens_with_no_expiryrb) script below, and save it as a file on your instance:
- Name it `tokens_with_no_expiry.rb`.
- The file must be accessible to `git:git`.
1. Run this command, changing the path to the _full_ path to your `tokens_with_no_expiry.rb` file:
```shell
sudo gitlab-rails runner /path/to/tokens_with_no_expiry.rb
```
For more information, see the [Rails Runner troubleshooting section](../administration/operations/rails_console.md#troubleshooting).
::EndTabs
##### `tokens_with_no_expiry.rb`
This script finds tokens without a value set for `expires_at`.
```ruby
# This script finds tokens which do not have an expires_at value set.
# Check for expiring personal access tokens
PersonalAccessToken.owner_is_human.where(expires_at: nil).find_each do |token|
puts "Expires_at is nil for Personal Access Token ID: #{token.id}, User Email: #{token.user.email}, Name: #{token.name}, Scopes: #{token.scopes}, Last used: #{token.last_used_at}"
end
# Check for expiring project and group access tokens
PersonalAccessToken.project_access_token.where(expires_at: nil).find_each do |token|
token.user.members.each do |member|
type = member.is_a?(GroupMember) ? 'Group' : 'Project'
puts "Expires_at is nil for #{type} access token in #{type} ID #{member.source_id}, Token ID: #{token.id}, Name: #{token.name}, Scopes: #{token.scopes}, Last used: #{token.last_used_at}"
end
end
```
### Extend token lifetime
Delay the expiration of certain tokens with this script.
From GitLab 16.0, all access tokens have an expiration date. After you deploy at least GitLab 16.0,
any non-expiring access tokens expire one year from the date of deployment.
If this date is approaching and there are tokens that have not yet
been rotated, you can use this script to delay expiration and give
users more time to rotate their tokens.
#### Extend lifetime for specific tokens
This script extends the lifetime of all tokens which expire on a specified date, including:
- Personal access tokens
- Group access tokens
- Project access tokens
Users that have intentionally set a token to expire on the specified date will have their
token lifetimes extended as well.
To use the script:
::Tabs
:::TabTitle Rails console session
1. In your terminal window, start a Rails console session with `sudo gitlab-rails console`.
1. Paste in the entire [`extend_expiring_tokens.rb`](#extend_expiring_tokensrb) script below.
If desired, change the `expiring_date` to a different date.
1. Press <kbd>Enter</kbd>.
:::TabTitle Rails Runner
1. In your terminal window, connect to your instance.
1. Copy this entire [`extend_expiring_tokens.rb`](#extend_expiring_tokensrb) script below, and save it as a file on your instance:
- Name it `extend_expiring_tokens.rb`.
- If desired, change the `expiring_date` to a different date.
- The file must be accessible to `git:git`.
1. Run this command, changing `/path/to/extend_expiring_tokens.rb`
to the _full_ path to your `extend_expiring_tokens.rb` file:
```shell
sudo gitlab-rails runner /path/to/extend_expiring_tokens.rb
```
For more information, see the [Rails Runner troubleshooting section](../administration/operations/rails_console.md#troubleshooting).
::EndTabs
##### `extend_expiring_tokens.rb`
```ruby
expiring_date = Date.new(2024, 5, 30)
new_expires_at = 6.months.from_now
total_updated = PersonalAccessToken
.not_revoked
.without_impersonation
.where(expires_at: expiring_date.to_date)
.update_all(expires_at: new_expires_at.to_date)
puts "Updated #{total_updated} tokens with new expiry date #{new_expires_at}"
```
# frozen_string_literal: true
require_relative 'tokens/manage_expiry_task'
namespace :gitlab do
namespace :tokens do
desc 'GitLab | Tokens | Show information about tokens'
task analyze: :environment do |_t, _args|
Tasks::Gitlab::Tokens::ManageExpiryTask.new.analyze
end
desc 'GitLab | Tokens | Edit expiration dates for tokens'
task edit: :environment do |_t, _args|
Tasks::Gitlab::Tokens::ManageExpiryTask.new.edit
end
end
end
# frozen_string_literal: true
require 'tty-prompt'
module Tasks
module Gitlab
module Tokens
class ManageExpiryTask
TOTAL_WIDTH = 70
def analyze
show_pat_expires_at_migration_status
show_most_common_pat_expiration_dates
end
def edit
loop do
analyze
break unless prompt_action
end
end
private
def show_pat_expires_at_migration_status
sql = <<~SQL
SELECT * FROM batched_background_migrations
WHERE job_class_name = 'CleanupPersonalAccessTokensWithNilExpiresAt'
AND table_name = 'personal_access_tokens'
AND column_name = 'id'
SQL
print_header("Personal/Project/Group Access Token Expiration Migration")
base_model = ::Gitlab::Database.database_base_models[::Gitlab::Database::MAIN_DATABASE_NAME]
record = base_model.connection.select_one(sql)
if record
puts "Started at: #{record['started_at']}"
puts "Finished : #{record['finished_at']}"
else
puts "Status: Not run"
end
end
def show_most_common_pat_expiration_dates
print_header "Top 10 Personal/Project/Group Access Token Expiration Dates"
puts "| Expiration Date | Count |"
puts "|-----------------|-------|"
with_most_common_pat_expiration_dates do |row|
expiry_date = row[:expires_at] || "(none)"
puts "| #{expiry_date.to_s.ljust(15)} | #{row[:count].to_s.ljust(5)} |"
end
print_footer
end
def with_most_common_pat_expiration_dates
# rubocop:disable CodeReuse/ActiveRecord -- Rake task specifically for fixing an issue
# rubocop:disable Style/ExplicitBlockArgument -- This is a backport, the cop is removed 16.6 onwards
ApplicationRecord.with_fast_read_statement_timeout(0) do # rubocop: disable Performance/ActiveRecordSubtransactionMethods -- no subtransaction here
PersonalAccessToken
.select(:expires_at, Arel.sql('count(*)'))
.group(:expires_at)
.order(Arel.sql('count(*) DESC'))
.order(expires_at: :desc)
.limit(10)
.each do |row|
yield row
end
end
# rubocop:enable CodeReuse/ActiveRecord
# rubocop:enable Style/ExplicitBlockArgument
end
def prompt_action
prompt = TTY::Prompt.new
puts ""
user_choice = prompt.select("What do you want to do?") do |menu|
menu.enum "."
menu.choice "Extend expiration date", 1
menu.choice "Remove expiration date", 2
menu.choice "Quit", 3
end
case user_choice
when 1
extend_expiration_date
true
when 2
remove_expiration_date
true
when 3
false
end
end
def extend_expiration_date
old_date = prompt_expiration_date_selection
return unless old_date
prompt = TTY::Prompt.new
num_days = ::Gitlab::CurrentSettings.max_personal_access_token_lifetime || 365
new_date = old_date + num_days.days
new_date = prompt.ask("What would you like the new expiration date to be?", default: new_date)
new_date = Date.parse(new_date) unless new_date.is_a?(Date)
puts ""
puts "Old expiration date: #{old_date}"
puts "New expiration date: #{new_date}"
confirmed = prompt.yes?(
"WARNING: This will now update #{token_count(old_date)} token(s). Are you sure?",
default: false
)
if confirmed
puts "Updating tokens..."
update_tokens_with_expiration(old_date, new_date)
else
puts "Aborting!"
end
rescue Date::Error
puts "Invalid date, aborting..."
end
def remove_expiration_date
old_date = prompt_expiration_date_selection
return unless old_date
prompt = TTY::Prompt.new
puts ""
puts "WARNING: This will remove the expiration for tokens that expire on #{old_date}."
confirmed = prompt.yes?("This will affect #{token_count(old_date)} tokens. Are you sure?", default: false)
if confirmed
update_tokens_with_expiration(old_date, nil)
else
puts "Aborting!"
end
end
def update_tokens_with_expiration(old_date, new_date)
total = 0
# rubocop:disable CodeReuse/ActiveRecord -- Rake task specifically for fixing an issue
PersonalAccessToken.where(expires_at: old_date).each_batch do |batch|
puts "Updating personal access tokens from ID #{batch.minimum(:id)} to #{batch.maximum(:id)}..."
total += batch.update_all(expires_at: new_date)
end
# rubocop:enable CodeReuse/ActiveRecord -- Rake task specifically for fixing an issue
puts "Updated #{total} tokens!"
end
def prompt_expiration_date_selection
prompt = TTY::Prompt.new
choices = []
with_most_common_pat_expiration_dates do |row|
choices << row[:expires_at] if row[:expires_at]
end
abort_choice = "--> Abort"
choices << abort_choice
selection = prompt.select("Select an expiration date", choices)
selection == abort_choice ? nil : selection
end
def token_count(expiration_date)
# rubocop:disable CodeReuse/ActiveRecord -- Rake task specifically for fixing an issue
ApplicationRecord.with_fast_read_statement_timeout(0) do # rubocop: disable Performance/ActiveRecordSubtransactionMethods -- no subtransaction here
PersonalAccessToken.where(expires_at: expiration_date).count
end
# rubocop:enable CodeReuse/ActiveRecord
end
def print_header(title, total_width = TOTAL_WIDTH)
title_length = title.length
side_length = (total_width - title_length) / 2
left_side = "=" * side_length
right_side = "=" * (side_length + (total_width % 2))
header = "#{left_side} #{title} #{right_side}"
puts header
end
def print_footer
# Account for the spaces between the "=" in the header
puts "=" * (TOTAL_WIDTH + 2)
end
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
require_relative '../../../../lib/tasks/gitlab/tokens/manage_expiry_task'
RSpec.describe 'Tasks::Gitlab::Tokens::ManageExpiryTask', feature_category: :system_access do
# rubocop:disable RSpec/AvoidTestProf -- this is not a migration spec
let_it_be(:expires_at) { Date.today + 364.days }
let_it_be(:user) { create(:user) }
let_it_be(:migration_status) do
create(:batched_background_migration, :finished,
job_class_name: 'CleanupPersonalAccessTokensWithNilExpiresAt',
table_name: 'personal_access_tokens',
column_name: 'id')
end
# rubocop:enable RSpec/AvoidTestProf
let!(:personal_access_token1) { create(:personal_access_token, user: user, expires_at: expires_at) }
let!(:personal_access_token2) { create(:personal_access_token, user: user, expires_at: expires_at) }
let!(:personal_access_token3) { create(:personal_access_token, user: user, expires_at: expires_at + 1.day) }
subject(:task) { Tasks::Gitlab::Tokens::ManageExpiryTask.new }
describe '.analyze' do
it 'calls the expected methods' do
expect(task).to receive(:show_pat_expires_at_migration_status)
expect(task).to receive(:show_most_common_pat_expiration_dates)
task.analyze
end
end
describe '.edit' do
it 'calls analyze and prompts for action' do
expect(task).to receive(:analyze).at_least(:once)
expect(task).to receive(:prompt_action).at_least(:once).and_return(false)
task.edit
end
end
describe '.show_pat_expires_at_migration_status' do
it 'prints the migration status' do
expect { task.send(:show_pat_expires_at_migration_status) }.to output(
/Started at: #{migration_status[:started_at]}\nFinished : #{migration_status[:finished_at]}/).to_stdout
end
end
describe '.show_most_common_pat_expiration_dates' do
let(:second) { personal_access_token3.expires_at }
it 'shows the two groups of expiration dates' do
expect { task.send(:show_most_common_pat_expiration_dates) }.to output(
/#{expires_at}.*\|\s+2\s+\|\n\|\s+#{second}\s+\|\s+1\s+/).to_stdout
end
end
describe '.extend_expiration_date' do
it 'extends the expiration date for selected tokens' do
new_date = expires_at + 1.day
prompt = instance_double(TTY::Prompt)
allow(task).to receive(:prompt_expiration_date_selection).and_return(expires_at)
allow(TTY::Prompt).to receive(:new).and_return(prompt)
allow(prompt).to receive(:ask).and_return(new_date.to_s)
allow(prompt).to receive(:yes?).and_return(true)
expect(task).to receive(:update_tokens_with_expiration).with(expires_at, new_date).and_call_original
expect { task.send(:extend_expiration_date) }.to output(/Updated 2 tokens!/).to_stdout
expect(personal_access_token1.reload.expires_at).to eq(new_date)
expect(personal_access_token2.reload.expires_at).to eq(new_date)
end
end
describe '.remove_expiration_date' do
it 'removes the expiration date for selected tokens' do
prompt = instance_double(TTY::Prompt)
allow(task).to receive(:prompt_expiration_date_selection).and_return(expires_at)
allow(TTY::Prompt).to receive(:new).and_return(prompt)
allow(prompt).to receive(:yes?).and_return(true)
expect(task).to receive(:update_tokens_with_expiration).with(expires_at, nil).and_call_original
expect { task.send(:remove_expiration_date) }.to output(/Updated 2 tokens!/).to_stdout
expect(personal_access_token1.reload.expires_at).to be_nil
expect(personal_access_token2.reload.expires_at).to be_nil
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment