Commit 71421729 authored by GitLab Bot's avatar GitLab Bot

Automatic merge of gitlab-org/gitlab-ce master

parents 37d0cbaa 19e0a9a0
......@@ -63,6 +63,8 @@ gem 'u2f', '~> 0.2.1'
# GitLab Pages
gem 'validates_hostname', '~> 1.0.6'
gem 'rubyzip', '~> 1.2.2', require: 'zip'
# GitLab Pages letsencrypt support
gem 'acme-client', '~> 2.0.2'
# Browser detection
gem 'browser', '~> 2.5'
......
......@@ -4,6 +4,8 @@ GEM
RedCloth (4.3.2)
abstract_type (0.0.7)
ace-rails-ap (4.1.2)
acme-client (2.0.2)
faraday (~> 0.9, >= 0.9.1)
actioncable (5.1.7)
actionpack (= 5.1.7)
nio4r (~> 2.0)
......@@ -1031,6 +1033,7 @@ PLATFORMS
DEPENDENCIES
RedCloth (~> 4.3.2)
ace-rails-ap (~> 4.1.0)
acme-client (~> 2.0.2)
activerecord_sane_schema_dumper (= 1.0)
acts-as-taggable-on (~> 6.0)
addressable (~> 2.5.2)
......
......@@ -89,6 +89,13 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
)
end
# Getting ToS url requires `directory` api call to Let's Encrypt
# which could result in 500 error/slow rendering on settings page
# Because of that we use separate controller action
def lets_encrypt_terms_of_service
redirect_to ::Gitlab::LetsEncrypt.terms_of_service_url
end
private
def set_application_setting
......
......@@ -30,8 +30,7 @@
.form-check
= f.check_box :lets_encrypt_terms_of_service_accepted, class: 'form-check-input'
= f.label :lets_encrypt_terms_of_service_accepted, class: 'form-check-label' do
// Terms of Service should actually be a link, but the best way to get the url is using API
// So it will be done in later MR
= _("I have read and agree to the Let's Encrypt Terms of Service")
- terms_of_service_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: lets_encrypt_terms_of_service_admin_application_settings_path }
= _("I have read and agree to the Let's Encrypt %{link_start}Terms of Service%{link_end}").html_safe % { link_start: terms_of_service_link_start, link_end: '</a>'.html_safe }
= f.submit _('Save changes'), class: "btn btn-success"
......@@ -111,6 +111,7 @@ namespace :admin do
put :reset_health_check_token
put :clear_repository_check_states
get :integrations, :repository, :templates, :ci_cd, :reporting, :metrics_and_profiling, :network, :geo, :preferences
get :lets_encrypt_terms_of_service
end
resources :labels
......
# frozen_string_literal: true
module Gitlab
module LetsEncrypt
class Challenge
def initialize(acme_challenge)
@acme_challenge = acme_challenge
end
delegate :url, :token, :file_content, :status, :request_validation, to: :acme_challenge
private
attr_reader :acme_challenge
end
end
end
# frozen_string_literal: true
module Gitlab
module LetsEncrypt
class Client
PRODUCTION_DIRECTORY_URL = 'https://acme-v02.api.letsencrypt.org/directory'
STAGING_DIRECTORY_URL = 'https://acme-staging-v02.api.letsencrypt.org/directory'
def new_order(domain_name)
ensure_account
acme_order = acme_client.new_order(identifiers: [domain_name])
::Gitlab::LetsEncrypt::Order.new(acme_order)
end
def load_order(url)
ensure_account
# rubocop: disable CodeReuse/ActiveRecord
::Gitlab::LetsEncrypt::Order.new(acme_client.order(url: url))
# rubocop: enable CodeReuse/ActiveRecord
end
def load_challenge(url)
ensure_account
::Gitlab::LetsEncrypt::Challenge.new(acme_client.challenge(url: url))
end
def terms_of_service_url
acme_client.terms_of_service
end
def enabled?
return false unless Feature.enabled?(:pages_auto_ssl)
Gitlab::CurrentSettings.lets_encrypt_terms_of_service_accepted
end
private
def acme_client
@acme_client ||= ::Acme::Client.new(private_key: private_key, directory: acme_api_directory_url)
end
def private_key
@private_key ||= OpenSSL::PKey.read(Gitlab::Application.secrets.lets_encrypt_private_key)
end
def admin_email
Gitlab::CurrentSettings.lets_encrypt_notification_email
end
def contact
"mailto:#{admin_email}"
end
def ensure_account
raise 'Acme integration is disabled' unless enabled?
@acme_account ||= acme_client.new_account(contact: contact, terms_of_service_agreed: true)
end
def acme_api_directory_url
if Rails.env.production?
PRODUCTION_DIRECTORY_URL
else
STAGING_DIRECTORY_URL
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module LetsEncrypt
class Order
def initialize(acme_order)
@acme_order = acme_order
end
def new_challenge
authorization = @acme_order.authorizations.first
challenge = authorization.http
::Gitlab::LetsEncrypt::Challenge.new(challenge)
end
delegate :url, :status, to: :acme_order
private
attr_reader :acme_order
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe ::Gitlab::LetsEncrypt::Challenge do
delegated_methods = {
url: 'https://example.com/',
status: 'pending',
token: 'tokenvalue',
file_content: 'hereisfilecontent',
request_validation: true
}
let(:acme_challenge) do
acme_challenge = instance_double('Acme::Client::Resources::Challenge')
allow(acme_challenge).to receive_messages(delegated_methods)
acme_challenge
end
let(:challenge) { described_class.new(acme_challenge) }
delegated_methods.each do |method, value|
describe "##{method}" do
it 'delegates to Acme::Client::Resources::Challenge' do
expect(challenge.public_send(method)).to eq(value)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe ::Gitlab::LetsEncrypt::Client do
include LetsEncryptHelpers
let(:client) { described_class.new }
before do
stub_application_setting(
lets_encrypt_notification_email: 'myemail@test.example.com',
lets_encrypt_terms_of_service_accepted: true
)
end
let!(:stub_client) { stub_lets_encrypt_client }
shared_examples 'ensures account registration' do
it 'ensures account registration' do
subject
expect(stub_client).to have_received(:new_account).with(
contact: 'mailto:myemail@test.example.com',
terms_of_service_agreed: true
)
end
context 'when acme integration is disabled' do
before do
stub_application_setting(lets_encrypt_terms_of_service_accepted: false)
end
it 'raises error' do
expect do
subject
end.to raise_error('Acme integration is disabled')
end
end
end
describe '#new_order' do
subject(:new_order) { client.new_order('example.com') }
before do
order_double = instance_double('Acme::Order')
allow(stub_client).to receive(:new_order).and_return(order_double)
end
include_examples 'ensures account registration'
it 'returns order' do
is_expected.to be_a(::Gitlab::LetsEncrypt::Order)
end
end
describe '#load_order' do
let(:url) { 'https://example.com/order' }
subject { client.load_order(url) }
before do
acme_order = instance_double('Acme::Client::Resources::Order')
allow(stub_client).to receive(:order).with(url: url).and_return(acme_order)
end
include_examples 'ensures account registration'
it 'loads order' do
is_expected.to be_a(::Gitlab::LetsEncrypt::Order)
end
end
describe '#load_challenge' do
let(:url) { 'https://example.com/challenge' }
subject { client.load_challenge(url) }
before do
acme_challenge = instance_double('Acme::Client::Resources::Challenge')
allow(stub_client).to receive(:challenge).with(url: url).and_return(acme_challenge)
end
include_examples 'ensures account registration'
it 'loads challenge' do
is_expected.to be_a(::Gitlab::LetsEncrypt::Challenge)
end
end
describe '#enabled?' do
subject { client.enabled? }
context 'when terms of service are accepted' do
it { is_expected.to eq(true) }
context 'when feature flag is disabled' do
before do
stub_feature_flags(pages_auto_ssl: false)
end
it { is_expected.to eq(false) }
end
end
context 'when terms of service are not accepted' do
before do
stub_application_setting(lets_encrypt_terms_of_service_accepted: false)
end
it { is_expected.to eq(false) }
end
end
describe '#terms_of_service_url' do
subject { client.terms_of_service_url }
it 'returns valid url' do
is_expected.to eq("https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf")
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe ::Gitlab::LetsEncrypt::Order do
delegated_methods = {
url: 'https://example.com/',
status: 'valid'
}
let(:acme_order) do
acme_order = instance_double('Acme::Client::Resources::Order')
allow(acme_order).to receive_messages(delegated_methods)
acme_order
end
let(:order) { described_class.new(acme_order) }
delegated_methods.each do |method, value|
describe "##{method}" do
it 'delegates to Acme::Client::Resources::Order' do
expect(order.public_send(method)).to eq(value)
end
end
end
describe '#new_challenge' do
before do
challenge = instance_double('Acme::Client::Resources::Challenges::HTTP01')
authorization = instance_double('Acme::Client::Resources::Authorization')
allow(authorization).to receive(:http).and_return(challenge)
allow(acme_order).to receive(:authorizations).and_return([authorization])
end
it 'returns challenge' do
expect(order.new_challenge).to be_a(::Gitlab::LetsEncrypt::Challenge)
end
end
end
# frozen_string_literal: true
module LetsEncryptHelpers
def stub_lets_encrypt_client
client = instance_double('Acme::Client')
allow(client).to receive(:new_account)
allow(client).to receive(:terms_of_service).and_return(
"https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf"
)
allow(Acme::Client).to receive(:new).with(
private_key: kind_of(OpenSSL::PKey::RSA),
directory: ::Gitlab::LetsEncrypt::Client::STAGING_DIRECTORY_URL
).and_return(client)
client
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