Add skeleton Pages internal API

Basic `/internal/pages` endpoint that will be used for Pages virtual
domains internal API. The endpoint is currently behind feature flag and
provides authetication similar to how Workhorse is authenticating with
the GitLab.
This commit is contained in:
Krasimir Angelov 2019-09-06 16:06:25 +12:00
parent 9d38778f41
commit 477ba2b346
8 changed files with 132 additions and 1 deletions

1
.gitignore vendored
View file

@ -65,6 +65,7 @@ eslint-report.html
/vendor/gitaly-ruby
/builds*
/.gitlab_workhorse_secret
/.gitlab_pages_shared_secret
/webpack-report/
/knapsack/
/rspec_flaky/

View file

@ -321,6 +321,9 @@ production: &base
# external_https: ["1.1.1.1:443", "[2001::1]:443"] # If defined, enables custom domain and certificate support in GitLab Pages
admin:
address: unix:/home/git/gitlab/tmp/sockets/private/pages-admin.socket # TCP connections are supported too (e.g. tcp://host:port)
# File that contains the shared secret key for verifying access for gitlab-pages.
# Default is '.gitlab_pages_shared_secret' relative to Rails.root (i.e. root of the GitLab app).
# secret_file: /home/git/gitlab/.gitlab_pages_shared_secret
## Mattermost
## For enabling Add to Mattermost button

View file

@ -292,6 +292,7 @@ Settings.pages['artifacts_server'] ||= Settings.pages['enabled'] if Settings.pa
Settings.pages['admin'] ||= Settingslogic.new({})
Settings.pages.admin['certificate'] ||= ''
Settings.pages['secret_file'] ||= Rails.root.join('.gitlab_pages_shared_secret')
#
# Geo

View file

@ -119,6 +119,7 @@ module API
mount ::API::GroupVariables
mount ::API::ImportGithub
mount ::API::Internal::Base
mount ::API::Internal::Pages
mount ::API::Issues
mount ::API::JobArtifacts
mount ::API::Jobs

27
lib/api/internal/pages.rb Normal file
View file

@ -0,0 +1,27 @@
# frozen_string_literal: true
module API
# Pages Internal API
module Internal
class Pages < Grape::API
before do
not_found! unless Feature.enabled?(:pages_internal_api)
authenticate_gitlab_pages_request!
end
helpers do
def authenticate_gitlab_pages_request!
unauthorized! unless Gitlab::Pages.verify_api_request(headers)
end
end
namespace 'internal' do
namespace 'pages' do
get "/" do
status :ok
end
end
end
end
end
end

View file

@ -1,7 +1,22 @@
# frozen_string_literal: true
module Gitlab
module Pages
class Pages
VERSION = File.read(Rails.root.join("GITLAB_PAGES_VERSION")).strip.freeze
INTERNAL_API_REQUEST_HEADER = 'Gitlab-Pages-Api-Request'.freeze
include JwtAuthenticatable
class << self
def verify_api_request(request_headers)
decode_jwt_for_issuer('gitlab-pages', request_headers[INTERNAL_API_REQUEST_HEADER])
rescue JWT::DecodeError
false
end
def secret_path
Gitlab.config.pages.secret_file
end
end
end
end

View file

@ -0,0 +1,29 @@
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::Pages do
let(:pages_shared_secret) { SecureRandom.random_bytes(Gitlab::Pages::SECRET_LENGTH) }
before do
allow(described_class).to receive(:secret).and_return(pages_shared_secret)
end
describe '.verify_api_request' do
let(:payload) { { 'iss' => 'gitlab-pages' } }
it 'returns false if fails to validate the JWT' do
encoded_token = JWT.encode(payload, 'wrongsecret', 'HS256')
headers = { described_class::INTERNAL_API_REQUEST_HEADER => encoded_token }
expect(described_class.verify_api_request(headers)).to eq(false)
end
it 'returns the decoded JWT' do
encoded_token = JWT.encode(payload, described_class.secret, 'HS256')
headers = { described_class::INTERNAL_API_REQUEST_HEADER => encoded_token }
expect(described_class.verify_api_request(headers)).to eq([{ "iss" => "gitlab-pages" }, { "alg" => "HS256" }])
end
end
end

View file

@ -0,0 +1,54 @@
# frozen_string_literal: true
require 'spec_helper'
describe API::Internal::Pages do
describe "GET /internal/pages" do
let(:pages_shared_secret) { SecureRandom.random_bytes(Gitlab::Pages::SECRET_LENGTH) }
before do
allow(Gitlab::Pages).to receive(:secret).and_return(pages_shared_secret)
end
def query_host(host, headers = {})
get api("/internal/pages"), headers: headers, params: { host: host }
end
context 'feature flag disabled' do
before do
stub_feature_flags(pages_internal_api: false)
end
it 'responds with 404 Not Found' do
query_host('pages.gitlab.io')
expect(response).to have_gitlab_http_status(404)
end
end
context 'feature flag enabled' do
context 'not authenticated' do
it 'responds with 401 Unauthorized' do
query_host('pages.gitlab.io')
expect(response).to have_gitlab_http_status(401)
end
end
context 'authenticated' do
def query_host(host)
jwt_token = JWT.encode({ 'iss' => 'gitlab-pages' }, Gitlab::Pages.secret, 'HS256')
headers = { Gitlab::Pages::INTERNAL_API_REQUEST_HEADER => jwt_token }
super(host, headers)
end
it 'responds with 200 OK' do
query_host('pages.gitlab.io')
expect(response).to have_gitlab_http_status(200)
end
end
end
end
end