summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/controllers/projects/pages_controller.rb94
-rw-r--r--app/helpers/projects_helper.rb4
-rw-r--r--app/models/pages_domain.rb84
-rw-r--r--app/services/projects/update_pages_configuration_service.rb32
-rw-r--r--app/views/projects/pages/_access.html.haml29
-rw-r--r--app/views/projects/pages/_destroy.haml2
-rw-r--r--app/views/projects/pages/_form.html.haml64
-rw-r--r--app/views/projects/pages/_list.html.haml16
-rw-r--r--app/views/projects/pages/_no_domains.html.haml6
-rw-r--r--app/views/projects/pages/_remove_certificate.html.haml16
-rw-r--r--app/views/projects/pages/_upload_certificate.html.haml32
-rw-r--r--app/views/projects/pages/index.html.haml25
-rw-r--r--app/views/projects/pages/new.html.haml6
-rw-r--r--app/views/projects/pages/show.html.haml38
-rw-r--r--config/gitlab.yml.example2
-rw-r--r--config/initializers/1_settings.rb3
-rw-r--r--config/routes/project.rb4
17 files changed, 249 insertions, 208 deletions
diff --git a/app/controllers/projects/pages_controller.rb b/app/controllers/projects/pages_controller.rb
index 055f182ae00..82814afe196 100644
--- a/app/controllers/projects/pages_controller.rb
+++ b/app/controllers/projects/pages_controller.rb
@@ -2,25 +2,45 @@ class Projects::PagesController < Projects::ApplicationController
layout 'project_settings'
before_action :authorize_update_pages!, except: [:show]
- before_action :authorize_remove_pages!, only: :destroy
+ before_action :authorize_remove_pages!, only: [:remove_pages]
+ before_action :label, only: [:destroy]
+ before_action :domain, only: [:show]
helper_method :valid_certificate?, :valid_certificate_key?
helper_method :valid_key_for_certificiate?, :valid_certificate_intermediates?
helper_method :certificate, :certificate_key
+ def index
+ @domains = @project.pages_domains.order(:domain)
+ end
+
def show
end
- def update
- if @project.update_attributes(pages_params)
+ def new
+ @domain = @project.pages_domains.new
+ end
+
+ def create
+ @domain = @project.pages_domains.create(pages_domain_params)
+
+ if @domain.valid?
redirect_to namespace_project_pages_path(@project.namespace, @project)
else
- render 'show'
+ render 'new'
end
end
- def certificate
- @project.remove_pages_certificate
+ def destroy
+ @domain.destroy
+
+ respond_to do |format|
+ format.html do
+ redirect_to(namespace_project_pages_path(@project.namespace, @project),
+ notice: 'Domain was removed')
+ end
+ format.js
+ end
end
def destroy
@@ -33,63 +53,15 @@ class Projects::PagesController < Projects::ApplicationController
private
- def pages_params
- params.require(:project).permit(
- :pages_custom_certificate,
- :pages_custom_certificate_key,
- :pages_custom_domain,
- :pages_redirect_http,
+ def pages_domain_params
+ params.require(:pages_domain).permit(
+ :certificate,
+ :key,
+ :domain
)
end
- def valid_certificate?
- certificate.present?
- end
-
- def valid_certificate_key?
- certificate_key.present?
- end
-
- def valid_key_for_certificiate?
- return false unless certificate
- return false unless certificate_key
-
- # We compare the public key stored in certificate with public key from certificate key
- certificate.public_key.to_pem == certificate_key.public_key.to_pem
- rescue OpenSSL::X509::CertificateError, OpenSSL::PKey::PKeyError
- false
- end
-
- def valid_certificate_intermediates?
- return false unless certificate
-
- store = OpenSSL::X509::Store.new
- store.set_default_paths
-
- # This forces to load all intermediate certificates stored in `pages_custom_certificate`
- Tempfile.open('project_certificate') do |f|
- f.write(@project.pages_custom_certificate)
- f.flush
- store.add_file(f.path)
- end
-
- store.verify(certificate)
- rescue OpenSSL::X509::StoreError
- false
- end
-
- def certificate
- return unless @project.pages_custom_certificate
-
- @certificate ||= OpenSSL::X509::Certificate.new(@project.pages_custom_certificate)
- rescue OpenSSL::X509::CertificateError
- nil
- end
-
- def certificate_key
- return unless @project.pages_custom_certificate_key
- @certificate_key ||= OpenSSL::PKey::RSA.new(@project.pages_custom_certificate_key)
- rescue OpenSSL::PKey::PKeyError, OpenSSL::Cipher::CipherError
- nil
+ def domain
+ @domain ||= @project.pages_domains.find_by(domain: params[:id].to_s)
end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 63aa182502d..054cc849839 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -85,10 +85,6 @@ module ProjectsHelper
"You are going to remove the pages for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?"
end
- def remove_pages_certificate_message(project)
- "You are going to remove a certificates for #{project.name_with_namespace}.\n Are you ABSOLUTELY sure?"
- end
-
def project_nav_tabs
@nav_tabs ||= get_project_nav_tabs(@project, current_user)
end
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index eebdf7501de..810af4e832a 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -2,19 +2,25 @@ class PagesDomain < ActiveRecord::Base
belongs_to :project
validates :domain, hostname: true
- validates_uniqueness_of :domain, allow_nil: true, allow_blank: true
+ validates_uniqueness_of :domain, case_sensitive: false
validates :certificate, certificate: true, allow_nil: true, allow_blank: true
validates :key, certificate_key: true, allow_nil: true, allow_blank: true
- attr_encrypted :pages_custom_certificate_key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base
+ validate :validate_matching_key, if: ->(domain) { domain.certificate.present? && domain.key.present? }
+ validate :validate_intermediates, if: ->(domain) { domain.certificate.present? }
+
+ attr_encrypted :key, mode: :per_attribute_iv_and_salt, key: Gitlab::Application.secrets.db_key_base
after_create :update
after_save :update
after_destroy :update
+ def to_param
+ domain
+ end
+
def url
return unless domain
- return unless Dir.exist?(project.public_pages_path)
if certificate
return "https://#{domain}"
@@ -23,7 +29,77 @@ class PagesDomain < ActiveRecord::Base
end
end
+ def has_matching_key?
+ return unless x509
+ return unless pkey
+
+ # 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
+
+ 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
+ return current < x509.not_before || x509.not_after < current
+ end
+
+ def subject
+ return unless x509
+ return x509.subject.to_s
+ end
+
+ def fingerprint
+ return unless x509
+ @fingeprint ||= OpenSSL::Digest::SHA256.new(x509.to_der).to_s
+ end
+
+ private
+
+ 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
+
def update
- UpdatePagesConfigurationService.new(project).execute
+ ::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
end
end
diff --git a/app/services/projects/update_pages_configuration_service.rb b/app/services/projects/update_pages_configuration_service.rb
index be4c2fbef8c..5afb0582ca6 100644
--- a/app/services/projects/update_pages_configuration_service.rb
+++ b/app/services/projects/update_pages_configuration_service.rb
@@ -7,9 +7,7 @@ module Projects
end
def execute
- update_file(pages_cname_file, project.pages_custom_domain)
- update_file(pages_certificate_file, project.pages_custom_certificate)
- update_file(pages_certificate_file_key, project.pages_custom_certificate_key)
+ update_file(pages_config_file, pages_config)
reload_daemon
success
rescue => e
@@ -18,6 +16,22 @@ module Projects
private
+ def pages_config
+ {
+ domains: pages_domains_config
+ }
+ end
+
+ def pages_domains_config
+ project.pages_domains.map do |domain|
+ {
+ domain: domain.domain,
+ certificate: domain.certificate,
+ key: domain.key,
+ }
+ end
+ end
+
def reload_daemon
# GitLab Pages daemon constantly watches for modification time of `pages.path`
# It reloads configuration when `pages.path` is modified
@@ -28,16 +42,8 @@ module Projects
@pages_path ||= project.pages_path
end
- def pages_cname_file
- File.join(pages_path, 'CNAME')
- end
-
- def pages_certificate_file
- File.join(pages_path, 'domain.crt')
- end
-
- def pages_certificate_key_file
- File.join(pages_path, 'domain.key')
+ def pages_config_file
+ File.join(pages_path, 'config.jso')
end
def update_file(file, data)
diff --git a/app/views/projects/pages/_access.html.haml b/app/views/projects/pages/_access.html.haml
index d64f99fd22b..9740877b214 100644
--- a/app/views/projects/pages/_access.html.haml
+++ b/app/views/projects/pages/_access.html.haml
@@ -5,30 +5,9 @@
.panel-body
%p
%strong
- Congratulations! Your pages are served at:
- %p= link_to @project.pages_url, @project.pages_url
-
- - if Settings.pages.custom_domain && @project.pages_custom_url
- %p= link_to @project.pages_custom_url, @project.pages_custom_url
-
- - if @project.pages_custom_certificate
- - unless valid_certificate?
- #error_explanation
- .alert.alert-warning
- Your certificate is invalid.
+ Congratulations! Your pages are served under:
- - unless valid_certificate_key?
- #error_explanation
- .alert.alert-warning
- Your private key is invalid.
-
- - unless valid_key_for_certificiate?
- #error_explanation
- .alert.alert-warning
- Your private key can't be used with your certificate.
+ %p= link_to @project.pages_url, @project.pages_url
- - unless valid_certificate_intermediates?
- #error_explanation
- .alert.alert-warning
- Your certificate doesn't have intermediates.
- Your page may not work properly.
+ - @project.pages_domains.each do |domain|
+ %p= link_to domain.url, domain.url
diff --git a/app/views/projects/pages/_destroy.haml b/app/views/projects/pages/_destroy.haml
index 61b995a5934..dd493a6d312 100644
--- a/app/views/projects/pages/_destroy.haml
+++ b/app/views/projects/pages/_destroy.haml
@@ -3,7 +3,7 @@
.panel-heading Remove pages
.errors-holder
.panel-body
- = form_tag(namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do
+ = form_tag(remove_pages_namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do
%p
Removing the pages will prevent from exposing them to outside world.
.form-actions
diff --git a/app/views/projects/pages/_form.html.haml b/app/views/projects/pages/_form.html.haml
index a7b03d552db..c69b76c6697 100644
--- a/app/views/projects/pages/_form.html.haml
+++ b/app/views/projects/pages/_form.html.haml
@@ -1,35 +1,35 @@
-- if can?(current_user, :update_pages, @project)
- .panel.panel-default
- .panel-heading
- Settings
- .panel-body
- = form_for [@project], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f|
- - if @project.errors.any?
- #error_explanation
- .alert.alert-danger
- - @project.errors.full_messages.each do |msg|
- %p= msg
+= form_for [@domain], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f|
+ - if @domain.errors.any?
+ #error_explanation
+ .alert.alert-danger
+ - @domain.errors.full_messages.each do |msg|
+ %p= msg
- .form-group
- = f.label :pages_domain, class: 'control-label' do
- Custom domain
- .col-sm-10
- - if Settings.pages.custom_domain
- = f.text_field :pages_custom_domain, required: false, autocomplete: 'off', class: 'form-control'
- %span.help-inline Allows you to serve the pages under your domain
- - else
- .nothing-here-block
- Support for custom domains and certificates is disabled.
- Ask your system's administrator to enable it.
+ .form-group
+ = f.label :domain, class: 'control-label' do
+ Domain
+ .col-sm-10
+ = f.text_field :domain, required: true, autocomplete: 'off', class: 'form-control'
+ %span.help-inline * required
- - if Settings.pages.https
- .form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :pages_redirect_http do
- = f.check_box :pages_redirect_http
- %span.descr Force HTTPS
- .help-block Redirect the HTTP to HTTPS forcing to always use the secure connection
+ - if Settings.pages.external_https
+ .form-group
+ = f.label :certificate, class: 'control-label' do
+ Certificate (PEM)
+ .col-sm-10
+ = f.text_area :certificate, rows: 5, class: 'form-control', value: ''
+ %span.help-inline Upload a certificate for your domain with all intermediates
- .form-actions
- = f.submit 'Save changes', class: "btn btn-save"
+ .form-group
+ = f.label :key, class: 'control-label' do
+ Key (PEM)
+ .col-sm-10
+ = f.text_area :key, rows: 5, class: 'form-control', value: ''
+ %span.help-inline Upload a certificate for your domain with all intermediates
+ - else
+ .nothing-here-block
+ Support for custom certificates is disabled.
+ Ask your system's administrator to enable it.
+
+ .form-actions
+ = f.submit 'Create New Domain', class: "btn btn-save"
diff --git a/app/views/projects/pages/_list.html.haml b/app/views/projects/pages/_list.html.haml
new file mode 100644
index 00000000000..7dfeb0e6e12
--- /dev/null
+++ b/app/views/projects/pages/_list.html.haml
@@ -0,0 +1,16 @@
+.panel.panel-default
+ .panel-heading
+ Domains (#{@domains.count})
+ %ul.well-list
+ - @domains.each do |domain|
+ %li
+ .pull-right
+ = link_to 'Details', namespace_project_page_path(@project.namespace, @project, domain), class: "btn btn-sm btn-grouped"
+ = link_to 'Remove', namespace_project_page_path(@project.namespace, @project, domain), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped"
+ .clearfix
+ %span= link_to domain.domain, domain.url
+ %p
+ - if domain.subject
+ %span.label.label-gray Certificate: #{domain.subject}
+ - if domain.expired?
+ %span.label.label-danger Expired
diff --git a/app/views/projects/pages/_no_domains.html.haml b/app/views/projects/pages/_no_domains.html.haml
new file mode 100644
index 00000000000..5a18740346a
--- /dev/null
+++ b/app/views/projects/pages/_no_domains.html.haml
@@ -0,0 +1,6 @@
+.panel.panel-default
+ .panel-heading
+ Domains
+ .nothing-here-block
+ Support for domains and certificates is disabled.
+ Ask your system's administrator to enable it.
diff --git a/app/views/projects/pages/_remove_certificate.html.haml b/app/views/projects/pages/_remove_certificate.html.haml
deleted file mode 100644
index e8c0d03adfa..00000000000
--- a/app/views/projects/pages/_remove_certificate.html.haml
+++ /dev/null
@@ -1,16 +0,0 @@
-- if can?(current_user, :update_pages, @project) && @project.pages_custom_certificate
- .panel.panel-default.panel.panel-danger
- .panel-heading
- Remove certificate
- .errors-holder
- .panel-body
- = form_tag(certificates_namespace_project_pages_path(@project.namespace, @project), method: :delete, class: 'form-horizontal') do
- %p
- Removing the certificate will stop serving the page under HTTPS.
- - if certificate
- %p
- %pre
- = certificate.to_text
-
- .form-actions
- = button_to 'Remove certificate', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_pages_certificate_message(@project) }
diff --git a/app/views/projects/pages/_upload_certificate.html.haml b/app/views/projects/pages/_upload_certificate.html.haml
deleted file mode 100644
index 30873fcf395..00000000000
--- a/app/views/projects/pages/_upload_certificate.html.haml
+++ /dev/null
@@ -1,32 +0,0 @@
-- if can?(current_user, :update_pages, @project) && Settings.pages.https && Settings.pages.custom_domain
- .panel.panel-default
- .panel-heading
- Certificate
- .panel-body
- %p
- Allows you to upload your certificate which will be used to serve pages under your domain.
- %br
-
- = form_for [@project], url: namespace_project_pages_path(@project.namespace, @project), html: { class: 'form-horizontal fieldset-form' } do |f|
- - if @project.errors.any?
- #error_explanation
- .alert.alert-danger
- - @project.errors.full_messages.each do |msg|
- %p= msg
-
- .form-group
- = f.label :pages_custom_certificate, class: 'control-label' do
- Certificate (PEM)
- .col-sm-10
- = f.text_area :pages_custom_certificate, required: true, rows: 5, class: 'form-control', value: ''
- %span.help-inline Upload a certificate for your domain with all intermediates
-
- .form-group
- = f.label :pages_custom_certificate_key, class: 'control-label' do
- Key (PEM)
- .col-sm-10
- = f.text_area :pages_custom_certificate_key, required: true, rows: 5, class: 'form-control', value: ''
- %span.help-inline Upload a certificate for your domain with all intermediates
-
- .form-actions
- = f.submit 'Update certificate', class: "btn btn-save"
diff --git a/app/views/projects/pages/index.html.haml b/app/views/projects/pages/index.html.haml
new file mode 100644
index 00000000000..fea34c113ba
--- /dev/null
+++ b/app/views/projects/pages/index.html.haml
@@ -0,0 +1,25 @@
+- page_title "Pages"
+%h3.page_title
+ Pages
+
+ = link_to new_namespace_project_page_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Domain" do
+ %i.fa.fa-plus
+ New Domain
+
+%p.light
+ With GitLab Pages you can host for free your static websites on GitLab.
+ Combined with the power of GitLab CI and the help of GitLab Runner
+ you can deploy static pages for your individual projects, your user or your group.
+
+%hr.clearfix
+
+- if Settings.pages.enabled
+ = render 'access'
+ = render 'use'
+ - if Settings.pages.external_http || Settings.pages.external_https
+ = render 'list'
+ - else
+ = render 'no_domains'
+ = render 'destroy'
+- else
+ = render 'disabled'
diff --git a/app/views/projects/pages/new.html.haml b/app/views/projects/pages/new.html.haml
new file mode 100644
index 00000000000..2609df62aac
--- /dev/null
+++ b/app/views/projects/pages/new.html.haml
@@ -0,0 +1,6 @@
+- page_title 'Pages'
+%h3.page_title
+ New Pages Domain
+%hr.clearfix
+%div
+ = render 'form'
diff --git a/app/views/projects/pages/show.html.haml b/app/views/projects/pages/show.html.haml
index 5f689800da8..98c4e890968 100644
--- a/app/views/projects/pages/show.html.haml
+++ b/app/views/projects/pages/show.html.haml
@@ -1,18 +1,22 @@
-- page_title "Pages"
-%h3.page_title Pages
-%p.light
- With GitLab Pages you can host for free your static websites on GitLab.
- Combined with the power of GitLab CI and the help of GitLab Runner
- you can deploy static pages for your individual projects, your user or your group.
-%hr
+- page_title "#{@domain.domain}", "Pages Domain"
-- if Settings.pages.enabled
- = render 'access'
- = render 'use'
- - if @project.pages_url
- = render 'form'
- = render 'upload_certificate'
- = render 'remove_certificate'
- = render 'destroy'
-- else
- = render 'disabled'
+%h3.page-title
+ #{@domain.domain}
+
+.table-holder
+ %table.table
+ %tr
+ %td
+ Domain
+ %td
+ = link_to @domain.domain, @domain.url
+ %tr
+ %td
+ Certificate
+ %td
+ - if @domain.certificate
+ %pre
+ = @domain.certificate.to_text
+ - else
+ .light
+ missing
diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example
index c6f06d43d07..f2bde602795 100644
--- a/config/gitlab.yml.example
+++ b/config/gitlab.yml.example
@@ -165,6 +165,8 @@ production: &base
host: example.com
port: 80 # Set to 443 if you serve the pages with HTTPS
https: false # Set to true if you serve the pages with HTTPS
+ # external_http: "1.1.1.1:80" # if defined notifies the GitLab pages do support Custom Domains
+ # external_https: "1.1.1.1:443" # if defined notifies the GitLab pages do support Custom Domains with Certificates
## Mattermost
## For enabling Add to Mattermost button
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 239aa662d9f..0015ddf902d 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -273,7 +273,8 @@ Settings.pages['https'] = false if Settings.pages['https'].nil?
Settings.pages['port'] ||= Settings.pages.https ? 443 : 80
Settings.pages['protocol'] ||= Settings.pages.https ? "https" : "http"
Settings.pages['url'] ||= Settings.send(:build_pages_url)
-Settings.pages['custom_domain'] ||= false if Settings.pages['custom_domain'].nil?
+Settings.pages['external_http'] ||= false if Settings.pages['external_http'].nil?
+Settings.pages['external_https'] ||= false if Settings.pages['external_https'].nil?
#
# Git LFS
diff --git a/config/routes/project.rb b/config/routes/project.rb
index 956a2d3186f..ac1e3fce16a 100644
--- a/config/routes/project.rb
+++ b/config/routes/project.rb
@@ -39,8 +39,8 @@ constraints(ProjectUrlConstrainer.new) do
end
end
- resource :pages, only: [:show, :update, :destroy] do
- delete :certificates
+ resources :pages, except: [:edit, :update] do
+ delete :remove_pages
end
resources :compare, only: [:index, :create] do