# Copyright (c) 2010 - OpenSLX GmbH # # This program is free software distributed under the GPL version 2. # See http://openslx.org/COPYING # # If you have any feedback please consult http://openslx.org/feedback and # send your suggestions, praise, or complaints to feedback@openslx.org # # General information about OpenSLX can be found at http://openslx.org/ package OpenSLX::OSPlugin::auth; use strict; use warnings; use base qw(OpenSLX::OSPlugin::Base); use OpenSLX::Basics; use OpenSLX::Utils; sub new { my $class = shift; my $self = { name => 'auth', }; return bless $self, $class; } sub getInfo { my $self = shift; return { description => unshiftHereDoc(<<' End-of-Here'), auth plugin to configure desired authentification End-of-Here precedence => 50, }; } sub getAttrInfo { # returns a hash-ref with information about all attributes supported # by this specific plugin my $self = shift; # This default configuration will be added as attributes to the default # system, such that it can be overruled for any specific system by means # of slxconfig. return { # attribute 'active' is mandatory for all plugins 'auth::active' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), should the 'auth'-plugin be executed during boot? End-of-Here content_regex => qr{^(0|1)$}, content_descr => '1 means active - 0 means inactive', default => '1', }, # plugin specific attributes start here ... 'auth::files' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), Folder of extra files. Default: /root/auth-plugin Optional, except with kerberOS for krb5.conf! End-of-Here content_regex => qr{^/.*$}, content_descr => 'Path starting with /', default => '/root/auth-plugin', }, 'auth::passwd' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), Should the /etc/{passwd,shadow} authentification be used? End-of-Here content_regex => qr{^(0|1)$}, content_descr => '1 means active - 0 means inactive', default => '1', }, 'auth::rootpwd' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), If set, defines the root-password End-of-Here content_regex => qr{^.*$}, content_descr => 'Defines root-password. "" if not set.', default => '', }, 'auth::ldap' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), Should we use ldap for authentification? NSS for lookup. End-of-Here content_regex => qr{^(0|1)$}, content_descr => '1 means active - 0 means inactive', default => '0', }, 'auth::ldapuri' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), LDAP URI. Example: "ldap://server1 ldap://server2 ldap://server3" End-of-Here content_regex => qr{^.*$}, content_descr => 'ldap(s)://serverX (ldap(s)://serverY ...)', default => '', }, 'auth::ldapbase' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), LDAP base distinguished name. Example: "ou=people,dc=domain,dc=de" End-of-Here content_regex => qr{^.*$}, content_descr => 'ou=people,dc=domain,dc=de', default => '', }, 'auth::nss_base_passwd' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), specify the search base, scope and filter for passwd. Example: "ou=people,dc=domain,dc=de?one?foo=*" End-of-Here content_regex => qr{^.*$}, content_descr => '', default => '', }, 'auth::nss_base_group' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), specify the search base, scope and filter for group. Example: "ou=people,dc=domain,dc=de?one?foo=*" End-of-Here content_regex => qr{^.*$}, content_descr => '', default => '', }, 'auth::ldap_options' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), extra nss_ldap or ldap options. Example: "nss_map_attribute ". Multiple options can be seperated by "\n\n\n..." End-of-Here content_regex => qr{^.*$}, content_descr => 'see nss_ldap(5)', default => '', }, 'auth::ldapbinddn' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), specify the dn to bind to. Example: "ou=people,dc=domain,dc=de?one?foo=*" End-of-Here content_regex => qr{^.*$}, content_descr => 'see nss_ldap(5)', default => '', }, 'auth::ldapbindpw' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), specify the dn password to bind to. End-of-Here content_regex => qr{^.*$}, content_descr => 'see nss_ldap(5)', default => '', }, 'auth::automount' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), Should we use automount? End-of-Here content_regex => qr{^(0|1)$}, content_descr => '1 means active - 0 means inactive', default => '0', }, 'auth::automnt_src' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), give the source where we get the automount fs from. E.g. nfs://x.x.x.x/home End-of-Here content_regex => qr{^(|nfs://.*)$}, content_descr => 'currently only nfs://host/path possible', default => '', }, 'auth::automnt_dir' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), Where should we mount the data to? E.g.: /home/nfs/ End-of-Here content_regex => qr{^(\/.*|)$}, content_descr => 'Has to start with /', default => '', }, 'auth::automnt_script' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), Automount will use a script and not automnt_dir. This script has to be in path of auth::files End-of-Here content_regex => qr{^.*$}, content_descr => 'scriptname', default => '', }, 'auth::nfs4' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), Do you want to use NFS4? For example with automount. End-of-Here content_regex => qr{^(0|1)$}, content_descr => '1 means active - 0 means inactive', default => '0', }, 'auth::idmap_domain' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), What should be the domain used for idmapd? Usually needed with NFS4 End-of-Here content_regex => qr{^.*$}, content_descr => 'Domainname like mydomain.tld', default => '', }, 'auth::krb' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), Do you want to use KerberOS? Default is off If enabled, you must provide krb5.conf in the path defined in auth::files (default: /root/auth-plugin) End-of-Here content_regex => qr{^(0|1)$}, content_descr => '1 means active - 0 means inactive', default => '0', }, 'auth::krbscript' => { applies_to_systems => 1, applies_to_clients => 1, description => unshiftHereDoc(<<' End-of-Here'), KerberOS: script to run to do extra kerberOS configuration like getting a credential over network. Optional feature. The given script will be started through /etc/init.d/boot.slx in Stage4. End-of-Here content_regex => qr{^.*$}, content_descr => 'filename of script which needs to be in auth:krbconf-Directory', default => '', }, }; } # called while chrooted to the vendor-OS root in order to give the plugin # a chance to install required files into the vendor-OS. sub installationPhase { my $self = shift; my $info = shift; $self->{pluginRepositoryPath} = $info->{'plugin-repo-path'}; $self->{pluginTempPath} = $info->{'plugin-temp-path'}; $self->{openslxBasePath} = $info->{'openslx-base-path'}; $self->{openslxConfigPath} = $info->{'openslx-config-path'}; $self->{attrs} = $info->{'plugin-attrs'}; my $engine = $self->{'os-plugin-engine'}; my $ldap = $self->{attrs}->{'auth::ldap'}; my $automount = $self->{attrs}->{'auth::automount'}; my $nfs4 = $self->{attrs}->{'auth::nfs4'}; my $passwd = $self->{attrs}->{'auth::passwd'}; my $krb = $self->{attrs}->{'auth::krb'}; # configure passwd if($passwd) { my $rootPwd = $self->{attrs}->{'auth::rootpwd'}; if($rootPwd eq "") { print "root-password not set. Change auth::rootpwd and retry.\n"; exit 1; } $self->_setPasswordForUser( 'root', "$rootPwd" ); } # configure ldap if ($ldap) { if ($self->{attrs}->{'auth::ldapuri'} eq '' || $self->{attrs}->{'auth::ldapbase'} eq '') { print "auth::ldapuri and/or auth::ldapbase not defined. LDAP configuration canceld\n"; exit 1; } $self->_writeLdapConf(); #write ldap.conf } # configure automount if ($automount) { if ($self->{attrs}->{'auth::automnt_src'} eq '' && $self->{attrs}->{'auth::automnt_script'} eq '') { print "auth::automnt_src and auth::automnt_script not defined. Automount configuration canceld\n"; exit 1; } if ($self->{attrs}->{'auth::automnt_dir'} eq '') { print "auth::automnt_dir not defined. Automount configuration canceld\n"; exit 1; } $self->_writeAutomountConf(); } # configure kerberOS. Biggest part in preInstallationPhase()! if ($krb) { $self->_krbConf(); } return; } sub removalPhase { # called while chrooted to the vendor-OS root in order to give the plugin # a chance to uninstall no longer required files from the vendor-OS. my $self = shift; my $info = shift; my $pluginRepoPath = $info->{'plugin-repo-path'}; # The folder where the stage1-plugin should store all files # required by the corresponding stage3 runlevel script. # As this method is being executed while chrooted into the vendor-OS, # this path is relative to that root (i.e. directly usable). my $pluginTempPath = $info->{'plugin-temp-path'}; # A temporary playground that will be cleaned up automatically. # As this method is being executed while chrooted into the vendor-OS, # this path is relative to that root (i.e. directly usable). return; } sub preInstallationPhase() { my $self = shift; my $info = shift; $self->{pluginRepositoryPath} = $info->{'plugin-repo-path'}; $self->{pluginTempPath} = $info->{'plugin-temp-path'}; $self->{openslxBasePath} = $info->{'openslx-base-path'}; $self->{openslxConfigPath} = $info->{'openslx-config-path'}; $self->{attrs} = $info->{'plugin-attrs'}; $self->{vendorOsPath} = $info->{'vendor-os-path'}; my $files = $self->{attrs}->{'auth::files'}; my $krb = $self->{attrs}->{'auth::krb'}; my $krbScript = $self->{attrs}->{'auth::krbscript'}; my $autoMount = $self->{attrs}->{'auth::automount'}; my $automntScript = $self->{attrs}->{'auth::automnt_script'}; if ($krb && !-d $files) { print "KerberOS enabled, but path $files from auth::files not found. Configuration canceld.\n"; exit 1; } if ($krb && ! -e "$files/krb5.conf") { print "KerberOS enabled, but needed Configfile $files/krb5.conf not found. Installation stopped.\n"; exit 1; } if ($krb && $krbScript ne '' && ! -e "$files/$krbScript") { print "KerberOS enabled, but needed Configfile $files/$krbScript not found. Installation stopped.\n"; exit 1; } if ($autoMount && $automntScript ne '' && ! -e "$files/$automntScript") { print "auth::automnt_script $files/$automntScript not found. Installation stopped.\n"; exit 1; } # everything worked, now we can copy if ( -e $files) { system("cp -r $self->{attrs}->{'auth::files'}/* $self->{pluginRepositoryPath}/"); } } sub _setPasswordForUser { my $self = shift; my $username = shift; my $password = shift; my $hashedPassword = $self->_hashPassword($password); # now read, change and write shadow-file in atomic manner: my $shadowFile = '/etc/shadow'; if (!-e $shadowFile) { spitFile( $shadowFile, ''); } slxsystem("cp -r $shadowFile $shadowFile~"); my $shadowFH; open($shadowFH, '+<', $shadowFile) or croak _tr("could not open file '%s'! (%s)", $shadowFile, $!); # needs fcntl, but noone else should touch it. #flock($shadowFH, LOCK_EX) # or croak _tr("could not lock file '%s'! (%s)", $shadowFile, $!); my $lastChanged = int(time()/24/60/60); my $newEntry = "$username:$hashedPassword:$lastChanged:0:99999:7:::"; my $content = do { local $/; <$shadowFH> }; if ($content =~ m{^$username:}ims) { $content =~ s{^$username:.*$}{$newEntry}ms; } else { $content .= "$newEntry\n"; } seek($shadowFH, 0, 0) or croak _tr("could not seek file '%s'! (%s)", $shadowFile, $!); print $shadowFH $content or croak _tr("could not write to file '%s'! (%s)", $shadowFile, $!); close($shadowFH) or croak _tr("could not close file '%s'! (%s)", $shadowFile, $!); unlink "$shadowFile~"; # create missing entrys in /etc/shadow, else the system is screwed. slxsystem("pwconv"); # Just on suse... on the other side, -c blowfish is not really needed as argument slxsystem("echo root:$password |chpasswd"); } sub _hashPassword { my $self = shift; my $password = shift; # -1 = md5 my $hashedPassword = qx{openssl passwd -1 $password}; chomp $hashedPassword; return $hashedPassword; } sub _writeLdapConf { my $self = shift; my $ldapUri = $self->{attrs}->{'auth::ldapuri'}; my $ldapBase = $self->{attrs}->{'auth::ldapbase'}; my $nss_base_passwd = $self->{attrs}->{'auth::nss_base_passwd'}; my $nss_base_group = $self->{attrs}->{'auth::nss_base_group'}; my $ldap_options = $self->{attrs}->{'auth::ldap_options'}; my $ldapBindDn = $self->{attrs}->{'auth::ldapbinddn'}; my $ldapBindPw = $self->{attrs}->{'auth::ldapbindpw'}; my $ldapConf = "# ldap.conf created by OpenSLX auth-plugin\n"; $ldapConf .= "URI $ldapUri\n"; $ldapConf .= "BASE $ldapBase\n"; if($nss_base_passwd ne '') { $ldapConf .= "nss_base_passwd $nss_base_passwd\n"; } if($nss_base_group ne '') { $ldapConf .= "nss_base_group $nss_base_group\n"; } if($ldap_options ne '') { # hack. \n is not newline in this string. Repair it. $ldap_options =~ s/\\n/\n/g; $ldapConf .= "$ldap_options\n"; } if($ldapBindDn ne '') { $ldapConf .= "binddn $ldapBindDn\n"; } if($ldapBindDn ne '') { $ldapConf .= "bindpw $ldapBindPw\n"; } # if LDAP is damaged, we want to be able to access the system, even if # it takes a while to get to the login prompt. Waiting time could be # smaller. # Another workaround would be "nss_initgroups_ignoreusers" $ldapConf .= "nss_reconnect_tries 2\n"; $ldapConf .= "nss_reconnect_sleeptime 1\n"; $ldapConf .= "nss_reconnect_maxsleeptime 2\n"; $ldapConf .= "nss_reconnect_maxconntries 2\n"; spitFile("$self->{'pluginRepositoryPath'}/ldap.conf.slx", $ldapConf); return; } sub _writeAutomountConf { my $self = shift; my $automntSrc = $self->{attrs}->{'auth::automnt_src'}; my $automntDir = $self->{attrs}->{'auth::automnt_dir'}; my $automntScript = $self->{attrs}->{'auth::automnt_script'}; my $nfs4 = $self->{attrs}->{'auth::nfs4'}; my $idmapDomain = $self->{attrs}->{'auth::idmap_domain'}; # automntSrc looks like proto://server/path/foo (my $autoProto = $automntSrc ) =~ s,://.*,,; (my $autoHost = $automntSrc ) =~ s,.*://,,; $autoHost =~ s,/.*,,g; (my $autoSrcPath = $automntSrc ) =~ s,.*$autoHost,,; my $autoConf; if ($automntSrc ne '' && $automntScript ne '') { print "auth::automnt_src and auth::automnt_script enabled. Both won't work together."; print "You need to disable (set the value to '' (empty) one of them!"; print "Automount configuration failed."; exit 1; } # nfs if ($autoProto eq 'nfs') { my $autoConf = "# created by auth-Plugin\n"; $autoConf .= "* -fstype=nfs,rsize=32768,wsize=32768,rw $autoHost:$autoSrcPath/&\n"; spitFile("$self->{'pluginRepositoryPath'}/auto.slx", $autoConf); # nfs4 } elsif ($autoProto =~ m/nfs/ && $nfs4 && $idmapDomain ne '') { my $autoConf = "# created by auth-Plugin\n"; # Path is tricky. NFS4 starts from the exported dir and not from the root dir # e.g. in exports: "/export/homdirs 132.230.4.0/255.255.255.0(rw,fsid=0,insecure,no_subtree_check,async)" # "mount -t nfs4 server:/$user /home/$user" and not server:/export/homdirs/$user! # nfs4 needs fsid=0 $autoConf .= "* -fstype=nfs4,fsid=0,rsize=32768,wsize=32768,rw $autoHost:/&\n"; spitFile("$self->{'pluginRepositoryPath'}/auto.slx", $autoConf); # automnt script } elsif ( $automntScript ne '') { $autoConf .= "# not used, because we use auth::authmnt_script\n"; spitFile("$self->{'pluginRepositoryPath'}/auto.slx", $autoConf); } else { print "Automount Configuration failed. Unknown protocol in auth::automnt_src, auth::nfs4 or auth::idmap_domain not enabled.\n"; exit 1; } my $autoMaster; if ($autoProto =~ m/nfs/) { $autoMaster = "# created by auth-Plugin\n"; $autoMaster .= "$automntDir /etc/auto.slx\n"; spitFile("$self->{'pluginRepositoryPath'}/auto.master", $autoMaster); } elsif ($automntScript ne '') { #todo: openslx path is hard coded. Maybe we should move this block to stage3. Just how with # without doing all the tests above in Stage3 and being able to configuring client dependend # autmount for each host different $autoMaster = "# created by auth-Plugin\n"; $autoMaster .= "$automntDir program:/opt/openslx/plugin-repo/auth/$automntScript\n"; spitFile("$self->{'pluginRepositoryPath'}/auto.master", $autoMaster); } return; } sub _krbConf { my $self = shift; # currently not used... } 1;