Hello community, here is the log from the commit of package yast2-rmt for openSUSE:Factory checked in at 2018-04-06 17:50:05 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/yast2-rmt (Old) and /work/SRC/openSUSE:Factory/.yast2-rmt.new (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "yast2-rmt" Fri Apr 6 17:50:05 2018 rev:3 rq:593941 version:0.0.3 Changes: -------- --- /work/SRC/openSUSE:Factory/yast2-rmt/yast2-rmt.changes 2018-03-06 10:47:53.838963384 +0100 +++ /work/SRC/openSUSE:Factory/.yast2-rmt.new/yast2-rmt.changes 2018-04-06 17:52:22.661347465 +0200 @@ -1,0 +2,6 @@ +Tue Apr 3 08:16:37 UTC 2018 - [email protected] + +- version 0.0.3 +- Add setup steps for SSL certificate generation (fate#325115) + +------------------------------------------------------------------- Old: ---- yast2-rmt-0.0.2.tar.bz2 New: ---- yast2-rmt-0.0.3.tar.bz2 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ yast2-rmt.spec ++++++ --- /var/tmp/diff_new_pack.jadutX/_old 2018-04-06 17:52:23.305324182 +0200 +++ /var/tmp/diff_new_pack.jadutX/_new 2018-04-06 17:52:23.305324182 +0200 @@ -17,7 +17,7 @@ Name: yast2-rmt -Version: 0.0.2 +Version: 0.0.3 Release: 0 BuildArch: noarch @@ -58,6 +58,7 @@ %defattr(-,root,root) %{yast_dir}/clients/*.rb %{yast_dir}/lib/rmt +%{yast_dir}/data/rmt %doc COPYING %doc README.md ++++++ yast2-rmt-0.0.2.tar.bz2 -> yast2-rmt-0.0.3.tar.bz2 ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/.rubocop_todo.yml new/yast2-rmt-0.0.3/.rubocop_todo.yml --- old/yast2-rmt-0.0.2/.rubocop_todo.yml 2018-03-05 17:07:28.000000000 +0100 +++ new/yast2-rmt-0.0.3/.rubocop_todo.yml 2018-04-04 15:19:03.969121090 +0200 @@ -1,32 +1,21 @@ # This configuration was generated by # `rubocop --auto-gen-config` -# on 2018-02-26 13:22:06 +0100 using RuboCop version 0.52.1. +# on 2018-04-04 15:19:02 +0200 using RuboCop version 0.52.1. # The point is for the user to remove these configuration records # one by one as the offenses are removed from the code base. # Note that changes in the inspected code, or installation of new # versions of RuboCop, may require this file to be generated again. -# Offense count: 1 -Metrics/CyclomaticComplexity: - Max: 11 - # Offense count: 2 -# Configuration parameters: CountComments. -Metrics/MethodLength: - Max: 60 - -# Offense count: 1 -Metrics/PerceivedComplexity: - Max: 14 +# Configuration parameters: Max. +RSpec/ExampleLength: + Exclude: + - 'spec/features/**/*' + - 'spec/rmt/ssl/certificate_generator_spec.rb' + - 'spec/rmt/wizard_ssl_page_spec.rb' -# Offense count: 105 +# Offense count: 207 # Configuration parameters: . # SupportedStyles: have_received, receive RSpec/MessageSpies: EnforcedStyle: receive - -# Offense count: 2 -Style/MultipleComparison: - Exclude: - - 'src/lib/rmt/wizard_maria_db_page.rb' - - 'src/lib/rmt/wizard_scc_page.rb' diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/README.md new/yast2-rmt-0.0.3/README.md --- old/yast2-rmt-0.0.2/README.md 2018-03-05 17:07:28.000000000 +0100 +++ new/yast2-rmt-0.0.3/README.md 2018-03-12 17:02:15.344583881 +0100 @@ -28,4 +28,8 @@ ``` docker build -t yast-rmt-image . docker run -it yast-rmt-image rspec -``` \ No newline at end of file +``` + +### Package + +The package gets built for SLE15 here: https://build.opensuse.org/package/show/systemsmanagement:SCC:RMT/yast2-rmt diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/Rakefile new/yast2-rmt-0.0.3/Rakefile --- old/yast2-rmt-0.0.2/Rakefile 2018-03-05 17:07:28.000000000 +0100 +++ new/yast2-rmt-0.0.3/Rakefile 2018-03-27 15:09:30.067122856 +0200 @@ -23,7 +23,7 @@ conf.obs_project = 'systemsmanagement:SCC:RMT' # Default target for osc:build conf.obs_target = 'openSUSE_Factory' - conf.skip_license_check = [ %r{^Gemfile\.lock$} ] + conf.skip_license_check = [ %r{^Gemfile\.lock$}, %r{rmt.*\.cnf\.erb$} ] end # This is required, because `yast-travis-ruby` binary calls `rake test:unit` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/package/yast2-rmt.changes new/yast2-rmt-0.0.3/package/yast2-rmt.changes --- old/yast2-rmt-0.0.2/package/yast2-rmt.changes 2018-03-05 17:07:28.000000000 +0100 +++ new/yast2-rmt-0.0.3/package/yast2-rmt.changes 2018-04-03 10:17:50.038563438 +0200 @@ -1,4 +1,10 @@ ------------------------------------------------------------------- +Tue Apr 3 08:16:37 UTC 2018 - [email protected] + +- version 0.0.3 +- Add setup steps for SSL certificate generation (fate#325115) + +------------------------------------------------------------------- Mon Mar 5 15:12:56 UTC 2018 - [email protected] - version 0.0.2 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/package/yast2-rmt.spec new/yast2-rmt-0.0.3/package/yast2-rmt.spec --- old/yast2-rmt-0.0.2/package/yast2-rmt.spec 2018-03-05 17:07:28.000000000 +0100 +++ new/yast2-rmt-0.0.3/package/yast2-rmt.spec 2018-04-03 10:16:23.106209620 +0200 @@ -17,7 +17,7 @@ Name: yast2-rmt -Version: 0.0.2 +Version: 0.0.3 Release: 0 BuildArch: noarch @@ -58,6 +58,7 @@ %defattr(-,root,root) %{yast_dir}/clients/*.rb %{yast_dir}/lib/rmt +%{yast_dir}/data/rmt %doc COPYING %doc README.md diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/spec/rmt/execute_spec.rb new/yast2-rmt-0.0.3/spec/rmt/execute_spec.rb --- old/yast2-rmt-0.0.2/spec/rmt/execute_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/spec/rmt/execute_spec.rb 2018-04-04 13:29:22.337897712 +0200 @@ -0,0 +1,55 @@ +# Copyright (c) 2018 SUSE LLC. +# All Rights Reserved. + +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 or 3 of the GNU General +# Public License as published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. + +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com + +require 'rmt/execute' + +Yast.import 'Report' + +describe RMT::Execute do + describe '.on_target' do + let(:exit_double) { instance_double(Process::Status) } + + it 'executes the command' do + expect(described_class).to receive(:on_target!) + described_class.on_target + end + + it 'shows an error message when an exception ocurrs' do + expect(exit_double).to receive(:exitstatus).and_return(255) + expect(described_class).to receive(:on_target!).and_raise(Cheetah::ExecutionFailed.new('command', exit_double, '', 'Something went wrong')) + expect(Yast::Report).to receive(:Error) + described_class.on_target + end + end + + describe '.on_target!' do + let(:chroot) { '/tmp' } + + it 'appends chroot and runs command when args item is not a hash' do + expect(Yast::WFM).to receive(:scr_root).and_return(chroot) + expect(Cheetah).to receive(:run).with('cmd', { chroot: chroot }) + described_class.on_target!('cmd') + end + + it 'appends chroot and runs command when args item is a hash' do + expect(Yast::WFM).to receive(:scr_root).and_return(chroot) + expect(Cheetah).to receive(:run).with('cmd', { foo: 'bar', chroot: chroot }) + described_class.on_target!('cmd', { foo: 'bar' }) + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/spec/rmt/ssl/alternative_common_name_dialog_spec.rb new/yast2-rmt-0.0.3/spec/rmt/ssl/alternative_common_name_dialog_spec.rb --- old/yast2-rmt-0.0.2/spec/rmt/ssl/alternative_common_name_dialog_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/spec/rmt/ssl/alternative_common_name_dialog_spec.rb 2018-04-04 13:29:22.613899182 +0200 @@ -0,0 +1,64 @@ +# Copyright (c) 2018 SUSE LLC. +# All Rights Reserved. + +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 or 3 of the GNU General +# Public License as published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. + +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com + +require 'rmt/ssl/alternative_common_name_dialog' + +Yast.import 'Report' + +describe RMT::SSL::AlternativeCommonNameDialog do + subject(:dialog) { described_class.new } + + describe '#dialog_content' do + it 'creates the UI elements' do + expect(Yast::Term).to receive(:new).exactly(22).times + dialog.dialog_content + end + end + + describe '#user_input' do + it 'sets focus and waits for user input' do + expect(Yast::UI).to receive(:SetFocus).with(Id(:alt_name)) + expect_any_instance_of(UI::Dialog).to receive(:user_input) + dialog.user_input + end + end + + describe '#ok_handler' do + context 'when the alt name field is empty' do + let(:alt_name) { '' } + + it 'reports an error' do + expect(Yast::UI).to receive(:QueryWidget).with(Id(:alt_name), :Value).and_return(alt_name) + expect(Yast::UI).to receive(:SetFocus).with(Id(:alt_name)) + expect(Yast::Report).to receive(:Error).with('Alternative common name must not be empty.') + expect(dialog).not_to receive(:finish_dialog) + dialog.ok_handler + end + end + + context 'when the alt name field is not empty' do + let(:alt_name) { 'example.org' } + + it 'finishes the dialog and returns the alt name' do + expect(Yast::UI).to receive(:QueryWidget).with(Id(:alt_name), :Value).and_return(alt_name) + expect(dialog).to receive(:finish_dialog).with(alt_name) + dialog.ok_handler + end + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/spec/rmt/ssl/certificate_generator_spec.rb new/yast2-rmt-0.0.3/spec/rmt/ssl/certificate_generator_spec.rb --- old/yast2-rmt-0.0.2/spec/rmt/ssl/certificate_generator_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/spec/rmt/ssl/certificate_generator_spec.rb 2018-04-04 15:18:52.421068902 +0200 @@ -0,0 +1,161 @@ +# Copyright (c) 2018 SUSE LLC. +# All Rights Reserved. + +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 or 3 of the GNU General +# Public License as published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. + +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com + +require 'rmt/ssl/certificate_generator' + +Yast.import 'Report' + +describe RMT::SSL::CertificateGenerator do + subject(:generator) { described_class.new } + + let(:ssl_files) do + described_class::OPENSSL_FILES.map { |id, filename| [id, File.join(described_class::RMT_SSL_DIR, filename)] }.to_h + end + + let(:crt_and_key_files) { %i[ca_private_key ca_certificate server_private_key server_certificate] } + + describe '#check_certs_presence' do + subject(:result) { generator.check_certs_presence } + + before do + # Yast reads locale data at startup for i18n + expect(File).to receive(:exist?).with('/usr/share/YaST2/locale').and_return(false) + end + + it 'returns false when none of the files exist' do + crt_and_key_files.each do |file| + expect(File).to receive(:exist?).with(ssl_files[file]).and_return(false) + end + expect(result).to eq(false) + end + + it 'returns false when all of the files are empty' do + crt_and_key_files.each do |file| + expect(File).to receive(:exist?).with(ssl_files[file]).and_return(true) + expect(File).to receive(:zero?).with(ssl_files[file]).and_return(true) + end + expect(result).to eq(false) + end + + it 'returns true when one the files exist and is not empty' do + file = crt_and_key_files.shift + expect(File).to receive(:exist?).with(ssl_files[file]).and_return(true) + expect(File).to receive(:zero?).with(ssl_files[file]).and_return(false) + + expect(result).to eq(true) + end + end + + describe '#generate' do + let(:scr_path) { Yast.path('.target.string') } + let(:config_generator_double) { instance_double(RMT::SSL::ConfigGenerator) } + let(:ca_config) { 'ca_config' } + let(:server_config) { 'server_config' } + let(:ca_cert) { 'ca_cert' } + let(:server_cert) { 'server_cert' } + let(:common_name) { 'example.org' } + let(:alt_names) { ['foo.example.org', 'bar.example.org'] } + + it 'generates the certificate' do + expect(RMT::SSL::ConfigGenerator).to receive(:new).and_return(config_generator_double) + expect(config_generator_double).to receive(:make_ca_config) { ca_config } + expect(config_generator_double).to receive(:make_server_config) { server_config } + + expect(generator).to receive(:create_files) + + expect(Yast::SCR).to receive(:Write).with(scr_path, ssl_files[:ca_serial_file], '01') + expect(Yast::SCR).to receive(:Write).with(scr_path, ssl_files[:ca_config], ca_config) + expect(Yast::SCR).to receive(:Write).with(scr_path, ssl_files[:server_config], server_config) + + expect(RMT::Execute).to receive(:on_target!).with( + 'openssl', 'genrsa', '-out', + ssl_files[:ca_private_key], described_class::OPENSSL_KEY_BITS + ) + + expect(RMT::Execute).to receive(:on_target!).with( + 'openssl', 'genrsa', '-out', + ssl_files[:server_private_key], described_class::OPENSSL_KEY_BITS + ) + + expect(RMT::Execute).to receive(:on_target!).with( + 'openssl', 'req', '-x509', '-new', '-nodes', + '-key', ssl_files[:ca_private_key], '-sha256', '-days', described_class::OPENSSL_CA_VALIDITY_DAYS, + '-out', ssl_files[:ca_certificate], '-config', ssl_files[:ca_config] + ) + + expect(RMT::Execute).to receive(:on_target!).with( + 'openssl', 'req', '-new', '-key', ssl_files[:server_private_key], + '-out', ssl_files[:server_csr], '-config', ssl_files[:server_config] + ) + + expect(RMT::Execute).to receive(:on_target!).with( + 'openssl', 'x509', '-req', '-in', ssl_files[:server_csr], + '-out', ssl_files[:server_certificate], '-CA', ssl_files[:ca_certificate], + '-CAkey', ssl_files[:ca_private_key], '-days', described_class::OPENSSL_SERVER_CERT_VALIDITY_DAYS, + '-sha256', '-CAcreateserial', '-extensions', 'v3_server_sign', + '-extfile', ssl_files[:server_config] + ) + + expect(Yast::SCR).to receive(:Read).with(scr_path, ssl_files[:server_certificate]).and_return(server_cert) + expect(Yast::SCR).to receive(:Read).with(scr_path, ssl_files[:ca_certificate]).and_return(ca_cert) + expect(Yast::SCR).to receive(:Write).with(scr_path, ssl_files[:server_certificate], server_cert + ca_cert) + + expect(RMT::Execute).to receive(:on_target!).with('chown', 'root:nginx', ssl_files[:ca_certificate]) + expect(RMT::Execute).to receive(:on_target!).with('chmod', '0640', ssl_files[:ca_certificate]) + + generator.generate(common_name, alt_names) + end + + it 'handles Cheetah::ExecutionFailed exceptions' do + expect(RMT::SSL::ConfigGenerator).to receive(:new).and_raise(Cheetah::ExecutionFailed.new('cmd', 1, '', 'Dummy error')) + expect(Yast::Report).to receive(:Error).with("An error ocurred during SSL certificate generation:\nDummy error\n") + generator.generate(common_name, alt_names) + end + + it 'handles RMT::SSL::Exception exceptions' do + expect(RMT::SSL::ConfigGenerator).to receive(:new).and_raise(RMT::SSL::Exception.new('Dummy error')) + expect(Yast::Report).to receive(:Error).with("An error ocurred during SSL certificate generation:\nDummy error\n") + generator.generate(common_name, alt_names) + end + end + + describe '#create_files' do + it 'creates empty files for openssl and sets permissions' do + ssl_files.each_value do |file| + expect(generator).to receive(:write_file).with(file, '') + expect(RMT::Execute).to receive(:on_target!).with('chmod', '0600', file) + end + generator.send(:create_files) + end + end + + describe '#write_file' do + let(:filename) { '/tmp/test' } + let(:content) { 'test' } + + it 'writes a file' do + expect(Yast::SCR).to receive(:Write).with(Yast.path('.target.string'), filename, content).and_return(true) + generator.send(:write_file, filename, content) + end + + it 'raises and exception when write failed' do + expect(Yast::SCR).to receive(:Write).with(Yast.path('.target.string'), filename, content).and_return(false) + expect { generator.send(:write_file, filename, content) }.to raise_error(RMT::SSL::Exception, "Failed to write file #{filename}") + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/spec/rmt/ssl/config_generator_spec.rb new/yast2-rmt-0.0.3/spec/rmt/ssl/config_generator_spec.rb --- old/yast2-rmt-0.0.2/spec/rmt/ssl/config_generator_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/spec/rmt/ssl/config_generator_spec.rb 2018-04-04 13:29:22.681899544 +0200 @@ -0,0 +1,64 @@ +# Copyright (c) 2018 SUSE LLC. +# All Rights Reserved. + +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 or 3 of the GNU General +# Public License as published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. + +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com + +require 'rmt/ssl/config_generator' + +describe RMT::SSL::ConfigGenerator do + subject(:generator) { described_class.new(common_name, alt_names) } + + let(:common_name) { 'example.org' } + let(:dns_names) { ['foo.example.org', 'bar.example.org'] } + let(:ip_addresses) { ['1.1.1.1', '1111:2222:3333:4444:5555:6666:7777:8888'] } + let(:alt_names) { dns_names + ip_addresses } + + describe '#new' do + it 'matches DNS names' do + expect(generator.dns_alt_names).to eq([common_name] + dns_names) + end + + it 'matches IP addresses' do + expect(generator.ip_alt_names).to eq(ip_addresses) + end + end + + describe '#make_ca_config' do + it 'contains correct common name' do + expect(generator.make_ca_config).to match(/CN\s*=\s*RMT Certificate Authority \(#{common_name}\)/) + end + end + + describe '#make_server_config' do + subject(:config) { generator.make_server_config } + + it 'contains correct common name' do + expect(config).to match(/CN\s*=\s*#{common_name}/) + end + + it 'contains DNS alternative common names' do + dns_names.each do |alt_name| + expect(config).to match(/DNS\.\d+\s*=\s*#{alt_name}/) + end + end + + it 'contains IP alternative common names' do + ip_addresses.each do |alt_name| + expect(config).to match(/IP\.\d+\s*=\s*#{alt_name}/) + end + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/spec/rmt/wizard_maria_db_page_spec.rb new/yast2-rmt-0.0.3/spec/rmt/wizard_maria_db_page_spec.rb --- old/yast2-rmt-0.0.2/spec/rmt/wizard_maria_db_page_spec.rb 2018-03-05 17:07:28.000000000 +0100 +++ new/yast2-rmt-0.0.3/spec/rmt/wizard_maria_db_page_spec.rb 2018-04-04 15:27:00.475284854 +0200 @@ -29,7 +29,7 @@ describe '#render_content' do it 'renders UI elements' do - expect(Yast::Wizard).to receive(:SetNextButton).with(:next, Yast::Label.OKButton) + expect(Yast::Wizard).to receive(:SetNextButton).with(:next, Yast::Label.NextButton) expect(Yast::Wizard).to receive(:SetContents) expect(Yast::UI).to receive(:ChangeWidget).with(Id(:db_username), :Value, config['database']['username']) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/spec/rmt/wizard_spec.rb new/yast2-rmt-0.0.3/spec/rmt/wizard_spec.rb --- old/yast2-rmt-0.0.2/spec/rmt/wizard_spec.rb 2018-03-05 17:07:28.000000000 +0100 +++ new/yast2-rmt-0.0.3/spec/rmt/wizard_spec.rb 2018-03-27 15:39:21.355318371 +0200 @@ -28,6 +28,7 @@ let(:config) { { foo: 'bar' } } let(:scc_page_double) { instance_double(RMT::WizardSCCPage) } let(:db_page_double) { instance_double(RMT::WizardMariaDBPage) } + let(:ssl_page_double) { instance_double(RMT::WizardSSLPage) } it 'runs and goes through the sequence' do expect(Yast::Confirm).to receive(:MustBeRoot).and_return(true) @@ -42,6 +43,9 @@ expect(RMT::WizardMariaDBPage).to receive(:new).and_return(db_page_double) expect(db_page_double).to receive(:run).and_return(:next) + expect(RMT::WizardSSLPage).to receive(:new).and_return(ssl_page_double) + expect(ssl_page_double).to receive(:run).and_return(:next) + expect(Yast::UI).to receive(:CloseDialog) wizard.run end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/spec/rmt/wizard_ssl_page_spec.rb new/yast2-rmt-0.0.3/spec/rmt/wizard_ssl_page_spec.rb --- old/yast2-rmt-0.0.2/spec/rmt/wizard_ssl_page_spec.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/spec/rmt/wizard_ssl_page_spec.rb 2018-04-04 15:27:08.159320139 +0200 @@ -0,0 +1,263 @@ +# Copyright (c) 2018 SUSE LLC. +# All Rights Reserved. + +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 or 3 of the GNU General +# Public License as published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. + +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com + +require 'rmt/wizard_ssl_page' + +Yast.import 'Wizard' + +describe RMT::WizardSSLPage do + subject(:ssl_page) do + expect_any_instance_of(described_class).to receive(:query_alt_names).and_return(alt_names) + described_class.new(config) + end + + let(:config) { {} } + let(:generator_double) { instance_double(RMT::SSL::CertificateGenerator) } + let(:alt_names) { %w[rmt-main.example.com rmt-01.example.com] } + + before do + expect(RMT::SSL::CertificateGenerator).to receive(:new).and_return(generator_double) + end + + describe '#render_content' do + it 'renders UI elements' do + expect(Yast::Wizard).to receive(:SetNextButton).with(:next, Yast::Label.FinishButton) + expect(Yast::Wizard).to receive(:SetContents) + + expect(ssl_page).to receive(:query_common_name) + + ssl_page.render_content + end + end + + describe '#abort_handler' do + it 'finishes when cancel button is clicked' do + expect(ssl_page).to receive(:finish_dialog).with(:abort) + ssl_page.abort_handler + end + end + + describe '#back_handler' do + it 'goes back cancel button is clicked' do + expect(ssl_page).to receive(:finish_dialog).with(:back) + ssl_page.back_handler + end + end + + describe '#next_handler' do + let(:common_name) { 'rmt.example.com' } + let(:alt_names_items) { alt_names.map { |i| Yast::Term.new(:Item, i, i) } } + + it 'generates the certificates when next button is clicked' do + expect(Yast::UI).to receive(:QueryWidget).with(Id(:common_name), :Value).and_return(common_name) + expect(Yast::UI).to receive(:QueryWidget).with(Id(:alt_common_names), :Items).and_return(alt_names_items) + + expect(generator_double).to receive(:generate).with(common_name, alt_names) + + expect(ssl_page).to receive(:finish_dialog).with(:next) + ssl_page.next_handler + end + end + + describe '#add_alt_name_handler' do + let(:dialog_double) { instance_double(RMT::SSL::AlternativeCommonNameDialog) } + + context 'when alt name dialog is canceled' do + it 'displays add alt name dialog' do + expect(RMT::SSL::AlternativeCommonNameDialog).to receive(:new).and_return(dialog_double) + expect(dialog_double).to receive(:run).and_return(nil) + ssl_page.add_alt_name_handler + end + end + + context 'when alt name dialog returns an alt name' do + let(:alt_name) { 'alt-name.example.org' } + + it 'displays add alt name dialog' do + expect(RMT::SSL::AlternativeCommonNameDialog).to receive(:new).and_return(dialog_double) + expect(dialog_double).to receive(:run).and_return(alt_name) + expect(Yast::UI).to receive(:ChangeWidget).with(Id(:alt_common_names), :Items, alt_names + [alt_name]) + ssl_page.add_alt_name_handler + end + end + end + + describe '#remove_alt_name_handler' do + context 'when no item is selected' do + it "doesn't do anything" do + expect(Yast::UI).to receive(:QueryWidget).with(Id(:alt_common_names), :CurrentItem).and_return(nil) + ssl_page.remove_alt_name_handler + end + end + + context 'when an item is selected' do + it 'removes the selected item' do + expect(Yast::UI).to receive(:QueryWidget).with(Id(:alt_common_names), :CurrentItem).and_return(alt_names[0]) + expect(Yast::UI).to receive(:ChangeWidget).with(Id(:alt_common_names), :Items, [alt_names[1]]) + expect(Yast::UI).to receive(:ChangeWidget).with(Id(:alt_common_names), :CurrentItem, alt_names[1]) + ssl_page.remove_alt_name_handler + end + end + end + + describe '#run' do + context 'when certificates are already present' do + it 'shows a message and finishes' do + expect(generator_double).to receive(:check_certs_presence).and_return(true) + expect(Yast::Popup).to receive(:Message).with('SSL certificates already present, skipping generation.') + expect(ssl_page).to receive(:finish_dialog).with(:next) + ssl_page.run + end + end + + context 'when certificates are not present' do + it 'renders content and enters the event loop' do + expect(generator_double).to receive(:check_certs_presence).and_return(false) + expect(ssl_page).to receive(:render_content) + expect(ssl_page).to receive(:event_loop) + ssl_page.run + end + end + end + + describe '#query_common_name' do + it 'queries the long hostname' do + expect(RMT::Execute).to receive(:on_target!).with('hostname', '--long', stdout: :capture).and_return("\n\n\nexample.org\n\n") + expect(ssl_page.send(:query_common_name)).to eq('example.org') + end + end + + describe '#query_alt_names' do + subject(:ssl_page) do + expect(RMT::Execute).to receive(:on_target!).and_return('').exactly(2).times + described_class.new(config) + end + + let(:ipv4s) { %w[1.1.1.1 2.2.2.2 3.3.3.3] } + let(:ipv6s) { %w[1111:2222:3333:4444:5555:6666:7777:8888 8888:7777:6666:5555:4444:3333:2222:1111] } + let(:dns_name_1) { 'foo.example.org' } + let(:dns_name_2) { 'bar.example.org' } + + it 'queries IPs and DNS names' do + ipv4s.each do |ipv4| + expect(ssl_page).to receive(:query_dns_entries).with(ipv4).and_return([dns_name_1]) + end + + ipv6s.each do |ipv6| + expect(ssl_page).to receive(:query_dns_entries).with(ipv6).and_return([dns_name_2]) + end + + expect(RMT::Execute).to receive(:on_target!).with( + ['ip', '-f', 'inet', '-o', 'addr', 'show', 'scope', 'global'], + ['awk', '{print $4}'], + ['awk', '-F', '/', '{print $1}'], + ['tr', '\\n', ','], + { stdout: :capture } + ).and_return(ipv4s.join(',')) + + expect(RMT::Execute).to receive(:on_target!).with( + ['ip', '-f', 'inet6', '-o', 'addr', 'show', 'scope', 'global'], + ['awk', '{print $4}'], + ['awk', '-F', '/', '{print $1}'], + ['tr', '\\n', ','], + { stdout: :capture } + ).and_return(ipv6s.join(',')) + + + expect(ssl_page.send(:query_alt_names)).to eq([dns_name_1] + [dns_name_2] + ipv4s + ipv6s) + end + + it 'handles exceptions and writes errors to log' do + expect(RMT::Execute).to receive(:on_target!).with( + ['ip', '-f', 'inet', '-o', 'addr', 'show', 'scope', 'global'], + ['awk', '{print $4}'], + ['awk', '-F', '/', '{print $1}'], + ['tr', '\\n', ','], + { stdout: :capture } + ).and_raise(Cheetah::ExecutionFailed.new('command', 255, '', 'Something went wrong')) + + expect(RMT::Execute).to receive(:on_target!).with( + ['ip', '-f', 'inet6', '-o', 'addr', 'show', 'scope', 'global'], + ['awk', '{print $4}'], + ['awk', '-F', '/', '{print $1}'], + ['tr', '\\n', ','], + { stdout: :capture } + ).and_raise(Cheetah::ExecutionFailed.new('command', 255, '', 'Something went wrong')) + + expect_any_instance_of(Yast::Y2Logger).to receive(:warn).with('Failed to obtain IP addresses: Something went wrong').exactly(2).times + + expect(ssl_page.send(:query_alt_names)).to eq([]) + end + end + + describe '#query_dns_entries' do + it 'queries DNS' do + expect(RMT::Execute).to receive(:on_target!).with( + ['dig', '+noall', '+answer', '+time=2', '+tries=1', '-x', 'foo'], + ['awk', '{print $5}'], + ['sed', 's/\\.$//'], + ['tr', '\\n', '|'], + { stdout: :capture } + ).and_return('foo.example.org') + + expect(ssl_page.send(:query_dns_entries, 'foo')).to eq(['foo.example.org']) + end + + it 'queries hosts file when there are no DNS results' do + expect(RMT::Execute).to receive(:on_target!).with( + ['dig', '+noall', '+answer', '+time=2', '+tries=1', '-x', 'foo'], + ['awk', '{print $5}'], + ['sed', 's/\\.$//'], + ['tr', '\\n', '|'], + { stdout: :capture } + ).and_return('') + + expect(RMT::Execute).to receive(:on_target!).with( + ['getent', 'hosts', 'foo'], + ['awk', '{print $2}'], + ['sed', 's/\\.$//'], + ['tr', '\\n', '|'], + { stdout: :capture } + ).and_return('bar.example.org') + + expect(ssl_page.send(:query_dns_entries, 'foo')).to eq(['bar.example.org']) + end + + it 'handles exceptions and writes errors to log' do + expect(RMT::Execute).to receive(:on_target!).with( + ['dig', '+noall', '+answer', '+time=2', '+tries=1', '-x', 'foo'], + ['awk', '{print $5}'], + ['sed', 's/\\.$//'], + ['tr', '\\n', '|'], + { stdout: :capture } + ).and_raise(Cheetah::ExecutionFailed.new('command', 255, '', 'Something went wrong')) + + expect(RMT::Execute).to receive(:on_target!).with( + ['getent', 'hosts', 'foo'], + ['awk', '{print $2}'], + ['sed', 's/\\.$//'], + ['tr', '\\n', '|'], + { stdout: :capture } + ).and_raise(Cheetah::ExecutionFailed.new('command', 255, '', 'Something went wrong')) + + expect_any_instance_of(Yast::Y2Logger).to receive(:warn).with('Failed to obtain host names: Something went wrong').exactly(2).times + + expect(ssl_page.send(:query_dns_entries, 'foo')).to eq(nil) + end + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/src/data/rmt/rmt-ca.cnf.erb new/yast2-rmt-0.0.3/src/data/rmt/rmt-ca.cnf.erb --- old/yast2-rmt-0.0.2/src/data/rmt/rmt-ca.cnf.erb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/src/data/rmt/rmt-ca.cnf.erb 2018-03-27 16:27:14.584957686 +0200 @@ -0,0 +1,41 @@ +[ca] +default_ca = CA_default + +[CA_default] +default_bits = 2048 +x509_extensions = v3_ca +default_days = 3650 +default_md = default +policy = policy_optional +copy_extensions = copy +unique_subject = no + +[policy_optional] +countryName = optional +stateOrProvinceName = optional +localityName = optional +organizationName = optional +organizationalUnitName = optional +commonName = optional +emailAddress = optional + +############################################### + +[req] +default_bits = 2048 +distinguished_name = req_distinguished_name +x509_extensions = v3_ca +string_mask = utf8only +prompt = no + +[v3_ca] +basicConstraints = critical, CA:true +nsComment = "RMT Generated CA Certificate" +nsCertType = sslCA +keyUsage = cRLSign, keyCertSign +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid:always,issuer + +############################################### +[ req_distinguished_name ] +CN = <%= ca_common_name %> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/src/data/rmt/rmt-server-cert.cnf.erb new/yast2-rmt-0.0.3/src/data/rmt/rmt-server-cert.cnf.erb --- old/yast2-rmt-0.0.2/src/data/rmt/rmt-server-cert.cnf.erb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/src/data/rmt/rmt-server-cert.cnf.erb 2018-03-27 16:27:14.584957686 +0200 @@ -0,0 +1,33 @@ +[req] +default_bits = 2048 +distinguished_name = req_distinguished_name +x509_extensions = v3_server_sign +string_mask = utf8only +prompt = no +req_extensions = v3_req + +[v3_server_sign] +basicConstraints = CA:false +nsComment = "RMT Generated Server Certificate" +nsCertType = server +keyUsage = digitalSignature, keyEncipherment, keyAgreement +extendedKeyUsage = serverAuth, clientAuth +subjectKeyIdentifier = hash +authorityKeyIdentifier = keyid,issuer:always +subjectAltName = @alt_names + +[v3_req] +basicConstraints = CA:false +keyUsage = digitalSignature, keyEncipherment, keyAgreement +subjectAltName = @alt_names + +[req_distinguished_name] +CN = <%= server_common_name %> + +[alt_names] +<% dns_alt_names.each_with_index do |alt_name, index| %> +DNS.<%= index %> = <%= alt_name %> +<% end %> +<% ip_alt_names.each_with_index do |alt_name, index| %> +IP.<%= index %> = <%= alt_name %> +<% end %> diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/src/lib/rmt/execute.rb new/yast2-rmt-0.0.3/src/lib/rmt/execute.rb --- old/yast2-rmt-0.0.2/src/lib/rmt/execute.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/src/lib/rmt/execute.rb 2018-03-29 15:04:01.487495148 +0200 @@ -0,0 +1,65 @@ +# Copyright (c) 2018 SUSE LLC. +# All Rights Reserved. + +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 or 3 of the GNU General +# Public License as published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. + +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com + +require 'yast' +require 'cheetah' + +module RMT; end + +# This module is copypasta from version 4 of Yast::Execute. +# Leap 42.* and SLE12 has Yast version 3 which doesn't have `on_target!` method. +# Ideally. this needs to be removed and replaced with Yast::Execute once Leap 15 and SLE15 are out. +class RMT::Execute + Cheetah.default_options = { logger: Yast::Y2Logger.instance } + + extend Yast::I18n + textdomain 'rmt' + + def self.on_target(*args) + popup_error { on_target!(*args) } + end + + def self.on_target!(*args) + root = Yast::WFM.scr_root + + if args.last.is_a? ::Hash + args.last[:chroot] = root + else + args.push(chroot: root) + end + + Cheetah.run(*args) + end + + private_class_method def self.popup_error(&block) + block.call + rescue Cheetah::ExecutionFailed => e + Yast.import 'Report' + Yast::Report.Error( + _( + "Execution of command \"%<command>s\" failed.\n"\ + "Exit code: %<exitcode>s\n"\ + 'Error output: %<stderr>s' + ) % { + command: e.commands.inspect, + exitcode: e.status.exitstatus, + stderr: e.stderr + } + ) + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/src/lib/rmt/ssl/alternative_common_name_dialog.rb new/yast2-rmt-0.0.3/src/lib/rmt/ssl/alternative_common_name_dialog.rb --- old/yast2-rmt-0.0.2/src/lib/rmt/ssl/alternative_common_name_dialog.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/src/lib/rmt/ssl/alternative_common_name_dialog.rb 2018-04-04 11:49:54.293973889 +0200 @@ -0,0 +1,69 @@ +# Copyright (c) 2018 SUSE LLC. +# All Rights Reserved. + +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 or 3 of the GNU General +# Public License as published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. + +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com + +require 'rmt/utils' +require 'ui/dialog' + +module RMT; end +module RMT::SSL; end + +class RMT::SSL::AlternativeCommonNameDialog < UI::Dialog + def initialize + textdomain 'rmt' + end + + def dialog_content + VBox( + VSpacing(1), + Heading(_('Add an alternative common name')), + VSpacing(1), + HBox( + HSpacing(2), + VBox( + Label(_('Please provide the hostname or IP address.')), + MinWidth(15, InputField(Id(:alt_name), _('Alternative name'))) + ), + HSpacing(2) + ), + VSpacing(1), + HBox( + PushButton(Id(:cancel), Opt(:key_F9), Yast::Label.CancelButton), + HSpacing(2), + PushButton(Id(:ok), Opt(:default, :key_F10), Yast::Label.OKButton) + ), + VSpacing(1) + ) + end + + def user_input + Yast::UI.SetFocus(Id(:alt_name)) + super + end + + def ok_handler + alt_name = Yast::UI.QueryWidget(Id(:alt_name), :Value) + + if !alt_name || alt_name.empty? + Yast::UI.SetFocus(Id(:alt_name)) + Yast::Report.Error(_('Alternative common name must not be empty.')) + return + end + + finish_dialog(alt_name) + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/src/lib/rmt/ssl/certificate_generator.rb new/yast2-rmt-0.0.3/src/lib/rmt/ssl/certificate_generator.rb --- old/yast2-rmt-0.0.2/src/lib/rmt/ssl/certificate_generator.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/src/lib/rmt/ssl/certificate_generator.rb 2018-04-04 15:17:14.972628767 +0200 @@ -0,0 +1,124 @@ +# Copyright (c) 2018 SUSE LLC. +# All Rights Reserved. + +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 or 3 of the GNU General +# Public License as published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. + +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com + +require 'erb' +require 'resolv' +require 'rmt/execute' +require 'rmt/ssl/config_generator' + +module RMT; end +module RMT::SSL; end + +class RMT::SSL::Exception < RuntimeError; end + +class RMT::SSL::CertificateGenerator + RMT_SSL_DIR = '/usr/share/rmt/ssl/'.freeze + + OPENSSL_FILES = { + ca_config: 'rmt-ca.cnf', + ca_private_key: 'rmt-ca.key', + ca_certificate: 'rmt-ca.crt', + ca_serial_file: 'rmt-ca.srl', + server_config: 'rmt-server.cnf', + server_private_key: 'rmt-server.key', + server_certificate: 'rmt-server.crt', + server_csr: 'rmt-server.csr' + }.freeze + + OPENSSL_KEY_BITS = 2048 + OPENSSL_CA_VALIDITY_DAYS = 1825 + OPENSSL_SERVER_CERT_VALIDITY_DAYS = 1825 + + def initialize + extend Yast::I18n + textdomain 'rmt' + + @ssl_paths = OPENSSL_FILES.map { |id, filename| [id, File.join(RMT_SSL_DIR, filename)] }.to_h + end + + def check_certs_presence + %i[ca_private_key ca_certificate server_private_key server_certificate].each do |file_type| + return true if File.exist?(@ssl_paths[file_type]) && !File.zero?(@ssl_paths[file_type]) + end + + false + end + + def generate(common_name, alt_names) + config_generator = RMT::SSL::ConfigGenerator.new(common_name, alt_names) + + create_files + + Yast::SCR.Write(Yast.path('.target.string'), @ssl_paths[:ca_serial_file], '01') + Yast::SCR.Write(Yast.path('.target.string'), @ssl_paths[:ca_config], config_generator.make_ca_config) + Yast::SCR.Write(Yast.path('.target.string'), @ssl_paths[:server_config], config_generator.make_server_config) + + RMT::Execute.on_target!('openssl', 'genrsa', '-out', @ssl_paths[:ca_private_key], OPENSSL_KEY_BITS) + RMT::Execute.on_target!('openssl', 'genrsa', '-out', @ssl_paths[:server_private_key], OPENSSL_KEY_BITS) + + RMT::Execute.on_target!( + 'openssl', 'req', '-x509', '-new', '-nodes', '-key', @ssl_paths[:ca_private_key], + '-sha256', '-days', OPENSSL_CA_VALIDITY_DAYS, '-out', @ssl_paths[:ca_certificate], + '-config', @ssl_paths[:ca_config] + ) + + RMT::Execute.on_target!( + 'openssl', 'req', '-new', '-key', @ssl_paths[:server_private_key], + '-out', @ssl_paths[:server_csr], '-config', @ssl_paths[:server_config] + ) + + RMT::Execute.on_target!( + 'openssl', 'x509', '-req', '-in', @ssl_paths[:server_csr], '-out', @ssl_paths[:server_certificate], + '-CA', @ssl_paths[:ca_certificate], '-CAkey', @ssl_paths[:ca_private_key], + '-days', OPENSSL_SERVER_CERT_VALIDITY_DAYS, '-sha256', + '-CAcreateserial', + '-extensions', 'v3_server_sign', '-extfile', @ssl_paths[:server_config] + ) + + # create certificates bundle + server_cert = Yast::SCR.Read(Yast.path('.target.string'), @ssl_paths[:server_certificate]) + ca_cert = Yast::SCR.Read(Yast.path('.target.string'), @ssl_paths[:ca_certificate]) + Yast::SCR.Write(Yast.path('.target.string'), @ssl_paths[:server_certificate], server_cert + ca_cert) + + # change permissions so that clients can download CA certificate + RMT::Execute.on_target!('chown', 'root:nginx', @ssl_paths[:ca_certificate]) + RMT::Execute.on_target!('chmod', '0640', @ssl_paths[:ca_certificate]) + rescue Cheetah::ExecutionFailed, RMT::SSL::Exception => e + Yast.import 'Report' + Yast::Report.Error( + _("An error ocurred during SSL certificate generation:\n%<error>s\n") % { + error: (e.class == Cheetah::ExecutionFailed) ? e.stderr : e.to_s + } + ) + end + + protected + + # Creates empty files and sets 600 permissions + def create_files + @ssl_paths.each_value do |file| + write_file(file, '') + RMT::Execute.on_target!('chmod', '0600', file) + end + end + + def write_file(filename, content) + result = Yast::SCR.Write(Yast.path('.target.string'), filename, content) + raise RMT::SSL::Exception, "Failed to write file #{filename}" unless result + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/src/lib/rmt/ssl/config_generator.rb new/yast2-rmt-0.0.3/src/lib/rmt/ssl/config_generator.rb --- old/yast2-rmt-0.0.2/src/lib/rmt/ssl/config_generator.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/src/lib/rmt/ssl/config_generator.rb 2018-04-04 12:19:49.162539813 +0200 @@ -0,0 +1,54 @@ +# Copyright (c) 2018 SUSE LLC. +# All Rights Reserved. + +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 or 3 of the GNU General +# Public License as published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. + +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com + +require 'erb' +require 'resolv' + +module RMT; end +module RMT::SSL; end + +class RMT::SSL::ConfigGenerator + attr_reader :ca_common_name, :server_common_name, :dns_alt_names, :ip_alt_names + + def initialize(hostname, alt_names) + @ca_common_name = "RMT Certificate Authority (#{hostname})" + @server_common_name = hostname + @dns_alt_names = [] + @ip_alt_names = [] + @templates_dir = File.expand_path('./../../../data/rmt', __dir__) + + alt_names.unshift(@server_common_name) unless alt_names.include?(@server_common_name) + alt_names.each do |alt_name| + if (alt_name.match(Resolv::IPv4::Regex) || alt_name.match(Resolv::IPv6::Regex)) + @ip_alt_names << alt_name + else + @dns_alt_names << alt_name + end + end + end + + def make_ca_config + template = File.read(File.join(@templates_dir, 'rmt-ca.cnf.erb')) + ERB.new(template).result(binding) + end + + def make_server_config + template = File.read(File.join(@templates_dir, 'rmt-server-cert.cnf.erb')) + ERB.new(template, nil, '<>').result(binding) + end +end diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/src/lib/rmt/utils.rb new/yast2-rmt-0.0.3/src/lib/rmt/utils.rb --- old/yast2-rmt-0.0.2/src/lib/rmt/utils.rb 2018-03-05 17:07:28.000000000 +0100 +++ new/yast2-rmt-0.0.3/src/lib/rmt/utils.rb 2018-04-04 12:41:15.521020233 +0200 @@ -20,8 +20,7 @@ Yast.import 'Report' -module RMT -end +module RMT; end class RMT::Utils include Yast::Logger @@ -63,7 +62,7 @@ # Runs a command and returns the exit code def run_command(command, *params) - params = params.map { |p| String.Quote(p) } + params = params.map { |p| Yast::String.Quote(p) } Yast::SCR.Execute( Yast.path('.target.bash'), diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/src/lib/rmt/wizard.rb new/yast2-rmt-0.0.3/src/lib/rmt/wizard.rb --- old/yast2-rmt-0.0.2/src/lib/rmt/wizard.rb 2018-03-05 17:07:28.000000000 +0100 +++ new/yast2-rmt-0.0.3/src/lib/rmt/wizard.rb 2018-04-04 15:21:33.149795816 +0200 @@ -19,6 +19,7 @@ require 'rmt/utils' require 'rmt/wizard_scc_page' require 'rmt/wizard_maria_db_page' +require 'rmt/wizard_ssl_page' module RMT end @@ -40,28 +41,20 @@ @config = RMT::Utils.read_config_file end - def step1 - page = RMT::WizardSCCPage.new(@config) - page.run - end - - def step2 - page = RMT::WizardMariaDBPage.new(@config) - page.run - end - def run return unless Yast::Confirm.MustBeRoot aliases = { - 'step1' => -> { step1 }, - 'step2' => -> { step2 } + 'step1' => -> { RMT::WizardSCCPage.new(@config).run }, + 'step2' => -> { RMT::WizardMariaDBPage.new(@config).run }, + 'step3' => -> { RMT::WizardSSLPage.new(@config).run } } sequence = { 'ws_start' => 'step1', 'step1' => { abort: :abort, next: 'step2' }, - 'step2' => { abort: :abort, next: :next } + 'step2' => { abort: :abort, next: 'step3' }, + 'step3' => { abort: :abort, next: :next } } Wizard.CreateDialog() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/src/lib/rmt/wizard_maria_db_page.rb new/yast2-rmt-0.0.3/src/lib/rmt/wizard_maria_db_page.rb --- old/yast2-rmt-0.0.2/src/lib/rmt/wizard_maria_db_page.rb 2018-03-05 17:07:28.000000000 +0100 +++ new/yast2-rmt-0.0.3/src/lib/rmt/wizard_maria_db_page.rb 2018-04-04 15:26:06.627037592 +0200 @@ -49,9 +49,9 @@ ) ) - Wizard.SetNextButton(:next, Label.OKButton) + Wizard.SetNextButton(:next, Label.NextButton) Wizard.SetContents( - _('RMT configuration step 2/2'), + _('RMT configuration step 2/3'), contents, '<p>This step of the wizard performs the necessary database setup.</p>', true, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/src/lib/rmt/wizard_scc_page.rb new/yast2-rmt-0.0.3/src/lib/rmt/wizard_scc_page.rb --- old/yast2-rmt-0.0.2/src/lib/rmt/wizard_scc_page.rb 2018-03-05 17:07:28.000000000 +0100 +++ new/yast2-rmt-0.0.3/src/lib/rmt/wizard_scc_page.rb 2018-04-04 15:26:11.315059118 +0200 @@ -54,7 +54,7 @@ ) Wizard.SetContents( - _('RMT configuration step 1/2'), + _('RMT configuration step 1/3'), contents, "<p>Organization credentials can be found on Organization page at <a href='https://scc.suse.com/'>SUSE Customer Center</a>.</p>", true, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/yast2-rmt-0.0.2/src/lib/rmt/wizard_ssl_page.rb new/yast2-rmt-0.0.3/src/lib/rmt/wizard_ssl_page.rb --- old/yast2-rmt-0.0.2/src/lib/rmt/wizard_ssl_page.rb 1970-01-01 01:00:00.000000000 +0100 +++ new/yast2-rmt-0.0.3/src/lib/rmt/wizard_ssl_page.rb 2018-04-04 15:24:17.146538672 +0200 @@ -0,0 +1,189 @@ +# Copyright (c) 2018 SUSE LLC. +# All Rights Reserved. + +# This program is free software; you can redistribute it and/or +# modify it under the terms of version 2 or 3 of the GNU General +# Public License as published by the Free Software Foundation. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, contact SUSE LLC. + +# To contact SUSE about this file by physical or electronic mail, +# you may find current contact information at www.suse.com + +require 'rmt/ssl/alternative_common_name_dialog' +require 'rmt/ssl/config_generator' +require 'rmt/ssl/certificate_generator' +require 'rmt/execute' +require 'ui/event_dispatcher' + +module RMT; end + +class RMT::WizardSSLPage < Yast::Client + include ::UI::EventDispatcher + include Yast::Logger + + def initialize(config) + textdomain 'rmt' + @config = config + @alt_names = query_alt_names + @cert_generator = RMT::SSL::CertificateGenerator.new + end + + def render_content + common_name = query_common_name + + contents = Frame( + _('SSL certificate generation'), + HBox( + HSpacing(1), + VBox( + VSpacing(1), + Left( + HSquash( + MinWidth(30, InputField(Id(:common_name), _('Common name'), common_name)) + ) + ), + VSpacing(1), + SelectionBox( + Id(:alt_common_names), + _('&Alternative common names:'), + @alt_names + ), + VSpacing(1), + HBox( + PushButton(Id(:add_alt_name), Opt(:default, :key_F5), _('Add')), + PushButton(Id(:remove_alt_name), Opt(:default, :key_F6), _('Remove selected')) + ) + ), + HSpacing(1) + ) + ) + + Wizard.SetNextButton(:next, Label.FinishButton) + Wizard.SetContents( + _('RMT configuration step 3/3'), + contents, + '<p>This step of the wizard generates the required SSL certificates.</p>', + true, + true + ) + end + + def abort_handler + finish_dialog(:abort) + end + + def back_handler + finish_dialog(:back) + end + + def next_handler + common_name = UI.QueryWidget(Id(:common_name), :Value) + alt_names_items = UI.QueryWidget(Id(:alt_common_names), :Items) + alt_names = alt_names_items.map { |item| item.params[1] } + + @cert_generator.generate(common_name, alt_names) + + finish_dialog(:next) + end + + def add_alt_name_handler + dialog = RMT::SSL::AlternativeCommonNameDialog.new + alt_name = dialog.run + + return unless alt_name + @alt_names << alt_name + UI::ChangeWidget(Id(:alt_common_names), :Items, @alt_names) + end + + def remove_alt_name_handler + selected_alt_name = UI.QueryWidget(Id(:alt_common_names), :CurrentItem) + return unless selected_alt_name + + selected_index = @alt_names.find_index(selected_alt_name) + return unless selected_index + + @alt_names.reject! { |item| item == selected_alt_name } + selected_index = (selected_index >= @alt_names.size) ? @alt_names.size - 1 : selected_index + + UI::ChangeWidget(Id(:alt_common_names), :Items, @alt_names) + UI::ChangeWidget(Id(:alt_common_names), :CurrentItem, @alt_names[selected_index]) + end + + def run + if @cert_generator.check_certs_presence + Yast::Popup.Message('SSL certificates already present, skipping generation.') + return finish_dialog(:next) + end + render_content + event_loop + end + + protected + + def query_common_name + output = RMT::Execute.on_target!('hostname', '--long', stdout: :capture) + output.strip + end + + def query_alt_names + ips = [] + + %w[inet inet6].each do |addr_type| + begin + output = RMT::Execute.on_target!( + ['ip', '-f', addr_type, '-o', 'addr', 'show', 'scope', 'global'], + ['awk', '{print $4}'], + ['awk', '-F', '/', '{print $1}'], + ['tr', '\n', ','], + stdout: :capture + ) + + ips += output.split(',').compact + rescue Cheetah::ExecutionFailed => e + log.warn "Failed to obtain IP addresses: #{e.stderr}" + end + end + + dns_entries = ips.flat_map { |ip| query_dns_entries(ip) }.uniq.compact + dns_entries + ips + end + + def query_dns_entries(ip) + commands = [ + [ + ['dig', '+noall', '+answer', '+time=2', '+tries=1', '-x', ip], + ['awk', '{print $5}'], + ['sed', 's/\\.$//'], + ['tr', '\n', '|'] + ], + [ + ['getent', 'hosts', ip], + ['awk', '{print $2}'], + ['sed', 's/\\.$//'], + ['tr', '\n', '|'] + ] + ] + + commands.each do |command| + begin + output = RMT::Execute.on_target!( + *command, + stdout: :capture + ) + + return output.split('|').compact unless output.empty? + rescue Cheetah::ExecutionFailed => e + log.warn "Failed to obtain host names: #{e.stderr}" + end + end + + nil + end +end
