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 - ikapelyuk...@suse.com
+
+- 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 - ikapelyuk...@suse.com
+
+- version 0.0.3
+- Add setup steps for SSL certificate generation (fate#325115)
+
+-------------------------------------------------------------------
 Mon Mar  5 15:12:56 UTC 2018 - ikapelyuk...@suse.com
 
 - 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


Reply via email to