Skip to content
Snippets Groups Projects
Verified Commit 733e8f64 authored by Tan Le's avatar Tan Le :two:
Browse files

Get authentication secret via secure volume mount

parent e40d2f44
No related branches found
No related tags found
1 merge request!103881Add internal API to create access token for suggested reviewers
......@@ -73,6 +73,7 @@ eslint-report.html
/.gitlab_workhorse_secret
/.gitlab_pages_secret
/.gitlab_kas_secret
/.gitlab_suggested_reviewers_secret
/webpack-report/
/crystalball/
/test_results/
......
......@@ -1258,6 +1258,11 @@ production: &base
# The URL to the Kubernetes API proxy (used by GitLab users)
# external_k8s_proxy_url: https://localhost:8154 # default: nil
gitlab_suggested_reviewers:
# File that contains the secret key for verifying access for gitlab-suggested-reviewers.
# Default is '.gitlab_suggested_reviewers_secret' relative to Rails.root (i.e. root of the GitLab app).
# secret_file: /home/git/gitlab/.gitlab_suggested_reviewers_secret
## GitLab Elasticsearch settings
elasticsearch:
indexer_path: /home/git/gitlab-elasticsearch-indexer/
......
......@@ -852,6 +852,12 @@
Settings.gitlab_kas['internal_url'] ||= 'grpc://localhost:8153'
# Settings.gitlab_kas['external_k8s_proxy_url'] ||= 'grpc://localhost:8154' # NOTE: Do not set a default until all distributions have been updated with a correct value
#
# GitLab Suggested Reviewers
#
Settings['gitlab_suggested_reviewers'] ||= Settingslogic.new({})
Settings.gitlab_suggested_reviewers['secret_file'] ||= Rails.root.join('.gitlab_suggested_reviewers_secret')
#
# Repositories
#
......
# frozen_string_literal: true
return unless Gitlab.com? && Rails.env.production?
Gitlab::AppliedMl::SuggestedReviewers.ensure_secret!
......@@ -5,7 +5,6 @@ module API
module Internal
class SuggestedReviewers < ::API::Base
feature_category :workflow_automation
urgency :low
before do
check_feature_enabled
......
......@@ -5,10 +5,8 @@ module AppliedMl
module SuggestedReviewers
INTERNAL_API_REQUEST_HEADER = 'Gitlab-Sugggested-Reviewers-Api-Request'
JWT_ISSUER = 'gitlab-suggested-reviewers'
SECRET_NAME = 'GITLAB_SUGGESTED_REVIEWERS_API_SECRET'
SECRET_LENGTH = 64
EXPIRATION = 5.minutes
include Gitlab::Utils::StrongMemoize
include JwtAuthenticatable
class << self
......@@ -16,30 +14,23 @@ def verify_api_request(request_headers)
token = request_headers[INTERNAL_API_REQUEST_HEADER]
return unless token
decode_jwt(token, issuer: JWT_ISSUER)
decode_jwt(
token,
issuer: JWT_ISSUER,
iat_after: Time.current - EXPIRATION
)
rescue JWT::DecodeError
nil
end
# rubocop:disable Gitlab/StrongMemoizeAttr
def secret
strong_memoize(:secret) do
ENV.fetch(SECRET_NAME)
end
def secret_path
Gitlab.config.gitlab_suggested_reviewers.secret_file
end
# rubocop:enable Gitlab/StrongMemoizeAttr
def ensure_secret!
secret = ENV[SECRET_NAME]
return if File.exist?(secret_path)
raise Gitlab::AppliedMl::Errors::ConfigurationError, "Variable #{SECRET_NAME} is missing" if secret.blank?
if secret.length != SECRET_LENGTH
raise Gitlab::AppliedMl::Errors::ConfigurationError,
"Secret must contain #{SECRET_LENGTH} bytes"
end
secret
write_secret
end
end
end
......
......@@ -10,7 +10,8 @@
end
describe '.verify_api_request' do
let(:payload) { { 'iss' => described_class::JWT_ISSUER } }
let(:iat) { 1.minute.ago.to_i }
let(:payload) { { 'iss' => described_class::JWT_ISSUER, 'iat' => iat } }
subject(:decoded_token) do
described_class.verify_api_request(headers)
......@@ -37,44 +38,38 @@
{ described_class::INTERNAL_API_REQUEST_HEADER => encoded_token }
end
it { is_expected.to match_array([{ "iss" => described_class::JWT_ISSUER }, { "alg" => "HS256" }]) }
it { is_expected.to match_array([{ 'iss' => described_class::JWT_ISSUER, 'iat' => iat }, { 'alg' => 'HS256' }]) }
end
end
describe '.ensure_secret!' do
context 'when environment value is not set' do
before do
stub_env(described_class::SECRET_NAME, nil)
end
it 'raises an error' do
expect { described_class.ensure_secret! }.to raise_error(
Gitlab::AppliedMl::Errors::ConfigurationError,
'Variable GITLAB_SUGGESTED_REVIEWERS_API_SECRET is missing'
)
end
describe '.secret_path' do
it 'returns default gitlab config' do
expect(described_class.secret_path).to eq(Gitlab.config.gitlab_suggested_reviewers.secret_file)
end
end
context 'when secret is not correct length' do
describe '.ensure_secret!' do
context 'when secret file exists' do
before do
stub_env(described_class::SECRET_NAME, 'abcd1234')
allow(File).to receive(:exist?).with(Gitlab.config.gitlab_suggested_reviewers.secret_file).and_return(true)
end
it 'raises an error' do
expect { described_class.ensure_secret! }.to raise_error(
Gitlab::AppliedMl::Errors::ConfigurationError,
"Secret must contain #{described_class::SECRET_LENGTH} bytes"
)
it 'does not call write_secret' do
expect(described_class).not_to receive(:write_secret)
described_class.ensure_secret!
end
end
context 'when secret is valid' do
context 'when secret file does not exist' do
before do
stub_env(described_class::SECRET_NAME, secret)
allow(File).to receive(:exist?).with(Gitlab.config.gitlab_suggested_reviewers.secret_file).and_return(false)
end
it 'returns the secret' do
expect(described_class.ensure_secret!).to eq(secret)
it 'calls write_secret' do
expect(described_class).to receive(:write_secret)
described_class.ensure_secret!
end
end
end
......
......@@ -18,7 +18,7 @@
end
before do
stub_env(Gitlab::AppliedMl::SuggestedReviewers::SECRET_NAME, secret)
allow(Gitlab::AppliedMl::SuggestedReviewers).to receive(:secret).and_return(secret)
end
context 'when feature flag is disabled' do
......@@ -49,7 +49,10 @@
context 'when authentication header is set' do
let(:headers) do
jwt_token = JWT.encode({ 'iss' => Gitlab::AppliedMl::SuggestedReviewers::JWT_ISSUER }, secret, 'HS256')
jwt_token = JWT.encode(
{ 'iss' => Gitlab::AppliedMl::SuggestedReviewers::JWT_ISSUER, 'iat' => 1.minute.ago.to_i },
secret, 'HS256'
)
{ Gitlab::AppliedMl::SuggestedReviewers::INTERNAL_API_REQUEST_HEADER => jwt_token }
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