From 3c33724e2e182436a2d8b44ef71d0bdac37c585b Mon Sep 17 00:00:00 2001 From: Vladimir Shushlin Date: Thu, 16 May 2019 09:32:25 +0000 Subject: Add Let's Encrypt client Part of adding Let's Encrypt certificates for pages domains Add acme-client gem Client is being initialized by private key stored in secrets.yml Let's Encrypt account is being created lazily. If it's already created, Acme::Client just gets account_kid by calling new_account method Make Let's Encrypt client an instance Wrap order and challenge classes --- spec/lib/gitlab/lets_encrypt/challenge_spec.rb | 29 ++++++ spec/lib/gitlab/lets_encrypt/client_spec.rb | 120 +++++++++++++++++++++++++ spec/lib/gitlab/lets_encrypt/order_spec.rb | 39 ++++++++ 3 files changed, 188 insertions(+) create mode 100644 spec/lib/gitlab/lets_encrypt/challenge_spec.rb create mode 100644 spec/lib/gitlab/lets_encrypt/client_spec.rb create mode 100644 spec/lib/gitlab/lets_encrypt/order_spec.rb (limited to 'spec/lib/gitlab/lets_encrypt') diff --git a/spec/lib/gitlab/lets_encrypt/challenge_spec.rb b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb new file mode 100644 index 00000000000..74622f356de --- /dev/null +++ b/spec/lib/gitlab/lets_encrypt/challenge_spec.rb @@ -0,0 +1,29 @@ +# 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 diff --git a/spec/lib/gitlab/lets_encrypt/client_spec.rb b/spec/lib/gitlab/lets_encrypt/client_spec.rb new file mode 100644 index 00000000000..16a16acfd25 --- /dev/null +++ b/spec/lib/gitlab/lets_encrypt/client_spec.rb @@ -0,0 +1,120 @@ +# 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 diff --git a/spec/lib/gitlab/lets_encrypt/order_spec.rb b/spec/lib/gitlab/lets_encrypt/order_spec.rb new file mode 100644 index 00000000000..ee7058baf8d --- /dev/null +++ b/spec/lib/gitlab/lets_encrypt/order_spec.rb @@ -0,0 +1,39 @@ +# 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 -- cgit v1.2.3