debian-mirror-gitlab/spec/models/pages_domain_spec.rb

701 lines
20 KiB
Ruby
Raw Normal View History

2019-07-07 11:18:12 +05:30
# frozen_string_literal: true
2017-08-17 22:00:37 +05:30
require 'spec_helper'
2020-07-28 23:09:34 +05:30
RSpec.describe PagesDomain do
2018-03-17 18:26:18 +05:30
using RSpec::Parameterized::TableSyntax
subject(:pages_domain) { described_class.new }
2017-08-17 22:00:37 +05:30
describe 'associations' do
it { is_expected.to belong_to(:project) }
2020-04-08 14:13:33 +05:30
it { is_expected.to have_many(:serverless_domain_clusters) }
2017-08-17 22:00:37 +05:30
end
2021-09-04 01:27:46 +05:30
describe '.for_project' do
it 'returns domains assigned to project' do
domain = create(:pages_domain, :with_project)
create(:pages_domain) # unrelated domain
expect(described_class.for_project(domain.project)).to eq([domain])
end
end
2017-08-17 22:00:37 +05:30
describe 'validate domain' do
2017-09-10 17:25:29 +05:30
subject(:pages_domain) { build(:pages_domain, domain: domain) }
2017-08-17 22:00:37 +05:30
context 'is unique' do
let(:domain) { 'my.domain.com' }
2017-09-10 17:25:29 +05:30
it { is_expected.to validate_uniqueness_of(:domain).case_insensitive }
2017-08-17 22:00:37 +05:30
end
2018-05-09 12:01:36 +05:30
describe "hostname" do
{
'my.domain.com' => true,
'123.456.789' => true,
'0x12345.com' => true,
'0123123' => true,
'_foo.com' => false,
'reserved.com' => false,
'a.reserved.com' => false,
nil => false
}.each do |value, validity|
context "domain #{value.inspect} validity" do
before do
allow(Settings.pages).to receive(:host).and_return('reserved.com')
end
let(:domain) { value }
it { expect(pages_domain.valid?).to eq(validity) }
end
end
end
describe "HTTPS-only" do
using RSpec::Parameterized::TableSyntax
let(:domain) { 'my.domain.com' }
let(:project) do
instance_double(Project, pages_https_only?: pages_https_only)
end
let(:pages_domain) do
2019-09-30 21:07:59 +05:30
build(:pages_domain, certificate: certificate, key: key,
auto_ssl_enabled: auto_ssl_enabled).tap do |pd|
2018-05-09 12:01:36 +05:30
allow(pd).to receive(:project).and_return(project)
pd.valid?
2017-09-10 17:25:29 +05:30
end
2018-05-09 12:01:36 +05:30
end
2017-09-10 17:25:29 +05:30
2019-09-30 21:07:59 +05:30
where(:pages_https_only, :certificate, :key, :auto_ssl_enabled, :errors_on) do
2018-05-09 12:01:36 +05:30
attributes = attributes_for(:pages_domain)
cert, key = attributes.fetch_values(:certificate, :key)
2019-09-30 21:07:59 +05:30
true | nil | nil | false | %i(certificate key)
true | nil | nil | true | []
true | cert | nil | false | %i(key)
true | cert | nil | true | %i(key)
true | nil | key | false | %i(certificate key)
true | nil | key | true | %i(key)
true | cert | key | false | []
true | cert | key | true | []
false | nil | nil | false | []
false | nil | nil | true | []
false | cert | nil | false | %i(key)
false | cert | nil | true | %i(key)
false | nil | key | false | %i(key)
false | nil | key | true | %i(key)
false | cert | key | false | []
false | cert | key | true | []
2018-05-09 12:01:36 +05:30
end
2017-09-10 17:25:29 +05:30
2018-05-09 12:01:36 +05:30
with_them do
it "is adds the expected errors" do
2021-11-18 22:05:49 +05:30
expect(pages_domain.errors.attribute_names).to eq errors_on
2018-05-09 12:01:36 +05:30
end
2017-09-10 17:25:29 +05:30
end
2017-08-17 22:00:37 +05:30
end
end
2019-09-04 21:01:54 +05:30
describe 'when certificate is specified' do
let(:domain) { build(:pages_domain) }
it 'saves validity time' do
domain.save
2020-06-23 00:09:42 +05:30
expect(domain.certificate_valid_not_before).to be_like_time(Time.zone.parse("2020-03-16 14:20:34 UTC"))
expect(domain.certificate_valid_not_after).to be_like_time(Time.zone.parse("2220-01-28 14:20:34 UTC"))
2019-09-04 21:01:54 +05:30
end
end
2017-08-17 22:00:37 +05:30
describe 'validate certificate' do
subject { domain }
2020-03-13 15:44:24 +05:30
context 'serverless domain' do
it 'requires certificate and key to be present' do
expect(build(:pages_domain, :without_certificate, :without_key, usage: :serverless)).not_to be_valid
expect(build(:pages_domain, :without_certificate, usage: :serverless)).not_to be_valid
expect(build(:pages_domain, :without_key, usage: :serverless)).not_to be_valid
end
end
2018-05-09 12:01:36 +05:30
context 'with matching key' do
let(:domain) { build(:pages_domain) }
2017-08-17 22:00:37 +05:30
2018-05-09 12:01:36 +05:30
it { is_expected.to be_valid }
2017-08-17 22:00:37 +05:30
end
2018-05-09 12:01:36 +05:30
context 'when no certificate is specified' do
let(:domain) { build(:pages_domain, :without_certificate) }
2017-08-17 22:00:37 +05:30
it { is_expected.not_to be_valid }
end
2018-05-09 12:01:36 +05:30
context 'when no key is specified' do
let(:domain) { build(:pages_domain, :without_key) }
2017-08-17 22:00:37 +05:30
2018-05-09 12:01:36 +05:30
it { is_expected.not_to be_valid }
2017-08-17 22:00:37 +05:30
end
context 'for not matching key' do
2018-05-09 12:01:36 +05:30
let(:domain) { build(:pages_domain, :with_missing_chain) }
2017-08-17 22:00:37 +05:30
it { is_expected.not_to be_valid }
end
2019-09-30 21:07:59 +05:30
context 'when certificate is expired' do
let(:domain) do
build(:pages_domain, :with_trusted_expired_chain)
end
context 'when certificate is being changed' do
it "adds error to certificate" do
domain.valid?
2021-11-18 22:05:49 +05:30
expect(domain.errors.attribute_names).to contain_exactly(:key, :certificate)
2019-09-30 21:07:59 +05:30
end
end
context 'when certificate is already saved' do
it "doesn't add error to certificate" do
domain.save(validate: false)
domain.valid?
2021-11-18 22:05:49 +05:30
expect(domain.errors.attribute_names).to contain_exactly(:key)
2019-09-30 21:07:59 +05:30
end
end
end
2019-12-04 20:38:33 +05:30
context 'with ecdsa certificate' do
it "is valid" do
domain = build(:pages_domain, :ecdsa)
expect(domain).to be_valid
end
context 'when curve is set explicitly by parameters' do
2019-12-21 20:55:43 +05:30
it 'adds errors to private key' do
2019-12-04 20:38:33 +05:30
domain = build(:pages_domain, :explicit_ecdsa)
expect(domain).to be_invalid
expect(domain.errors[:key]).not_to be_empty
end
end
end
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
describe 'validations' do
it { is_expected.to validate_presence_of(:verification_code) }
end
2020-01-01 13:55:28 +05:30
describe 'default values' do
it 'defaults wildcard to false' do
expect(subject.wildcard).to eq(false)
end
2020-03-13 15:44:24 +05:30
it 'defaults scope to project' do
expect(subject.scope).to eq('project')
end
it 'defaults usage to pages' do
expect(subject.usage).to eq('pages')
2020-01-01 13:55:28 +05:30
end
end
2018-03-17 18:26:18 +05:30
describe '#verification_code' do
subject { pages_domain.verification_code }
it 'is set automatically with 128 bits of SecureRandom data' do
expect(SecureRandom).to receive(:hex).with(16) { 'verification code' }
is_expected.to eq('verification code')
end
end
describe '#keyed_verification_code' do
subject { pages_domain.keyed_verification_code }
it { is_expected.to eq("gitlab-pages-verification-code=#{pages_domain.verification_code}") }
end
describe '#verification_domain' do
subject { pages_domain.verification_domain }
it { is_expected.to be_nil }
it 'is a well-known subdomain if the domain is present' do
pages_domain.domain = 'example.com'
is_expected.to eq('_gitlab-pages-verification-code.example.com')
end
end
2017-08-17 22:00:37 +05:30
describe '#url' do
subject { domain.url }
2018-05-09 12:01:36 +05:30
let(:domain) { build(:pages_domain) }
2017-08-17 22:00:37 +05:30
2018-05-09 12:01:36 +05:30
it { is_expected.to eq("https://#{domain.domain}") }
2017-08-17 22:00:37 +05:30
2018-05-09 12:01:36 +05:30
context 'without the certificate' do
let(:domain) { build(:pages_domain, :without_certificate) }
2017-08-17 22:00:37 +05:30
2018-05-09 12:01:36 +05:30
it { is_expected.to eq("http://#{domain.domain}") }
2017-08-17 22:00:37 +05:30
end
end
describe '#has_matching_key?' do
subject { domain.has_matching_key? }
2018-05-09 12:01:36 +05:30
let(:domain) { build(:pages_domain) }
2017-08-17 22:00:37 +05:30
2018-05-09 12:01:36 +05:30
it { is_expected.to be_truthy }
2017-08-17 22:00:37 +05:30
context 'for invalid key' do
2018-05-09 12:01:36 +05:30
let(:domain) { build(:pages_domain, :with_missing_chain) }
2017-08-17 22:00:37 +05:30
it { is_expected.to be_falsey }
end
end
describe '#has_intermediates?' do
subject { domain.has_intermediates? }
context 'for self signed' do
2018-05-09 12:01:36 +05:30
let(:domain) { build(:pages_domain) }
2017-08-17 22:00:37 +05:30
it { is_expected.to be_truthy }
end
context 'for missing certificate chain' do
let(:domain) { build(:pages_domain, :with_missing_chain) }
it { is_expected.to be_falsey }
end
context 'for trusted certificate chain' do
# We only validate that we can to rebuild the trust chain, for certificates
# We assume that 'AddTrustExternalCARoot' needed to validate the chain is in trusted store.
# It will be if ca-certificates is installed on Debian/Ubuntu/Alpine
let(:domain) { build(:pages_domain, :with_trusted_chain) }
it { is_expected.to be_truthy }
end
2021-11-18 22:05:49 +05:30
# The LetsEncrypt DST Root CA X3 expired on 2021-09-30, but the
# cross-sign in ISRG Root X1 enables it to function provided a chain
# of trust can be established with the system store. See:
#
# 1. https://community.letsencrypt.org/t/production-chain-changes/150739
# 2. https://letsencrypt.org/2020/12/21/extending-android-compatibility.html
# 3. https://www.openssl.org/blog/blog/2021/09/13/LetsEncryptRootCertExpire/
context 'with a LetsEncrypt bundle with an expired DST Root CA X3' do
let(:domain) { build(:pages_domain, :letsencrypt_expired_x3_root) }
it { is_expected.to be_truthy }
end
2017-08-17 22:00:37 +05:30
end
describe '#expired?' do
subject { domain.expired? }
context 'for valid' do
2018-05-09 12:01:36 +05:30
let(:domain) { build(:pages_domain) }
2017-08-17 22:00:37 +05:30
it { is_expected.to be_falsey }
end
context 'for expired' do
let(:domain) { build(:pages_domain, :with_expired_certificate) }
it { is_expected.to be_truthy }
end
end
describe '#subject' do
2018-05-09 12:01:36 +05:30
let(:domain) { build(:pages_domain) }
2017-08-17 22:00:37 +05:30
subject { domain.subject }
it { is_expected.to eq('/CN=test-certificate') }
end
describe '#certificate_text' do
2018-05-09 12:01:36 +05:30
let(:domain) { build(:pages_domain) }
2017-08-17 22:00:37 +05:30
subject { domain.certificate_text }
# We test only existence of output, since the output is long
it { is_expected.not_to be_empty }
end
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
describe "#https?" do
context "when a certificate is present" do
subject { build(:pages_domain) }
2019-12-21 20:55:43 +05:30
2018-05-09 12:01:36 +05:30
it { is_expected.to be_https }
end
context "when no certificate is present" do
subject { build(:pages_domain, :without_certificate) }
2019-12-21 20:55:43 +05:30
2018-05-09 12:01:36 +05:30
it { is_expected.not_to be_https }
end
end
2018-03-17 18:26:18 +05:30
describe '#update_daemon' do
2020-10-24 23:57:45 +05:30
let_it_be(:project) { create(:project).tap(&:mark_pages_as_deployed) }
2020-03-13 15:44:24 +05:30
context 'when usage is serverless' do
it 'does not call the UpdatePagesConfigurationService' do
2020-10-24 23:57:45 +05:30
expect(PagesUpdateConfigurationWorker).not_to receive(:perform_async)
2020-01-01 13:55:28 +05:30
2020-03-13 15:44:24 +05:30
create(:pages_domain, usage: :serverless)
2020-01-01 13:55:28 +05:30
end
end
2018-03-17 18:26:18 +05:30
it 'runs when the domain is created' do
domain = build(:pages_domain)
expect(domain).to receive(:update_daemon)
domain.save!
end
it 'runs when the domain is destroyed' do
domain = create(:pages_domain)
expect(domain).to receive(:update_daemon)
domain.destroy!
end
2020-10-24 23:57:45 +05:30
it "schedules a PagesUpdateConfigurationWorker" do
expect(PagesUpdateConfigurationWorker).to receive(:perform_async).with(project.id)
create(:pages_domain, project: project)
end
context "when the pages aren't deployed" do
let_it_be(:project) { create(:project).tap(&:mark_pages_as_not_deployed) }
it "does not schedule a PagesUpdateConfigurationWorker" do
expect(PagesUpdateConfigurationWorker).not_to receive(:perform_async).with(project.id)
create(:pages_domain, project: project)
end
2018-03-17 18:26:18 +05:30
end
context 'configuration updates when attributes change' do
2020-03-13 15:44:24 +05:30
let_it_be(:project1) { create(:project) }
let_it_be(:project2) { create(:project) }
let_it_be(:domain) { create(:pages_domain) }
2018-03-17 18:26:18 +05:30
where(:attribute, :old_value, :new_value, :update_expected) do
2020-06-23 00:09:42 +05:30
now = Time.current
2018-03-17 18:26:18 +05:30
future = now + 1.day
:project | nil | :project1 | true
:project | :project1 | :project1 | false
:project | :project1 | :project2 | true
:project | :project1 | nil | true
# domain can't be set to nil
:domain | 'a.com' | 'a.com' | false
:domain | 'a.com' | 'b.com' | true
# verification_code can't be set to nil
:verification_code | 'foo' | 'foo' | false
:verification_code | 'foo' | 'bar' | false
:verified_at | nil | now | false
:verified_at | now | now | false
:verified_at | now | future | false
:verified_at | now | nil | false
:enabled_until | nil | now | true
:enabled_until | now | now | false
:enabled_until | now | future | false
:enabled_until | now | nil | true
end
with_them do
it 'runs if a relevant attribute has changed' do
a = old_value.is_a?(Symbol) ? send(old_value) : old_value
b = new_value.is_a?(Symbol) ? send(new_value) : new_value
domain.update!(attribute => a)
if update_expected
expect(domain).to receive(:update_daemon)
else
expect(domain).not_to receive(:update_daemon)
end
domain.update!(attribute => b)
end
end
context 'TLS configuration' do
2020-03-13 15:44:24 +05:30
let_it_be(:domain_without_tls) { create(:pages_domain, :without_certificate, :without_key) }
let_it_be(:domain) { create(:pages_domain) }
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
let(:cert1) { domain.certificate }
2018-03-17 18:26:18 +05:30
let(:cert2) { cert1 + ' ' }
2018-05-09 12:01:36 +05:30
let(:key1) { domain.key }
2018-03-17 18:26:18 +05:30
let(:key2) { key1 + ' ' }
it 'updates when added' do
2018-05-09 12:01:36 +05:30
expect(domain_without_tls).to receive(:update_daemon)
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
domain_without_tls.update!(key: key1, certificate: cert1)
2018-03-17 18:26:18 +05:30
end
it 'updates when changed' do
2018-05-09 12:01:36 +05:30
expect(domain).to receive(:update_daemon)
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
domain.update!(key: key2, certificate: cert2)
2018-03-17 18:26:18 +05:30
end
it 'updates when removed' do
2018-05-09 12:01:36 +05:30
expect(domain).to receive(:update_daemon)
2018-03-17 18:26:18 +05:30
2018-05-09 12:01:36 +05:30
domain.update!(key: nil, certificate: nil)
2018-03-17 18:26:18 +05:30
end
end
end
end
2019-07-31 22:56:46 +05:30
2019-09-30 21:07:59 +05:30
describe '#user_provided_key' do
subject { domain.user_provided_key }
context 'when certificate is provided by user' do
let(:domain) { create(:pages_domain) }
it 'returns key' do
is_expected.to eq(domain.key)
end
end
context 'when certificate is provided by gitlab' do
let(:domain) { create(:pages_domain, :letsencrypt) }
it 'returns nil' do
is_expected.to be_nil
end
end
end
describe '#user_provided_certificate' do
subject { domain.user_provided_certificate }
context 'when certificate is provided by user' do
let(:domain) { create(:pages_domain) }
it 'returns key' do
is_expected.to eq(domain.certificate)
end
end
context 'when certificate is provided by gitlab' do
let(:domain) { create(:pages_domain, :letsencrypt) }
it 'returns nil' do
is_expected.to be_nil
end
end
end
shared_examples 'certificate setter' do |attribute, setter_name, old_certificate_source, new_certificate_source|
let(:domain) do
create(:pages_domain, certificate_source: old_certificate_source)
end
let(:old_value) { domain.public_send(attribute) }
subject { domain.public_send(setter_name, new_value) }
context 'when value has been changed' do
let(:new_value) { 'new_value' }
it "assignes new value to #{attribute}" do
expect do
subject
end.to change { domain.public_send(attribute) }.from(old_value).to('new_value')
end
it 'changes certificate source' do
expect do
subject
end.to change { domain.certificate_source }.from(old_certificate_source).to(new_certificate_source)
end
end
context 'when value has not been not changed' do
let(:new_value) { old_value }
it 'does not change certificate source' do
expect do
subject
end.not_to change { domain.certificate_source }.from(old_certificate_source)
end
end
end
describe '#user_provided_key=' do
include_examples('certificate setter', 'key', 'user_provided_key=',
'gitlab_provided', 'user_provided')
end
describe '#gitlab_provided_key=' do
include_examples('certificate setter', 'key', 'gitlab_provided_key=',
'user_provided', 'gitlab_provided')
end
describe '#user_provided_certificate=' do
include_examples('certificate setter', 'certificate', 'user_provided_certificate=',
'gitlab_provided', 'user_provided')
end
describe '#gitlab_provided_certificate=' do
include_examples('certificate setter', 'certificate', 'gitlab_provided_certificate=',
'user_provided', 'gitlab_provided')
end
2020-04-22 19:07:51 +05:30
describe '#save' do
context 'when we failed to obtain ssl certificate' do
let(:domain) { create(:pages_domain, auto_ssl_enabled: true, auto_ssl_failed: true) }
it 'clears failure if auto ssl is disabled' do
expect do
domain.update!(auto_ssl_enabled: false)
end.to change { domain.auto_ssl_failed }.from(true).to(false)
end
it 'does not clear failure on unrelated updates' do
expect do
2020-06-23 00:09:42 +05:30
domain.update!(verified_at: Time.current)
2020-04-22 19:07:51 +05:30
end.not_to change { domain.auto_ssl_failed }.from(true)
end
end
end
2019-07-31 22:56:46 +05:30
describe '.for_removal' do
subject { described_class.for_removal }
context 'when domain is not schedule for removal' do
let!(:domain) { create :pages_domain }
it 'does not return domain' do
is_expected.to be_empty
end
end
context 'when domain is scheduled for removal yesterday' do
let!(:domain) { create :pages_domain, remove_at: 1.day.ago }
it 'returns domain' do
is_expected.to eq([domain])
end
end
context 'when domain is scheduled for removal tomorrow' do
let!(:domain) { create :pages_domain, remove_at: 1.day.from_now }
it 'does not return domain' do
is_expected.to be_empty
end
end
end
2019-09-30 21:07:59 +05:30
2020-03-13 15:44:24 +05:30
describe '.instance_serverless' do
subject { described_class.instance_serverless }
before do
create(:pages_domain, wildcard: true)
create(:pages_domain, :instance_serverless)
create(:pages_domain, scope: :instance)
create(:pages_domain, :instance_serverless)
create(:pages_domain, usage: :serverless)
end
it 'returns domains that are wildcard, instance-level, and serverless' do
expect(subject.length).to eq(2)
subject.each do |domain|
expect(domain.wildcard).to eq(true)
expect(domain.usage).to eq('serverless')
expect(domain.scope).to eq('instance')
end
end
end
2019-09-30 21:07:59 +05:30
describe '.need_auto_ssl_renewal' do
subject { described_class.need_auto_ssl_renewal }
let!(:domain_with_user_provided_certificate) { create(:pages_domain) }
let!(:domain_with_expired_user_provided_certificate) do
create(:pages_domain, :with_expired_certificate)
end
2020-10-24 23:57:45 +05:30
2019-09-30 21:07:59 +05:30
let!(:domain_with_user_provided_certificate_and_auto_ssl) do
create(:pages_domain, auto_ssl_enabled: true)
end
let!(:domain_with_gitlab_provided_certificate) { create(:pages_domain, :letsencrypt) }
let!(:domain_with_expired_gitlab_provided_certificate) do
create(:pages_domain, :letsencrypt, :with_expired_certificate)
end
2020-05-24 23:13:21 +05:30
let!(:domain_with_failed_auto_ssl) do
create(:pages_domain, auto_ssl_enabled: true, auto_ssl_failed: true)
end
it 'contains only domains needing ssl renewal' do
2019-09-30 21:07:59 +05:30
is_expected.to(
contain_exactly(
domain_with_user_provided_certificate_and_auto_ssl,
domain_with_expired_gitlab_provided_certificate
)
)
end
end
2019-12-04 20:38:33 +05:30
2019-12-21 20:55:43 +05:30
describe '#pages_virtual_domain' do
let(:project) { create(:project) }
let(:pages_domain) { create(:pages_domain, project: project) }
context 'when there are no pages deployed for the project' do
it 'returns nil' do
expect(pages_domain.pages_virtual_domain).to be_nil
end
end
2021-09-04 01:27:46 +05:30
it 'returns the virual domain when there are pages deployed for the project' do
project.mark_pages_as_deployed
project.update_pages_deployment!(create(:pages_deployment, project: project))
2019-12-04 20:38:33 +05:30
2021-09-04 01:27:46 +05:30
expect(Pages::VirtualDomain).to receive(:new).with([project], domain: pages_domain).and_call_original
2019-12-04 20:38:33 +05:30
2021-09-04 01:27:46 +05:30
virtual_domain = pages_domain.pages_virtual_domain
2019-12-04 20:38:33 +05:30
2021-09-04 01:27:46 +05:30
expect(virtual_domain).to be_an_instance_of(Pages::VirtualDomain)
expect(virtual_domain.lookup_paths).not_to be_empty
2019-12-04 20:38:33 +05:30
end
end
2020-04-08 14:13:33 +05:30
describe '.find_by_domain_case_insensitive' do
it 'lookup is case-insensitive' do
pages_domain = create(:pages_domain, domain: "Pages.IO")
expect(PagesDomain.find_by_domain_case_insensitive('pages.io')).to eq(pages_domain)
end
end
2017-08-17 22:00:37 +05:30
end