diff --git a/README.md b/README.md index 0127cb2..00855a8 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,83 @@ The features are: ## Install To install the latest stable version on openSUSE or SLE, use zypper: - $ sudo zypper install yast2-auth-server +``` +$ sudo zypper install yast2-auth-server +``` # Run -Visit Yast control panel and launch "Create New Kerberos Server" or "Create New Directory Server". \ No newline at end of file +Visit Yast control panel and launch "Create New Kerberos Server" or "Create New Directory Server". + + +# Development + +You need to prepare your environment with: + +``` +ruby_version=$(ruby -e "puts RbConfig::CONFIG['ruby_version']") +zypper install -C "rubygem(ruby:$ruby_version:yast-rake)" +zypper install -C "rubygem(ruby:$ruby_version:rspec)" +zypper install git yast2-devtools yast2-testsuite yast +``` + +You can then run the auth-server module with: + +``` +rake run +rake run[module name] +rake run[ldap-server] +``` + +For the 389-ds setup, you'll require a CA + pkcs12 bundle with a cert to use. You can generate +these with certutil from the package mozilla-nss-tools. + +``` +mkdir local_ca +cd local_ca +echo "password" > password.txt +certutil -N -f password.txt -d . +certutil -S -n CAissuer -t "C,C,C" -x -f password.txt -d . -v 24 -g 4096 -Z SHA256 --keyUsage certSigning -2 --nsCertType sslCA -s "CN=ca.nss.dev.example.com,O=Testing,L=example,ST=Queensland,C=AU" + +certutil -S -n Server-Cert -t ",," -c CAissuer -f password.txt -d . -s "CN=test_b.dev.example.com,O=Testing,L=example,ST=Queensland,C=AU" + +certutil -L -n CAissuer -a -d . > ca.pem +pk12util -o server-export.p12 -d . -k password.txt -n Server-Cert +``` + +# Tests + +``` +rake test:unit +``` + +# Logs + +If you are running as a non-root user, the logs are located in: + +``` +~/.y2log +``` + +If you are running as root, these logs are in: + +``` +/var/log/YaST2/y2log +``` + +For more detailed logging, you are able to execute YaST with debugging environment variables: + +``` +Y2DEBUG=1 rake run[ldap-server] +``` + +# Build + +You can build the package with: + +``` +rake osc:build +``` + + + + diff --git a/src/lib/authserver/dir/ds389.rb b/src/lib/authserver/dir/ds389.rb index d0acec1..7720f0c 100644 --- a/src/lib/authserver/dir/ds389.rb +++ b/src/lib/authserver/dir/ds389.rb @@ -9,13 +9,12 @@ # this program; if not, contact SUSE LINUX GmbH. # Authors: Howard Guo +# William Brown require 'yast' require 'open3' require 'fileutils' -# DS_SETUP_LOG_PATH is the path to progress and debug log file for setting up a new directory instance. -DS_SETUP_LOG_PATH = '/root/yast2-auth-server-dir-setup.log' # DS_SETUP_INI_PATH is the path to parameter file for setting up new directory instance. # Place the file under root directory because there are sensitive details in it. DS_SETUP_INI_PATH = '/root/yast2-auth-server-dir-setup.ini' @@ -23,6 +22,7 @@ # DS389 serves utility functions for setting up a new instance of 389 directory server. class DS389 include Yast + include Yast::Logger # install_pkgs installs software packages mandatory for setting up 389 directory server. def self.install_pkgs @@ -37,29 +37,42 @@ def self.get_instance_names end # gen_setup_ini generates INI file content with parameters for setting up directory server. - def self.gen_setup_ini(fqdn, instance_name, suffix, dm_dn, dm_pass) - return "[General] -FullMachineName=#{fqdn} -SuiteSpotUserID=dirsrv -SuiteSpotGroup=dirsrv + def self.gen_setup_ini(fqdn, instance_name, suffix, dm_pass) + return "# Generated by yast-auth-server +[general] +config_version = 2 +full_machine_name = #{fqdn} +# This may be need to be tweaked, it could break setups ... +# strict_host_checking = true/false [slapd] -ServerPort=389 -ServerIdentifier=#{instance_name} -Suffix=#{suffix} -RootDN=#{dm_dn} -RootDNPwd=#{dm_pass} -AddSampleEntries=No +root_password = #{dm_pass} +instance_name = #{instance_name} + +[backend-userroot] +sample_entries = yes +suffix = #{suffix} " end # exec_setup runs setup-ds.pl using input parameters file content. - # The output of setup script is written into file /root/yast2-auth-server-dir-setup.log + # The output of setup script is written into file .y2log or /var/log/YaST/y2log # Returns true only if setup was successful. def self.exec_setup(content) + append_to_log('Beginning YAST auth server installation ...') + open(DS_SETUP_INI_PATH, 'w') {|fh| fh.puts(content)} - stdin, stdouterr, result = Open3.popen2e('/usr/sbin/setup-ds.pl', '--debug', '--silent', '-f', DS_SETUP_INI_PATH) - append_to_log(stdouterr.readlines.join('\n')) + # dry run first to see if it breaks ... + stdin, stdouterr, result = Open3.popen2e('/usr/sbin/dscreate', '-v', 'from-file', '-n', DS_SETUP_INI_PATH) + stdouterr.readlines.map { |l| append_to_log(l) } + + if result.value.exitstatus != 0 + return false + end + + # Right do the real thing. + stdin, stdouterr, result = Open3.popen2e('/usr/sbin/dscreate', '-v', 'from-file', DS_SETUP_INI_PATH) + stdouterr.readlines.map { |l| append_to_log(l) } stdin.close return result.value.exitstatus == 0 end @@ -71,17 +84,7 @@ def self.remove_setup_ini # append_to_log appends current time and content into log file placed under /root/. def self.append_to_log(content) - open(DS_SETUP_LOG_PATH, 'a') {|fh| - fh.puts(Time.now) - fh.puts(content) - } - end - - # enable_krb_schema enables kerberos schema in the directory server and then restarts the directory server. - # Returns true only if server restarted successfully. - def self.enable_krb_schema(instance_name) - ::FileUtils.copy('/usr/share/dirsrv/data/60kerberos.ldif', '/etc/dirsrv/slapd-' + instance_name + '/schema/60kerberos.ldif') - return self.restart(instance_name) + log.info(content) end # restart the directory service specified by the instance name. Returns true only on success. @@ -94,48 +97,21 @@ def self.restart(instance_name) def self.install_tls_in_nss(instance_name, ca_path, p12_path) instance_dir = '/etc/dirsrv/slapd-' + instance_name # Put CA certificate into NSS database - _, stdouterr, result = Open3.popen2e('/usr/bin/certutil', '-A', '-d', instance_dir, '-n', 'ca_cert', '-t', 'C,,', '-i', ca_path) - append_to_log(stdouterr.readlines.join('\n')) + _, stdouterr, result = Open3.popen2e('/usr/bin/certutil', '-A', '-f', instance_dir + '/pwdfile.txt', '-d', instance_dir, '-n', 'ca_cert', '-t', 'C,,', '-i', ca_path) + stdouterr.readlines.map { |l| append_to_log(l) } if result.value.exitstatus != 0 return false end - # Put TLS certificate and key into NSS database - _, stdouterr, result = Open3.popen2e('/usr/bin/pk12util', '-d', instance_dir, '-W', '', '-K', '', '-i', p12_path) - append_to_log(stdouterr.readlines.join('\n')) + # Delete the automatically created Server-Cert - we don't care if it fails ... + _, stdouterr, result = Open3.popen2e('/usr/bin/certutil', '-F', '-d', instance_dir, '-n', 'Server-Cert', '-f', instance_dir + '/pwdfile.txt') + stdouterr.readlines.map { |l| append_to_log(l) } + # Put TLS certificate and key into NSS database - and hope it's named Server-Cert ... + _, stdouterr, result = Open3.popen2e('/usr/bin/pk12util', '-i', p12_path, '-k', instance_dir + '/pwdfile.txt', '-d', instance_dir, '-W', '') + stdouterr.readlines.map { |l| append_to_log(l) } if result.value.exitstatus != 0 return false end return true end - # get_enable_tls_ldif returns LDIF data that can be - def self.get_enable_tls_ldif - return 'dn: cn=encryption,cn=config -changetype: modify -replace: nsSSL3 -nsSSL3: off -- -replace: nsSSLClientAuth -nsSSLClientAuth: allowed -- -add: nsSSL3Ciphers -nsSSL3Ciphers: +all - -dn: cn=config -changetype: modify -add: nsslapd-security -nsslapd-security: on -- -replace: nsslapd-ssl-check-hostname -nsslapd-ssl-check-hostname: off - -dn: cn=RSA,cn=encryption,cn=config -changetype: add -objectclass: top -objectclass: nsEncryptionModule -cn: RSA -nsSSLPersonalitySSL: Server-Cert -nsSSLToken: internal (software) -nsSSLActivation: on' - end -end \ No newline at end of file +end diff --git a/src/lib/authserver/krb/mit.rb b/src/lib/authserver/krb/mit.rb index b2413d8..37326f2 100644 --- a/src/lib/authserver/krb/mit.rb +++ b/src/lib/authserver/krb/mit.rb @@ -13,9 +13,6 @@ require 'yast' require 'open3' -# KDC_SETUP_LOG_PATH is the path to progress and debug log file for setting up a new KDC. -KDC_SETUP_LOG_PATH = '/root/yast2-auth-server-kdc-setup.log' - # MITKerberos serves utility functions for setting up a new directory connected KDC. class MITKerberos include Yast @@ -130,9 +127,6 @@ def self.restart_kadmind # append_to_log appends current time and content into log file placed under /root/. def self.append_to_log(content) - open(KDC_SETUP_LOG_PATH, 'a') {|fh| - fh.puts(Time.now) - fh.puts(content) - } + log.info(content) end -end \ No newline at end of file +end diff --git a/src/lib/authserver/ui/new_dir_inst.rb b/src/lib/authserver/ui/new_dir_inst.rb index b39ad18..ce79521 100644 --- a/src/lib/authserver/ui/new_dir_inst.rb +++ b/src/lib/authserver/ui/new_dir_inst.rb @@ -9,6 +9,7 @@ # this program; if not, contact SUSE LINUX GmbH. # Authors: Howard Guo +# William Brown require 'yast' require 'ui/dialog' @@ -44,20 +45,25 @@ def dialog_content Left(Heading(_('Create New Directory Instance'))), HBox( Frame(_('General options (mandatory)'), - VBox( - InputField(Id(:fqdn), Opt(:hstretch), _('Fully qualified domain name (e.g. dir.example.net)'), ''), - InputField(Id(:instance_name), Opt(:hstretch), _('Directory server instance name (e.g. MyOrgDirectory)'), ''), - InputField(Id(:suffix), Opt(:hstretch), _('Directory suffix (e.g. dc=example,dc=net)'), ''), - InputField(Id(:dm_dn), Opt(:hstretch), _('Directory manager DN (e.g. cn=root)'), ''), - ), + VBox( + InputField(Id(:fqdn), Opt(:hstretch), _('Fully qualified domain name (e.g. dir.example.net)'), ''), + InputField(Id(:instance_name), Opt(:hstretch), _('Directory server instance name (e.g. localhost)'), ''), + InputField(Id(:suffix), Opt(:hstretch), _('Directory suffix (e.g. dc=example,dc=net)'), ''), + ), ), - Frame(_('Security options (mandatory)'), - VBox( - Password(Id(:dm_pass), Opt(:hstretch), _('Directory manager password'), ''), - Password(Id(:dm_pass_repeat), Opt(:hstretch), _('Repeat directory manager password'), ''), - InputField(Id(:tls_ca), Opt(:hstretch), _('Server TLS certificate authority in PEM format'), ''), - InputField(Id(:tls_p12), Opt(:hstretch), _('Server TLS certificate and key in PKCS12 format'), ''), - ), + VBox( + Frame(_('Security options (mandatory)'), + VBox( + Password(Id(:dm_pass), Opt(:hstretch), _('"cn=Directory Manager" password'), ''), + Password(Id(:dm_pass_repeat), Opt(:hstretch), _('Repeat "cn=Directory Manager" password'), ''), + ), + ), + Frame(_('Security options (optional)'), + VBox( + InputField(Id(:tls_ca), Opt(:hstretch), _('Server TLS certificate authority in PEM format'), ''), + InputField(Id(:tls_p12), Opt(:hstretch), _('Server TLS certificate and key in PKCS12 format with friendly name "Server-Cert"'), ''), + ), + ), ), ), HBox( @@ -72,64 +78,72 @@ def ok_handler fqdn = UI.QueryWidget(Id(:fqdn), :Value) instance_name = UI.QueryWidget(Id(:instance_name), :Value) suffix = UI.QueryWidget(Id(:suffix), :Value) - dm_dn = UI.QueryWidget(Id(:dm_dn), :Value) dm_pass = UI.QueryWidget(Id(:dm_pass), :Value) dm_pass_repeat = UI.QueryWidget(Id(:dm_pass_repeat), :Value) tls_ca = UI.QueryWidget(Id(:tls_ca), :Value) tls_p12 = UI.QueryWidget(Id(:tls_p12), :Value) + UI.ReplaceWidget(Id(:busy), Empty()) + # Validate input - if fqdn == '' || instance_name == ''|| suffix == '' || dm_dn == '' || dm_pass == '' || tls_ca == '' || tls_p12 == '' - Popup.Error(_('Please complete setup details. All input fields are mandatory.')) + if fqdn == '' || instance_name == ''|| suffix == '' || dm_pass == '' + Popup.Error(_('Please complete mandatory setup fields.')) return end if dm_pass_repeat != dm_pass Popup.Error(_('Two password entries do not match.')) return end - if !File.exists?(tls_ca) || !File.exists?(tls_p12) - Popup.Error(_('TLS certificate authority or certificate/key file does not exist.')) + if ! ((tls_ca == '' && tls_p12 == '') || (tls_ca != '' && tls_p12 != '')) + Popup.Error(_('Both TLS Certificate authority and PKCS12 must be provided, or none provided.')) return end - if DS389.get_instance_names.include?(instance_name) - Popup.Error(_('The instance name is already used.')) + if (tls_ca != '' && tls_p12 != '') && (!File.exists?(tls_ca) || !File.exists?(tls_p12)) + Popup.Error(_('TLS certificate authority PEM OR certificate/key PKCS12 file does not exist.')) return end + # The dscreate tool has an instance name checker that is much more aware of instance + # rules than this ruby tool can be. + UI.ReplaceWidget(Id(:busy), Label(_('Preparing to install new instance, this may take a minute ...'))) - UI.ReplaceWidget(Id(:busy), Label(_('Installing new instance, this may take a minute or two.'))) - begin - DS389.install_pkgs - # Collect setup parameters into an INI file and feed it into 389 setup script - ok = DS389.exec_setup(DS389.gen_setup_ini(fqdn, instance_name, suffix, dm_dn, dm_pass)) - DS389.remove_setup_ini - if !ok - Popup.Error(_('Failed to set up new instance! Log output may be found in %s') % [DS_SETUP_LOG_PATH]) - raise - end + if !DS389.install_pkgs + Popup.Error(_('Error during package installation.')) + return + end + + # Collect setup parameters into an INI file and feed it into 389 setup script + ini_content = DS389.gen_setup_ini(fqdn, instance_name, suffix, dm_pass) + ini_safe_content = DS389.gen_setup_ini(fqdn, instance_name, suffix, "********") + log.info(ini_safe_content) + UI.ReplaceWidget(Id(:busy), Label(_('Installing new instance, this may take a minute ...'))) + ok = DS389.exec_setup(ini_content) + # Always remove the ini file + DS389.remove_setup_ini + if !ok + Popup.Error(_('Failed to set up new instance! Log output may be found in /var/log/YaST/y2log')) + UI.ReplaceWidget(Id(:busy), Empty()) + return + end + + if (tls_ca != '' && tls_p12 != '') + UI.ReplaceWidget(Id(:busy), Label(_('Configuring instance TLS ...'))) # Turn on TLS if !DS389.install_tls_in_nss(instance_name, tls_ca, tls_p12) - Popup.Error(_('Failed to set up new instance! Log output may be found in %s') % [DS_SETUP_LOG_PATH]) - raise - end - ldap = LDAPClient.new('ldap://'+fqdn, dm_dn, dm_pass) - out, ok = ldap.modify(DS389.get_enable_tls_ldif, true) - DS389.append_to_log(out) - if !ok - Popup.Error(_('Failed to enable TLS! Log output may be found in %s') % [DS_SETUP_LOG_PATH]) - raise + Popup.Error(_('Failed to set up new instance! Log output may be found in /var/log/YaST/y2log')) + UI.ReplaceWidget(Id(:busy), Empty()) + return end + if !DS389.restart(instance_name) - Popup.Error(_('Failed to restart directory instance, please inspect the journal of dirsrv@%s.service') % [instance_name]) - raise + Popup.Error(_('Failed to restart directory instance, please inspect the journal of dirsrv@%s.service and /var/log/dirsrv/slapd-%s') % [instance_name, instance_name]) + UI.ReplaceWidget(Id(:busy), Empty()) + return end - - UI.ReplaceWidget(Id(:busy), Empty()) - Popup.Message(_('New instance has been set up! Log output may be found in %s') % [DS_SETUP_LOG_PATH]) - finish_dialog(:next) - rescue - # Give user an opportunity to correct mistake - UI.ReplaceWidget(Id(:busy), Empty()) end + UI.ReplaceWidget(Id(:busy), Empty()) + Popup.Message(_('New instance has been set up! Log output may be found in /var/log/YaST/y2log')) + finish_dialog(:next) + UI.ReplaceWidget(Id(:busy), Empty()) end -end \ No newline at end of file +end diff --git a/src/lib/authserver/ui/new_krb_inst.rb b/src/lib/authserver/ui/new_krb_inst.rb index 5b2b749..085edd5 100644 --- a/src/lib/authserver/ui/new_krb_inst.rb +++ b/src/lib/authserver/ui/new_krb_inst.rb @@ -53,12 +53,10 @@ def dialog_content ), Frame(_('389 directory server connectivity (mandatory)'), VBox( - InputField(Id(:dir_addr), Opt(:hstretch), _('Directory server address (e.g. dir.example.net)'), ''), - InputField(Id(:dir_inst), Opt(:hstretch), _('Directory instance name'), ''), + InputField(Id(:dir_addr), Opt(:hstretch), _('Fully qualified domain name (e.g. dir.example.net)'), ''), + InputField(Id(:dir_inst), Opt(:hstretch), _('Directory instance name (e.g. localhost)'), ''), InputField(Id(:dir_suffix), Opt(:hstretch), _('Directory suffix (e.g. dc=example,dc=net)'), ''), - InputField(Id(:container_dn), Opt(:hstretch), _('Container DN of existing users (e.g. ou=users,dc=example,dc=net)'), ''), - InputField(Id(:dm_dn), Opt(:hstretch), _('Directory manager DN (e.g. cn=root)'), ''), - Password(Id(:dm_pass), Opt(:hstretch), _('Directory manager password'), ''), + Password(Id(:dm_pass), Opt(:hstretch), _('"cn=Directory Manager" password'), ''), ), ), ), @@ -145,10 +143,7 @@ def ok_handler begin MITKerberos.install_pkgs # Enable kerberos schema on 389 - if !DS389.enable_krb_schema(dir_inst) - Popup.Error(_('Failed to enable Kerberos schema.')) - raise - end + # By default 389-ds ships with this schema enabled today. # Create kerberos users and give them password in LDAP kdc_dn = kdc_dn_prefix+','+dir_suffix @@ -243,4 +238,4 @@ def ok_handler UI.ReplaceWidget(Id(:busy), Empty()) end end -end \ No newline at end of file +end diff --git a/test/dir_test.rb b/test/dir_test.rb index 6ef447a..fda486a 100644 --- a/test/dir_test.rb +++ b/test/dir_test.rb @@ -10,6 +10,7 @@ # this program; if not, contact SUSE LINUX GmbH. # Authors: Howard Guo +# William Brown ENV['Y2DIR'] = File.expand_path('../../src', __FILE__) @@ -20,19 +21,21 @@ describe DS389 do it 'gen_setup_ini' do - match = '[General] -FullMachineName=dir.example.com -SuiteSpotUserID=dirsrv -SuiteSpotGroup=dirsrv + match = '# Generated by yast-auth-server +[general] +config_version = 2 +full_machine_name = dir.example.com +# This may be need to be tweaked, it could break setups ... +# strict_host_checking = true/false [slapd] -ServerPort=389 -ServerIdentifier=ExampleDotCom -Suffix=dc=example,dc=com -RootDN=cn=admin -RootDNPwd=pass -AddSampleEntries=No +root_password = pass +instance_name = ExampleDotCom + +[backend-userroot] +sample_entries = yes +suffix = dc=example,dc=com ' - expect(DS389.gen_setup_ini('dir.example.com', 'ExampleDotCom', 'dc=example,dc=com', 'cn=admin', 'pass')).to eq(match) + expect(DS389.gen_setup_ini('dir.example.com', 'ExampleDotCom', 'dc=example,dc=com', 'pass')).to eq(match) end -end \ No newline at end of file +end