Skip to content
Snippets Groups Projects
Commit 0b81b5ac authored by Z.J. van de Weg's avatar Z.J. van de Weg
Browse files

Create read_registry scope with JWT auth

This is the first commit doing mainly 3 things:
1. create a new scope and allow users to use it
2. Have the JWTController respond correctly on this
3. Updates documentation to suggest usage of PATs

There is one gotcha, there will be no support for impersonation tokens, as this
seems not needed.

Fixes gitlab-org/gitlab-ce#19219
parent a8901ce6
No related branches found
No related tags found
3 merge requests!14773Maxraab master patch 51809,!12073Add RC2 changes to 9-3-stable,!11845Allow pulling container images using personal access tokens
Pipeline #
Loading
Loading
@@ -20,13 +20,15 @@ class JwtController < ApplicationController
private
 
def authenticate_project_or_user
@authentication_result = Gitlab::Auth::Result.new(nil, nil, :none, Gitlab::Auth.read_authentication_abilities)
@authentication_result = Gitlab::Auth::Result.new(nil, nil, :none, Gitlab::Auth.read_api_abilities)
 
authenticate_with_http_basic do |login, password|
@authentication_result = Gitlab::Auth.find_for_git_client(login, password, project: nil, ip: request.ip)
 
render_unauthorized unless @authentication_result.success? &&
(@authentication_result.actor.nil? || @authentication_result.actor.is_a?(User))
if @authentication_result.failed? ||
(@authentication_result.actor.present? && !@authentication_result.actor.is_a?(User))
render_unauthorized
end
end
rescue Gitlab::Auth::MissingPersonalTokenError
render_missing_personal_token
Loading
Loading
Loading
Loading
@@ -38,7 +38,7 @@ class Profiles::PersonalAccessTokensController < Profiles::ApplicationController
end
 
def set_index_vars
@scopes = Gitlab::Auth::API_SCOPES
@scopes = Gitlab::Auth::AVAILABLE_SCOPES
 
@personal_access_token = finder.build
@inactive_personal_access_tokens = finder(state: 'inactive').execute
Loading
Loading
Loading
Loading
@@ -15,11 +15,10 @@ class PersonalAccessToken < ActiveRecord::Base
scope :without_impersonation, -> { where(impersonation: false) }
 
validates :scopes, presence: true
validate :validate_api_scopes
validate :validate_scopes
 
def revoke!
self.revoked = true
self.save
update!(revoked: true)
end
 
def active?
Loading
Loading
@@ -28,9 +27,9 @@ class PersonalAccessToken < ActiveRecord::Base
 
protected
 
def validate_api_scopes
unless scopes.all? { |scope| Gitlab::Auth::API_SCOPES.include?(scope.to_sym) }
errors.add :scopes, "can only contain API scopes"
def validate_scopes
unless scopes.all? { |scope| Gitlab::Auth::AVAILABLE_SCOPES.include?(scope.to_sym) }
errors.add :scopes, "can only contain available scopes"
end
end
end
---
title: Allow pulling of container images using personal access tokens
merge_request: 11845
author:
Loading
Loading
@@ -106,12 +106,14 @@ Make sure that your GitLab Runner is configured to allow building Docker images
following the [Using Docker Build](../../ci/docker/using_docker_build.md)
and [Using the GitLab Container Registry documentation](../../ci/docker/using_docker_build.md#using-the-gitlab-container-registry).
 
## Limitations
## Using with private projects
 
In order to use a container image from your private project as an `image:` in
your `.gitlab-ci.yml`, you have to follow the
[Using a private Docker Registry][private-docker]
documentation. This workflow will be simplified in the future.
If a project is private, credentials will need to be provided for authorization.
The preferred way to do this, is by using personal access tokens, which can be
created under `/profile/personal_access_tokens`. The minimal scope needed is:
`read_registry`.
This feature was introduced in GitLab 9.3.
 
## Troubleshooting the GitLab Container Registry
 
Loading
Loading
@@ -257,4 +259,3 @@ Once the right permissions were set, the error will go away.
 
[ce-4040]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/4040
[docker-docs]: https://docs.docker.com/engine/userguide/intro/
[private-docker]: https://docs.gitlab.com/runner/configuration/advanced-configuration.html#using-a-private-container-registry
Loading
Loading
@@ -2,6 +2,8 @@ module Gitlab
module Auth
MissingPersonalTokenError = Class.new(StandardError)
 
REGISTRY_SCOPES = [:read_registry].freeze
# Scopes used for GitLab API access
API_SCOPES = [:api, :read_user].freeze
 
Loading
Loading
@@ -11,8 +13,10 @@ module Gitlab
# Default scopes for OAuth applications that don't define their own
DEFAULT_SCOPES = [:api].freeze
 
AVAILABLE_SCOPES = (API_SCOPES + REGISTRY_SCOPES).freeze
# Other available scopes
OPTIONAL_SCOPES = (API_SCOPES + OPENID_SCOPES - DEFAULT_SCOPES).freeze
OPTIONAL_SCOPES = (AVAILABLE_SCOPES + OPENID_SCOPES - DEFAULT_SCOPES).freeze
 
class << self
def find_for_git_client(login, password, project:, ip:)
Loading
Loading
@@ -26,8 +30,8 @@ module Gitlab
build_access_token_check(login, password) ||
lfs_token_check(login, password) ||
oauth_access_token_check(login, password) ||
user_with_password_for_git(login, password) ||
personal_access_token_check(password) ||
user_with_password_for_git(login, password) ||
Gitlab::Auth::Result.new
 
rate_limit!(ip, success: result.success?, login: login)
Loading
Loading
@@ -103,15 +107,16 @@ module Gitlab
 
raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled?
 
Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_api_abilities)
end
 
def oauth_access_token_check(login, password)
if login == "oauth2" && password.present?
token = Doorkeeper::AccessToken.by_token(password)
if valid_oauth_token?(token)
user = User.find_by(id: token.resource_owner_id)
Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities)
Gitlab::Auth::Result.new(user, nil, :oauth, full_api_abilities)
end
end
end
Loading
Loading
@@ -121,17 +126,26 @@ module Gitlab
 
token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
 
if token && valid_api_token?(token)
Gitlab::Auth::Result.new(token.user, nil, :personal_token, full_authentication_abilities)
if token && valid_scoped_token?(token, scopes: AVAILABLE_SCOPES.map(&:to_s))
Gitlab::Auth::Result.new(token.user, nil, :personal_token, abilities_for_scope(token.scopes))
end
end
 
def valid_oauth_token?(token)
token && token.accessible? && valid_api_token?(token)
token && token.accessible? && valid_scoped_token?(token)
end
 
def valid_api_token?(token)
AccessTokenValidationService.new(token).include_any_scope?(['api'])
def valid_scoped_token?(token, scopes: %w[api])
AccessTokenValidationService.new(token).include_any_scope?(scopes)
end
def abilities_for_scope(scopes)
abilities = Set.new
abilities.merge(full_api_abilities) if scopes.include?("api")
abilities << :read_container_image if scopes.include?("read_registry")
abilities.to_a
end
 
def lfs_token_check(login, password)
Loading
Loading
@@ -150,9 +164,9 @@ module Gitlab
 
authentication_abilities =
if token_handler.user?
full_authentication_abilities
full_api_abilities
else
read_authentication_abilities
read_api_abilities
end
 
if Devise.secure_compare(token_handler.token, password)
Loading
Loading
@@ -188,7 +202,7 @@ module Gitlab
]
end
 
def read_authentication_abilities
def read_api_abilities
[
:read_project,
:download_code,
Loading
Loading
@@ -196,8 +210,8 @@ module Gitlab
]
end
 
def full_authentication_abilities
read_authentication_abilities + [
def full_api_abilities
read_api_abilities + [
:push_code,
:create_container_image
]
Loading
Loading
Loading
Loading
@@ -15,6 +15,10 @@ module Gitlab
def success?
actor.present? || type == :ci
end
def failed?
!success?
end
end
end
end
Loading
Loading
@@ -143,6 +143,13 @@ describe Gitlab::Auth, lib: true do
expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, full_authentication_abilities))
end
 
it 'succeeds for personal access tokens with the `read_registry` scope' do
personal_access_token = create(:personal_access_token, scopes: ['read_registry'])
expect(gl_auth).to receive(:rate_limit!).with('ip', success: true, login: '')
expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip')).to eq(Gitlab::Auth::Result.new(personal_access_token.user, nil, :personal_token, [:read_container_image]))
end
it 'succeeds if it is an impersonation token' do
impersonation_token = create(:personal_access_token, :impersonation, scopes: ['api'])
 
Loading
Loading
Loading
Loading
@@ -35,6 +35,16 @@ describe PersonalAccessToken, models: true do
end
end
 
describe 'revoke!' do
let(:active_personal_access_token) { create(:personal_access_token) }
it 'revokes the token' do
active_personal_access_token.revoke!
expect(active_personal_access_token.revoked?).to be true
end
end
context "validations" do
let(:personal_access_token) { build(:personal_access_token) }
 
Loading
Loading
@@ -51,11 +61,17 @@ describe PersonalAccessToken, models: true do
expect(personal_access_token).to be_valid
end
 
it "rejects creating a token with non-API scopes" do
it "allows creating a token with read_registry scope" do
personal_access_token.scopes = [:read_registry]
expect(personal_access_token).to be_valid
end
it "rejects creating a token with unavailable scopes" do
personal_access_token.scopes = [:openid, :api]
 
expect(personal_access_token).not_to be_valid
expect(personal_access_token.errors[:scopes].first).to eq "can only contain API scopes"
expect(personal_access_token.errors[:scopes].first).to eq "can only contain available scopes"
end
end
end
Loading
Loading
@@ -41,6 +41,19 @@ describe JwtController do
 
it { expect(response).to have_http_status(401) }
end
context 'using personal access tokens' do
let(:user) { create(:user) }
let(:pat) { create(:personal_access_token, user: user, scopes: ['read_registry']) }
let(:headers) { { authorization: credentials('personal_access_token', pat.token) } }
subject! { get '/jwt/auth', parameters, headers }
it 'authenticates correctly' do
expect(response).to have_http_status(200)
expect(service_class).to have_received(:new).with(nil, user, parameters)
end
end
end
 
context 'using User login' do
Loading
Loading
@@ -89,7 +102,7 @@ describe JwtController do
end
 
it 'allows read access' do
expect(service).to receive(:execute).with(authentication_abilities: Gitlab::Auth.read_authentication_abilities)
expect(service).to receive(:execute).with(authentication_abilities: Gitlab::Auth.read_api_abilities)
 
get '/jwt/auth', parameters
end
Loading
Loading
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