From 294f40e2c8f51239bfa0e3514e7fe4f3c8ae00cb Mon Sep 17 00:00:00 2001 From: Francisco Lopez Date: Thu, 24 Aug 2017 16:34:36 +0200 Subject: Added ssh fingerprint, gitlab ci and pages information in an instance configuration page Closes #25142 --- app/controllers/help_controller.rb | 4 + app/helpers/instance_configuration_helper.rb | 18 ++++ app/models/instance_configuration.rb | 71 ++++++++++++++ app/views/help/index.html.haml | 2 + app/views/help/instance_configuration.html.haml | 17 ++++ .../instance_configuration/_gitlab_ci.html.haml | 24 +++++ .../instance_configuration/_gitlab_pages.html.haml | 35 +++++++ .../instance_configuration/_ssh_info.html.haml | 27 +++++ .../unreleased/feature-ssh_host_fingerprint.yml | 5 + config/routes/help.rb | 9 +- spec/factories/instance_configuration.rb | 5 + spec/fixtures/ssh_host_example_key.pub | 1 + spec/helpers/instance_configuration_helper_spec.rb | 51 ++++++++++ spec/models/instance_configuration_spec.rb | 109 +++++++++++++++++++++ spec/views/help/index.html.haml_spec.rb | 8 ++ .../help/instance_configuration.html.haml_spec.rb | 29 ++++++ 16 files changed, 411 insertions(+), 4 deletions(-) create mode 100644 app/helpers/instance_configuration_helper.rb create mode 100644 app/models/instance_configuration.rb create mode 100644 app/views/help/instance_configuration.html.haml create mode 100644 app/views/help/instance_configuration/_gitlab_ci.html.haml create mode 100644 app/views/help/instance_configuration/_gitlab_pages.html.haml create mode 100644 app/views/help/instance_configuration/_ssh_info.html.haml create mode 100644 changelogs/unreleased/feature-ssh_host_fingerprint.yml create mode 100644 spec/factories/instance_configuration.rb create mode 100644 spec/fixtures/ssh_host_example_key.pub create mode 100644 spec/helpers/instance_configuration_helper_spec.rb create mode 100644 spec/models/instance_configuration_spec.rb create mode 100644 spec/views/help/instance_configuration.html.haml_spec.rb diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index 572915a4930..38f379dbf4f 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -57,6 +57,10 @@ class HelpController < ApplicationController def shortcuts end + def instance_configuration + @instance_configuration = InstanceConfiguration.new + end + def ui @user = User.new(id: 0, name: 'John Doe', username: '@johndoe') end diff --git a/app/helpers/instance_configuration_helper.rb b/app/helpers/instance_configuration_helper.rb new file mode 100644 index 00000000000..cee319f20bc --- /dev/null +++ b/app/helpers/instance_configuration_helper.rb @@ -0,0 +1,18 @@ +module InstanceConfigurationHelper + def instance_configuration_cell_html(value, &block) + return '-' unless value.to_s.presence + + block_given? ? yield(value) : value + end + + def instance_configuration_host(host) + @instance_configuration_host ||= instance_configuration_cell_html(host).capitalize + end + + # Value must be in bytes + def instance_configuration_human_size_cell(value) + instance_configuration_cell_html(value) do |v| + number_to_human_size(v, strip_insignificant_zeros: true, significant: false) + end + end +end diff --git a/app/models/instance_configuration.rb b/app/models/instance_configuration.rb new file mode 100644 index 00000000000..b30b707e5fe --- /dev/null +++ b/app/models/instance_configuration.rb @@ -0,0 +1,71 @@ +require 'resolv' + +class InstanceConfiguration + SSH_ALGORITHMS = %w(DSA ECDSA ED25519 RSA).freeze + SSH_ALGORITHMS_PATH = '/etc/ssh/'.freeze + CACHE_KEY = 'instance_configuration'.freeze + EXPIRATION_TIME = 24.hours + + def settings + @configuration ||= Rails.cache.fetch(CACHE_KEY, expires_in: EXPIRATION_TIME) do + { ssh_algorithms_hashes: ssh_algorithms_hashes, + host: host, + gitlab_pages: gitlab_pages, + gitlab_ci: gitlab_ci }.deep_symbolize_keys + end + end + + private + + def ssh_algorithms_hashes + SSH_ALGORITHMS.map { |algo| ssh_algorithm_hashes(algo) }.compact + end + + def host + Settings.gitlab.host + end + + def gitlab_pages + Settings.pages.to_h.merge(ip_address: resolv_dns(Settings.pages.host)) + end + + def resolv_dns(dns) + Resolv.getaddress(dns) + rescue Resolv::ResolvError + end + + def gitlab_ci + Settings.gitlab_ci + .to_h + .merge(artifacts_max_size: { value: Settings.artifacts.max_size&.megabytes, + default: 100.megabytes }) + end + + def ssh_algorithm_file(algorithm) + File.join(SSH_ALGORITHMS_PATH, "ssh_host_#{algorithm.downcase}_key.pub") + end + + def ssh_algorithm_hashes(algorithm) + content = ssh_algorithm_file_content(algorithm) + return unless content.present? + + { name: algorithm, + md5: ssh_algorithm_md5(content), + sha256: ssh_algorithm_sha256(content) } + end + + def ssh_algorithm_file_content(algorithm) + file = ssh_algorithm_file(algorithm) + return unless File.exist?(file) + + File.read(file) + end + + def ssh_algorithm_md5(ssh_file_content) + OpenSSL::Digest::MD5.hexdigest(ssh_file_content).scan(/../).join(':') + end + + def ssh_algorithm_sha256(ssh_file_content) + OpenSSL::Digest::SHA256.hexdigest(ssh_file_content) + end +end diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index c25eae63eec..d0c2e0b1d69 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -11,6 +11,7 @@ %span= Gitlab::VERSION %small= link_to Gitlab::REVISION, Gitlab::COM_URL + namespace_project_commits_path('gitlab-org', 'gitlab-ce', Gitlab::REVISION) = version_status_badge + %p.slead GitLab is open source software to collaborate on code. %br @@ -23,6 +24,7 @@ Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. %br Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank', rel: 'noopener noreferrer'}. + %p= link_to 'Check the current instance configuration ', help_instance_configuration_url %hr .row.prepend-top-default diff --git a/app/views/help/instance_configuration.html.haml b/app/views/help/instance_configuration.html.haml new file mode 100644 index 00000000000..f09e3825a4b --- /dev/null +++ b/app/views/help/instance_configuration.html.haml @@ -0,0 +1,17 @@ +- page_title 'Instance Configuration' +.wiki.documentation + %h1 Instance Configuration + + %p + In this page you will find information about the settings that are used in your current instance. + + = render 'help/instance_configuration/ssh_info' + = render 'help/instance_configuration/gitlab_pages' + = render 'help/instance_configuration/gitlab_ci' + %p + %strong Table of contents + + %ul + = content_for :table_content + + = content_for :settings_content diff --git a/app/views/help/instance_configuration/_gitlab_ci.html.haml b/app/views/help/instance_configuration/_gitlab_ci.html.haml new file mode 100644 index 00000000000..7fa8bd086d4 --- /dev/null +++ b/app/views/help/instance_configuration/_gitlab_ci.html.haml @@ -0,0 +1,24 @@ +- content_for :table_content do + %li= link_to 'GitLab CI', '#gitlab-ci' + +- content_for :settings_content do + %h2#gitlab-ci + GitLab CI + + %p + Below are the current settings regarding + = succeed('.') { link_to('GitLab CI', 'https://about.gitlab.com/gitlab-ci', target: '_blank') } + + .table-responsive + %table + %thead + %tr + %th Setting + %th= instance_configuration_host(@instance_configuration.settings[:host]) + %th Default + %tbody + %tr + - artifacts_size = @instance_configuration.settings[:gitlab_ci][:artifacts_max_size] + %td Artifacts maximum size + %td= instance_configuration_human_size_cell(artifacts_size[:value]) + %td= instance_configuration_human_size_cell(artifacts_size[:default]) diff --git a/app/views/help/instance_configuration/_gitlab_pages.html.haml b/app/views/help/instance_configuration/_gitlab_pages.html.haml new file mode 100644 index 00000000000..bdd77730dcc --- /dev/null +++ b/app/views/help/instance_configuration/_gitlab_pages.html.haml @@ -0,0 +1,35 @@ +- gitlab_pages = @instance_configuration.settings[:gitlab_pages] +- content_for :table_content do + %li= link_to 'GitLab Pages', '#gitlab-pages' + +- content_for :settings_content do + %h2#gitlab-pages + GitLab Pages + + %p + Below are the settings for + = succeed('.') { link_to('Gitlab Pages', gitlab_pages[:url], target: '_blank') } + .table-responsive + %table + %thead + %tr + %th Setting + %th= instance_configuration_host(@instance_configuration.settings[:host]) + %tbody + %tr + %td Domain Name + %td + %code= instance_configuration_cell_html(gitlab_pages[:host]) + %tr + %td IP Address + %td + %code= instance_configuration_cell_html(gitlab_pages[:ip_address]) + %tr + %td Port + %td + %code= instance_configuration_cell_html(gitlab_pages[:port]) + %br + + %p + The maximum size of your Pages site is regulated by the artifacts maximum + size which is part of #{succeed('.') { link_to('GitLab CI', '#gitlab-ci') }} diff --git a/app/views/help/instance_configuration/_ssh_info.html.haml b/app/views/help/instance_configuration/_ssh_info.html.haml new file mode 100644 index 00000000000..987cc61b3f6 --- /dev/null +++ b/app/views/help/instance_configuration/_ssh_info.html.haml @@ -0,0 +1,27 @@ +- ssh_info = @instance_configuration.settings[:ssh_algorithms_hashes] +- if ssh_info.any? + - content_for :table_content do + %li= link_to 'SSH host keys fingerprints', '#ssh-host-keys-fingerprints' + + - content_for :settings_content do + %h2#ssh-host-keys-fingerprints + SSH host keys fingerprints + + %p + Below are the fingerprints for the current instance SSH host keys. + + .table-responsive + %table + %thead + %tr + %th Algorithm + %th MD5 + %th SHA256 + %tbody + - ssh_info.each do |algorithm| + %tr + %td= algorithm[:name] + %td + %code= instance_configuration_cell_html(algorithm[:md5]) + %td + %code= instance_configuration_cell_html(algorithm[:sha256]) diff --git a/changelogs/unreleased/feature-ssh_host_fingerprint.yml b/changelogs/unreleased/feature-ssh_host_fingerprint.yml new file mode 100644 index 00000000000..04f9fd1d6ed --- /dev/null +++ b/changelogs/unreleased/feature-ssh_host_fingerprint.yml @@ -0,0 +1,5 @@ +--- +title: Automatic configuration settings page +merge_request: 13850 +author: Francisco Lopez +type: added diff --git a/config/routes/help.rb b/config/routes/help.rb index d53822da9ec..2ea8bfd7aed 100644 --- a/config/routes/help.rb +++ b/config/routes/help.rb @@ -1,4 +1,5 @@ -get 'help' => 'help#index' -get 'help/shortcuts' => 'help#shortcuts' -get 'help/ui' => 'help#ui' -get 'help/*path' => 'help#show', as: :help_page +get 'help' => 'help#index' +get 'help/shortcuts' => 'help#shortcuts' +get 'help/ui' => 'help#ui' +get 'help/instance_configuration' => 'help#instance_configuration' +get 'help/*path' => 'help#show', as: :help_page diff --git a/spec/factories/instance_configuration.rb b/spec/factories/instance_configuration.rb new file mode 100644 index 00000000000..406c7c3caf1 --- /dev/null +++ b/spec/factories/instance_configuration.rb @@ -0,0 +1,5 @@ +FactoryGirl.define do + factory :instance_configuration do + skip_create + end +end diff --git a/spec/fixtures/ssh_host_example_key.pub b/spec/fixtures/ssh_host_example_key.pub new file mode 100644 index 00000000000..6bac42b3ad0 --- /dev/null +++ b/spec/fixtures/ssh_host_example_key.pub @@ -0,0 +1 @@ +random content diff --git a/spec/helpers/instance_configuration_helper_spec.rb b/spec/helpers/instance_configuration_helper_spec.rb new file mode 100644 index 00000000000..5d716b9191d --- /dev/null +++ b/spec/helpers/instance_configuration_helper_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe InstanceConfigurationHelper do + describe '#instance_configuration_cell_html' do + describe 'if not block is passed' do + it 'returns the parameter if present' do + expect(helper.instance_configuration_cell_html('gitlab')).to eq('gitlab') + end + + it 'returns "-" if the parameter is blank' do + expect(helper.instance_configuration_cell_html(nil)).to eq('-') + expect(helper.instance_configuration_cell_html('')).to eq('-') + end + end + + describe 'if a block is passed' do + let(:upcase_block) { ->(value) { value.upcase } } + + it 'returns the result of the block' do + expect(helper.instance_configuration_cell_html('gitlab', &upcase_block)).to eq('GITLAB') + expect(helper.instance_configuration_cell_html('gitlab') { |v| v.upcase }).to eq('GITLAB') + end + + it 'returns "-" if the parameter is blank' do + expect(helper.instance_configuration_cell_html(nil, &upcase_block)).to eq('-') + expect(helper.instance_configuration_cell_html(nil) { |v| v.upcase }).to eq('-') + expect(helper.instance_configuration_cell_html('', &upcase_block)).to eq('-') + end + end + + it 'boolean are valid values to display' do + expect(helper.instance_configuration_cell_html(true)).to eq(true) + expect(helper.instance_configuration_cell_html(false)).to eq(false) + end + end + + describe '#instance_configuration_human_size_cell' do + it 'returns "-" if the parameter is blank' do + expect(helper.instance_configuration_human_size_cell(nil)).to eq('-') + expect(helper.instance_configuration_human_size_cell('')).to eq('-') + end + + it 'accepts the value in bytes' do + expect(helper.instance_configuration_human_size_cell(1024)).to eq('1 KB') + end + + it 'returns the value in human size readable format' do + expect(helper.instance_configuration_human_size_cell(1048576)).to eq('1 MB') + end + end +end diff --git a/spec/models/instance_configuration_spec.rb b/spec/models/instance_configuration_spec.rb new file mode 100644 index 00000000000..8548fff5c76 --- /dev/null +++ b/spec/models/instance_configuration_spec.rb @@ -0,0 +1,109 @@ +require 'spec_helper' + +RSpec.describe InstanceConfiguration do + context 'without cache' do + describe '#settings' do + describe '#ssh_algorithms_hashes' do + let(:md5) { '54:e0:f8:70:d6:4f:4c:b1:b3:02:44:77:cf:cd:0d:fc' } + let(:sha256) { '9327f0d15a48c4d9f6a3aee65a1825baf9a3412001c98169c5fd022ac27762fc' } + + it 'does not return anything if file does not exist' do + stub_pub_file(exist: false) + + expect(subject.settings[:ssh_algorithms_hashes]).to be_empty + end + + it 'does not return anything if file is empty' do + stub_pub_file + + allow(File).to receive(:read).and_return('') + + expect(subject.settings[:ssh_algorithms_hashes]).to be_empty + end + + it 'returns the md5 and sha256 if file valid and exists' do + stub_pub_file + + result = subject.settings[:ssh_algorithms_hashes].select { |o| o[:md5] == md5 && o[:sha256] == sha256 } + + expect(result.size).to eq(InstanceConfiguration::SSH_ALGORITHMS.size) + end + + def stub_pub_file(exist: true) + path = 'spec/fixtures/ssh_host_example_key.pub' + path << 'random' unless exist + allow(subject).to receive(:ssh_algorithm_file).and_return(Rails.root.join(path)) + end + end + + describe '#host' do + it 'returns current instance host' do + allow(Settings.gitlab).to receive(:host).and_return('exampledomain') + + expect(subject.settings[:host]).to eq(Settings.gitlab.host) + end + end + + describe '#gitlab_pages' do + let(:gitlab_pages) { subject.settings[:gitlab_pages] } + it 'returns Settings.pages' do + gitlab_pages.delete(:ip_address) + + expect(gitlab_pages).to eq(Settings.pages.symbolize_keys) + end + + it 'returns the Gitlab\'s pages host ip address' do + expect(gitlab_pages.keys).to include(:ip_address) + end + + it 'returns the ip address as nil if the domain is invalid' do + allow(Settings.pages).to receive(:host).and_return('exampledomain') + + expect(gitlab_pages[:ip_address]).to eq nil + end + + it 'returns the ip address of the domain' do + allow(Settings.pages).to receive(:host).and_return('localhost') + + expect(gitlab_pages[:ip_address]).to eq('127.0.0.1').or eq('::1') + end + end + + describe '#gitlab_ci' do + let(:gitlab_ci) { subject.settings[:gitlab_ci] } + it 'returns Settings.gitalb_ci' do + gitlab_ci.delete(:artifacts_max_size) + + expect(gitlab_ci).to eq(Settings.gitlab_ci.symbolize_keys) + end + + it 'returns the key artifacts_max_size' do + expect(gitlab_ci.keys).to include(:artifacts_max_size) + end + end + end + end + + context 'with cache', :use_clean_rails_memory_store_caching do + it 'caches settings content' do + expect(Rails.cache.read(described_class::CACHE_KEY)).to be_nil + + settings = subject.settings + + expect(Rails.cache.read(described_class::CACHE_KEY)).to eq(settings) + end + + describe 'cached settings' do + before do + subject.settings + end + + it 'expires after EXPIRATION_TIME' do + allow(Time).to receive(:now).and_return(Time.now + described_class::EXPIRATION_TIME) + Rails.cache.cleanup + + expect(Rails.cache.read(described_class::CACHE_KEY)).to eq(nil) + end + end + end +end diff --git a/spec/views/help/index.html.haml_spec.rb b/spec/views/help/index.html.haml_spec.rb index c030129559e..0a78606171d 100644 --- a/spec/views/help/index.html.haml_spec.rb +++ b/spec/views/help/index.html.haml_spec.rb @@ -25,6 +25,14 @@ describe 'help/index' do end end + describe 'instance configuration link' do + it 'is visible to guests' do + render + + expect(rendered).to have_link(nil, help_instance_configuration_url) + end + end + def stub_user(user = double) allow(view).to receive(:user_signed_in?).and_return(user) end diff --git a/spec/views/help/instance_configuration.html.haml_spec.rb b/spec/views/help/instance_configuration.html.haml_spec.rb new file mode 100644 index 00000000000..f30b5881fde --- /dev/null +++ b/spec/views/help/instance_configuration.html.haml_spec.rb @@ -0,0 +1,29 @@ +require 'rails_helper' + +describe 'help/instance_configuration' do + describe 'General Sections:' do + let(:instance_configuration) { build(:instance_configuration)} + let(:settings) { instance_configuration.settings } + let(:ssh_settings) { settings[:ssh_algorithms_hashes] } + + before do + assign(:instance_configuration, instance_configuration) + end + + it 'has links to several sections' do + render + + expect(rendered).to have_link(nil, '#ssh-host-keys-fingerprints') if ssh_settings.any? + expect(rendered).to have_link(nil, '#gitlab-pages') + expect(rendered).to have_link(nil, '#gitlab-ci') + end + + it 'has several sections' do + render + + expect(rendered).to have_css('h2#ssh-host-keys-fingerprints') if ssh_settings.any? + expect(rendered).to have_css('h2#gitlab-pages') + expect(rendered).to have_css('h2#gitlab-ci') + end + end +end -- cgit v1.2.3