summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorVladimir Shushlin <v.shushlin@gmail.com>2019-04-12 18:54:44 +0300
committerVladimir Shushlin <v.shushlin@gmail.com>2019-04-12 19:04:03 +0300
commit4c5a9cf3158939c1f645454887dda04cb0edfa8f (patch)
tree8f3241c1488a2e25188243db05eed3e5f772c1a3
parentd3bb4af50381451f623e01719e4387f14ff3912c (diff)
downloadgitlab-ce-acme-client.tar.gz
-rw-r--r--lib/gitlab/acme.rb69
-rw-r--r--spec/lib/gitlab/acme_spec.rb39
-rw-r--r--spec/support/helpers/acme_helpers.rb34
3 files changed, 142 insertions, 0 deletions
diff --git a/lib/gitlab/acme.rb b/lib/gitlab/acme.rb
new file mode 100644
index 00000000000..b682aeaec38
--- /dev/null
+++ b/lib/gitlab/acme.rb
@@ -0,0 +1,69 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Acme
+ PRODUCTION_DIRECTORY_URL = 'https://acme-v02.api.letsencrypt.org/directory'
+ STAGING_DIRECTORY_URL = 'https://acme-staging-v02.api.letsencrypt.org/directory'
+
+ class << self
+ def client
+ raise 'Acme integration is disabled' unless acme_integration_enabled?
+
+ acme_client = ::Acme::Client.new(private_key: private_key,
+ directory: directory,
+ kid: acme_account_kid)
+
+ # account wasn't yet registered in Let's Encrypt
+ # if it was calling new_account will just return the same id
+ # we save kid to avoid making new_account call every time
+ unless acme_account_kid
+ binding.pry
+ account = acme_client.new_account(contact: contact, terms_of_service_agreed: true)
+ ApplicationSetting.current.update(acme_account_kid: account.kid)
+ end
+
+ acme_client
+ end
+
+ def terms_of_service_url
+ ::Acme::Client.new(directory: directory).terms_of_service
+ end
+
+ private
+
+ def acme_integration_enabled?
+ application_settings = Gitlab::CurrentSettings.current_application_settings
+
+ application_settings.acme_terms_of_service_accepted &&
+ admin_email
+ end
+
+ # gets acme private key from application settings
+ # generates and saves one if it doesn't exist
+ def private_key
+ private_key_string = ApplicationSetting.current.acme_private_key
+ OpenSSL::PKey::RSA.new(private_key_string) if private_key_string
+ end
+
+ def acme_account_kid
+ ApplicationSetting.current.acme_account_kid
+ end
+
+ def admin_email
+ ApplicationSetting.current.acme_notification_email
+ end
+
+ def contact
+ "mailto:#{admin_email}"
+ end
+
+ def directory
+ if Rails.env.production?
+ PRODUCTION_DIRECTORY_URL
+ else
+ STAGING_DIRECTORY_URL
+ end
+ end
+ end
+ end
+end
diff --git a/spec/lib/gitlab/acme_spec.rb b/spec/lib/gitlab/acme_spec.rb
new file mode 100644
index 00000000000..560767816a9
--- /dev/null
+++ b/spec/lib/gitlab/acme_spec.rb
@@ -0,0 +1,39 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Gitlab::Acme do
+ include AcmeHelpers
+
+ before do
+ #WebMock.allow_net_connect!
+ stub_directory
+ end
+
+ describe '#create' do
+ subject { described_class.client }
+
+ context 'when admin email is set' do
+ let!(:application_setting) { create(:application_setting, acme_notification_email: 'info@test.example.com', acme_terms_of_service_accepted: true, acme_private_key: OpenSSL::PKey::RSA.new(4096).to_s) }
+
+ context 'when account is not yet created' do
+ it 'creates new account' do
+
+ subject
+ end
+ end
+
+ context 'when account is already created' do
+ it 'returns Acme client' do
+ expect(subject).to be_a(Acme::Client)
+ end
+ end
+ end
+
+ context 'when admin email is not set' do
+ it 'raises an exeption' do
+ expect { subject }.to raise_error('Acme integration is disabled')
+ end
+ end
+ end
+end
diff --git a/spec/support/helpers/acme_helpers.rb b/spec/support/helpers/acme_helpers.rb
new file mode 100644
index 00000000000..0cccc847d4c
--- /dev/null
+++ b/spec/support/helpers/acme_helpers.rb
@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module AcmeHelpers
+ NEW_NONCE_URL = 'https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce'
+
+ def stub_directory
+ response = <<-EOF
+{
+ "eQ3fEKjOSxE": "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417",
+ "keyChange": "https://acme-staging-v02.api.letsencrypt.org/acme/key-change",
+ "meta": {
+ "caaIdentities": [
+ "letsencrypt.org"
+ ],
+ "termsOfService": "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf",
+ "website": "https://letsencrypt.org/docs/staging-environment/"
+ },
+ "newAccount": "https://acme-staging-v02.api.letsencrypt.org/acme/new-acct",
+ "newNonce": "https://acme-staging-v02.api.letsencrypt.org/acme/new-nonce",
+ "newOrder": "https://acme-staging-v02.api.letsencrypt.org/acme/new-order",
+ "revokeCert": "https://acme-staging-v02.api.letsencrypt.org/acme/revoke-cert"
+}
+EOF
+ stub_request(:get, Gitlab::Acme::STAGING_DIRECTORY_URL)
+ .to_return(status: 200, body: response, headers: {})
+
+ stub_request(:head, NEW_NONCE_URL)
+ .to_return(status: 200, body: "", headers: {})
+ end
+
+ def stub_new_account
+ stub_request(:post, "https://acme-staging-v02.api.letsencrypt.org/acme/new-acct")
+ end
+end