Commit b70403cf authored by Ash McKenzie's avatar Ash McKenzie

Merge branch '9104-authenticate-with-ci_job_token-to-npm-registry' into 'master'

Authenticate with CI_JOB_TOKEN to NPM registry

Closes #9104

See merge request gitlab-org/gitlab!19059
parents d78caac0 153664b6
...@@ -108,6 +108,21 @@ Then, you could run `npm publish` either locally or via GitLab CI/CD: ...@@ -108,6 +108,21 @@ Then, you could run `npm publish` either locally or via GitLab CI/CD:
- **GitLab CI/CD:** Set an `NPM_TOKEN` [variable](../../../ci/variables/README.md) - **GitLab CI/CD:** Set an `NPM_TOKEN` [variable](../../../ci/variables/README.md)
under your project's **Settings > CI/CD > Variables**. under your project's **Settings > CI/CD > Variables**.
### Authenticating with a CI job token
> [Introduced](https://gitlab.com/gitlab-org/gitlab/issues/9104) in GitLab Premium 12.5.
If you’re using NPM with GitLab CI/CD, a CI job token can be used instead of a personal access token.
The token will inherit the permissions of the user that generates the pipeline.
Add a corresponding section to your `.npmrc` file:
```ini
@foo:registry=https://gitlab.com/api/v4/packages/npm/
//gitlab.com/api/v4/packages/npm/:_authToken=${env.CI_JOB_TOKEN}
//gitlab.com/api/v4/projects/{env.CI_PROJECT_ID>/packages/npm/:_authToken=${env.CI_JOB_TOKEN}
```
## Uploading packages ## Uploading packages
......
---
title: CI_JOB_TOKEN can be accepted with 'Bearer ' prefix to allow for NPM registry
usage
merge_request: 19059
author:
type: added
...@@ -77,6 +77,7 @@ module API ...@@ -77,6 +77,7 @@ module API
requires :package_name, type: String, desc: 'Package name' requires :package_name, type: String, desc: 'Package name'
requires :versions, type: Hash, desc: 'Package version info' requires :versions, type: Hash, desc: 'Package version info'
end end
route_setting :authentication, job_token_allowed: true
put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
authorize_create_package! authorize_create_package!
......
...@@ -8,7 +8,7 @@ module EE ...@@ -8,7 +8,7 @@ module EE
override :find_user_from_sources override :find_user_from_sources
def find_user_from_sources def find_user_from_sources
find_user_from_access_token || find_user_from_bearer_token ||
find_user_from_job_token || find_user_from_job_token ||
find_user_from_warden find_user_from_warden
end end
......
...@@ -10,6 +10,11 @@ module EE ...@@ -10,6 +10,11 @@ module EE
JOB_TOKEN_HEADER = "HTTP_JOB_TOKEN".freeze JOB_TOKEN_HEADER = "HTTP_JOB_TOKEN".freeze
JOB_TOKEN_PARAM = :job_token JOB_TOKEN_PARAM = :job_token
def find_user_from_bearer_token
find_user_from_job_bearer_token ||
find_user_from_access_token
end
def find_user_from_job_token def find_user_from_job_token
return unless route_authentication_setting[:job_token_allowed] return unless route_authentication_setting[:job_token_allowed]
...@@ -31,9 +36,31 @@ module EE ...@@ -31,9 +36,31 @@ module EE
super super
end end
override :validate_access_token!
def validate_access_token!(scopes: [])
# return early if we've already authenticated via a job token
@job_token_authentication.present? || super # rubocop:disable Gitlab/ModuleWithInstanceVariables
end
def scim_request? def scim_request?
current_request.path.starts_with?("/api/scim/") current_request.path.starts_with?("/api/scim/")
end end
private
def find_user_from_job_bearer_token
return unless route_authentication_setting[:job_token_allowed]
token = parsed_oauth_token
return unless token
job = ::Ci::Build.find_by_token(token)
return unless job
@job_token_authentication = true # rubocop:disable Gitlab/ModuleWithInstanceVariables
job.user
end
end end
end end
end end
......
...@@ -18,26 +18,97 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -18,26 +18,97 @@ describe Gitlab::Auth::UserAuthFinders do
request.update_param(key, value) request.update_param(key, value)
end end
describe '#find_user_from_job_token' do shared_examples 'find user from job token' do
context 'when route is allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: true } }
it "returns an Unauthorized exception for an invalid token" do
set_token('invalid token')
expect { subject }.to raise_error(Gitlab::Auth::UnauthorizedError)
end
it "return user if token is valid" do
set_token(job.token)
expect(subject).to eq(user)
end
end
end
describe '#validate_access_token!' do
subject { validate_access_token! }
context 'with a job token' do
let(:route_authentication_setting) { { job_token_allowed: true } }
let(:job) { create(:ci_build, user: user) }
before do
env['HTTP_AUTHORIZATION'] = "Bearer #{job.token}"
find_user_from_bearer_token
end
it 'does not raise an error' do
expect { subject }.not_to raise_error
end
end
context 'without a job token' do
let(:personal_access_token) { create(:personal_access_token, user: user) }
before do
personal_access_token.revoke!
allow_any_instance_of(described_class).to receive(:access_token).and_return(personal_access_token)
end
it 'delegates the logic to super' do
expect { subject }.to raise_error(Gitlab::Auth::RevokedError)
end
end
end
describe '#find_user_from_bearer_token' do
let(:job) { create(:ci_build, user: user) } let(:job) { create(:ci_build, user: user) }
subject { find_user_from_bearer_token }
context 'when the token is passed as an oauth token' do
def set_token(token)
env['HTTP_AUTHORIZATION'] = "Bearer #{token}"
end
shared_examples 'find user from job token' do context 'with a job token' do
context 'when route is allowed to be authenticated' do it_behaves_like 'find user from job token'
let(:route_authentication_setting) { { job_token_allowed: true } } end
it "returns an Unauthorized exception for an invalid token" do context 'with oauth token' do
set_token('invalid token') let(:application) { Doorkeeper::Application.create!(name: 'MyApp', redirect_uri: 'https://app.com', owner: user) }
let(:token) { Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: 'api').token }
expect { find_user_from_job_token }.to raise_error(Gitlab::Auth::UnauthorizedError) before do
set_token(token)
end end
it "return user if token is valid" do it { is_expected.to eq user }
set_token(job.token) end
end
expect(find_user_from_job_token).to eq(user) context 'with a personal access token' do
end let(:pat) { create(:personal_access_token, user: user) }
let(:token) { pat.token }
before do
env[Gitlab::Auth::UserAuthFinders::PRIVATE_TOKEN_HEADER] = pat.token
end end
it { is_expected.to eq user }
end
end
describe '#find_user_from_job_token' do
let(:job) { create(:ci_build, user: user) }
subject { find_user_from_job_token }
shared_examples 'job token disabled' do
context 'when route is not allowed to be authenticated' do context 'when route is not allowed to be authenticated' do
let(:route_authentication_setting) { { job_token_allowed: false } } let(:route_authentication_setting) { { job_token_allowed: false } }
...@@ -45,7 +116,7 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -45,7 +116,7 @@ describe Gitlab::Auth::UserAuthFinders do
set_token(job.token) set_token(job.token)
allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true) allow_any_instance_of(Gitlab::UserAccess).to receive(:allowed?).and_return(true)
expect(find_user_from_job_token).to be_nil expect(subject).to be_nil
end end
end end
end end
...@@ -56,6 +127,7 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -56,6 +127,7 @@ describe Gitlab::Auth::UserAuthFinders do
end end
it_behaves_like 'find user from job token' it_behaves_like 'find user from job token'
it_behaves_like 'job token disabled'
end end
context 'when the job token is in the params' do context 'when the job token is in the params' do
...@@ -64,6 +136,7 @@ describe Gitlab::Auth::UserAuthFinders do ...@@ -64,6 +136,7 @@ describe Gitlab::Auth::UserAuthFinders do
end end
it_behaves_like 'find user from job token' it_behaves_like 'find user from job token'
it_behaves_like 'job token disabled'
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment