BigW Consortium Gitlab

pages_domain.rb 2.78 KB
Newer Older
Kamil Trzcinski committed
1 2 3
class PagesDomain < ActiveRecord::Base
  belongs_to :project

4
  validates :domain, hostname: { allow_numeric_hostname: true }
Douwe Maan committed
5
  validates :domain, uniqueness: { case_sensitive: false }
Kamil Trzcinski committed
6 7 8
  validates :certificate, certificate: true, allow_nil: true, allow_blank: true
  validates :key, certificate_key: true, allow_nil: true, allow_blank: true

9 10
  validate :validate_pages_domain
  validate :validate_matching_key, if: ->(domain) { domain.certificate.present? || domain.key.present? }
11 12
  validate :validate_intermediates, if: ->(domain) { domain.certificate.present? }

13 14
  attr_encrypted :key,
    mode: :per_attribute_iv_and_salt,
James Lopez committed
15
    insecure_mode: true,
16 17
    key: Gitlab::Application.secrets.db_key_base,
    algorithm: 'aes-256-cbc'
Kamil Trzcinski committed
18 19 20 21 22

  after_create :update
  after_save :update
  after_destroy :update

23 24 25 26
  def to_param
    domain
  end

Kamil Trzcinski committed
27 28 29 30
  def url
    return unless domain

    if certificate
31
      "https://#{domain}"
Kamil Trzcinski committed
32
    else
33
      "http://#{domain}"
Kamil Trzcinski committed
34 35 36
    end
  end

37
  def has_matching_key?
38 39
    return false unless x509
    return false unless pkey
40 41 42 43 44 45 46 47

    # We compare the public key stored in certificate with public key from certificate key
    x509.check_private_key(pkey)
  end

  def has_intermediates?
    return false unless x509

48 49 50
    # self-signed certificates doesn't have the certificate chain
    return true if x509.verify(x509.public_key)

51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
    store = OpenSSL::X509::Store.new
    store.set_default_paths

    # This forces to load all intermediate certificates stored in `certificate`
    Tempfile.open('certificate_chain') do |f|
      f.write(certificate)
      f.flush
      store.add_file(f.path)
    end

    store.verify(x509)
  rescue OpenSSL::X509::StoreError
    false
  end

  def expired?
    return false unless x509
    current = Time.new
69
    current < x509.not_before || x509.not_after < current
70 71 72 73
  end

  def subject
    return unless x509
74
    x509.subject.to_s
75 76
  end

77 78
  def certificate_text
    @certificate_text ||= x509.try(:to_text)
79 80
  end

Kamil Trzcinski committed
81 82
  private

Kamil Trzcinski committed
83
  def update
84 85 86 87 88 89 90 91 92 93 94 95 96
    ::Projects::UpdatePagesConfigurationService.new(project).execute
  end

  def validate_matching_key
    unless has_matching_key?
      self.errors.add(:key, "doesn't match the certificate")
    end
  end

  def validate_intermediates
    unless has_intermediates?
      self.errors.add(:certificate, 'misses intermediates')
    end
Kamil Trzcinski committed
97
  end
98 99 100

  def validate_pages_domain
    return unless domain
101
    if domain.downcase.ends_with?(Settings.pages.host.downcase)
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
      self.errors.add(:domain, "*.#{Settings.pages.host} is restricted")
    end
  end

  def x509
    return unless certificate
    @x509 ||= OpenSSL::X509::Certificate.new(certificate)
  rescue OpenSSL::X509::CertificateError
    nil
  end

  def pkey
    return unless key
    @pkey ||= OpenSSL::PKey::RSA.new(key)
  rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError
    nil
  end
Kamil Trzcinski committed
119
end