#!/usr/bin/perl $|=1; # Displays status of LON-CAPA SSL certs, and allows new certificate # signing requests to be created and e-mailed to CA for cluster to # which server/VM belongs. # $Id: manage_ssl_certs.pl,v 1.1 2018/08/18 23:33:30 raeburn Exp $ # # Copyright Michigan State University Board of Trustees # # This file is part of the LearningOnline Network with CAPA (LON-CAPA). # # LON-CAPA is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # LON-CAPA 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 LON-CAPA; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # /home/httpd/html/adm/gpl.txt # # http://www.lon-capa.org/ # use strict; use lib '/home/httpd/lib/perl/'; use LONCAPA::Configuration; use LONCAPA::Lond; use LONCAPA::SSL; use LONCAPA; use Term::ReadKey; use Locale::Country; my ($lonCluster,$domainDescription,$hostname,$certmail, $certsdir,$privkey,$connectcsr,$replicatecsr); print(<; chomp($choice); if ($choice==1) { $lonCluster='production'; $flag=1; } elsif ($choice==2) { $lonCluster='standalone'; $flag=1; } elsif ($choice==3) { $lonCluster='development'; $flag=1; } elsif ($choice==4) { $lonCluster='existing'; $flag=1; my $earlyout; foreach my $file ('hosts.tab','dns_hosts.tab', 'domain.tab','dns_domain.tab') { unless (-e '/home/httpd/lonTabs/'.$file) { print <) { if ($configline =~ /^[^\#]*PerlSetVar/) { my ($unused,$varname,$varvalue)=split(/\s+/,$configline); chomp($varvalue); $perlvar{$varname}=$varvalue if $varvalue!~/^\{\[\[\[\[/; } } close(CONFIG); } } my $perlstaticref = &get_static_config($confdir); if (ref($perlstaticref) ne 'HASH') { exit; } if (open(IN,'<','/home/httpd/lonTabs/domain.tab')) { while(my $line = ) { if ($line =~ /^\Q$perlvar{'lonDefDomain'}\E\:/) { (undef,$domainDescription)=split(/:/,$line); chomp($domainDescription); last; } } close(IN); } if (open(IN,'<','/home/httpd/lonTabs/hosts.tab')) { while(my $line = ) { if ($line =~ /^\Q$perlvar{'lonHostID'}\E\:/) { (undef,undef,undef,$hostname)=split(/:/,$line); last; } } close(IN); } $certsdir = $perlstaticref->{'lonCertificateDirectory'}; $privkey = $perlstaticref->{'lonnetPrivateKey'}; $connectcsr = $perlstaticref->{'lonnetCertificate'}; $connectcsr =~ s/\.pem$/.csr/; $replicatecsr = $perlstaticref->{'lonnetHostnameCertificate'}; $replicatecsr =~ s/\.pem$/.csr/; $certmail = &get_mail(); print "\nRetrieving status information for SSL key and certificates ...\n\n"; my ($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) = &get_cert_status($perlvar{'lonHostID'},$hostname,$perlstaticref); print $certinfo; my %sslstatus; if (ref($sslref) eq 'HASH') { %sslstatus = %{$sslref}; } while (!$flag) { print(<; chomp($choice); if ($choice==1) { if ($sslstatus{'key'} == 1) { print(<; chomp($choice2); if ($choice2 eq '1') { my $sslkeypass = &get_new_sslkeypass(); &make_key($certsdir,$privkey,$sslkeypass); } } elsif ($sslstatus{'key'} == 0) { print(<; chomp($choice2); if (($choice2 eq '1') || ($choice2 eq '2')) { &ssl_info(); my $country = &get_country($hostname); my $state = &get_state(); my $city = &get_city(); my $connectsubj = "/C=$country/ST=$state/O=$domainDescription/L=$city/CN=$perlvar{'lonHostID'}/OU=LONCAPA/emailAddress=$certmail"; ($domainDescription,$country,$state,$city) = &confirm_locality($domainDescription,$country,$state,$city); my $sslkeypass; if ($choice2 eq '1') { $sslkeypass = &get_new_sslkeypass(); &make_key($certsdir,$privkey,$sslkeypass); } elsif ($choice2 eq '2') { $sslkeypass = &get_password('Enter existing password for SSL key'); &encrypt_key($certsdir,$privkey,$sslkeypass); } &make_host_csr($certsdir,$sslkeypass,$connectcsr,$connectsubj); &mail_csr('host',$lonCluster,$perlvar{'lonHostID'},$hostname,$certsdir,$connectcsr,$replicatecsr,$perlstaticref); print "\nRetrieving status information for SSL key and certificates ...\n\n"; ($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) = &get_cert_status($perlvar{'lonHostID'},$hostname,$perlstaticref); if (ref($sslref) eq 'HASH') { %sslstatus = %{$sslref}; } } elsif ($choice2 eq '3') { if (-e "$certsdir/$connectcsr") { &mail_csr('host',$lonCluster,$perlvar{'lonHostID'},$hostname,$certsdir,$connectcsr,$replicatecsr,$perlstaticref); } } } elsif (($sslstatus{'host'} == 0) || ($sslstatus{'host'} == 4) || ($sslstatus{'host'} == 5)) { my $sslkeypass; if ($sslstatus{'key'} == 1) { print(<; chomp($choice2); if ($choice2 eq '1') { $sslkeypass = &get_new_sslkeypass(); &make_key($certsdir,$privkey,$sslkeypass); } elsif ($choice2 eq '2') { $sslkeypass = &get_password('Enter existing password for SSL key'); &encrypt_key($certsdir,$privkey,$sslkeypass); } } else { print(<; chomp($choice2); if (($choice2 eq '1') || ($choice2 eq '2')) { &ssl_info(); my $country = &get_country($hostname); my $state = &get_state(); my $city = &get_city(); my $replicatesubj = "/C=$country/ST=$state/O=$domainDescription/L=$city/CN=internal-$hostname/OU=LONCAPA/emailAddress=$certmail"; my $sslkeypass; if ($choice2 eq '1') { $sslkeypass = &get_new_sslkeypass(); &make_key($certsdir,$privkey,$sslkeypass); } elsif ($choice2 eq '2') { $sslkeypass = &get_password('Enter existing password for SSL key'); &encrypt_key($certsdir,$privkey,$sslkeypass); } &make_hostname_csr($certsdir,$sslkeypass,$replicatecsr,$replicatesubj); &mail_csr('hostname',$lonCluster,$perlvar{'lonHostID'},$hostname,$certsdir,$connectcsr,$replicatecsr,$perlstaticref); print "\nRetrieving status information for SSL key and certificates ...\n\n"; ($certinfo,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,$sslref) = &get_cert_status($perlvar{'lonHostID'},$hostname,$perlstaticref); if (ref($sslref) eq 'HASH') { %sslstatus = %{$sslref}; } } elsif ($choice2 eq '3') { if (-e "$certsdir/$replicatecsr") { &mail_csr('hostname',$lonCluster,$perlvar{'lonHostID'},$hostname,$certsdir,$connectcsr,$replicatecsr,$perlstaticref); } } } elsif (($sslstatus{'hostname'} == 0) || ($sslstatus{'hostname'} == 4) || ($sslstatus{'hostname'} == 5)) { my $sslkeypass; if ($sslstatus{'key'} == 1) { print(<; chomp($choice2); if ($choice2 eq '1') { $sslkeypass = &get_new_sslkeypass(); &make_key($certsdir,$privkey,$sslkeypass); } elsif ($choice2 eq '2') { $sslkeypass = &get_password('Enter existing password for SSL key'); &encrypt_key($certsdir,$privkey,$sslkeypass); } } else { print(<) { if ($configline =~ /^[^\#]?PerlSetVar/) { my ($unused,$varname,$varvalue)=split(/\s+/,$configline); chomp($varvalue); $LCperlvar{$varname}=$varvalue; } } close(CONFIG); } return \%LCperlvar; } sub get_sslnames { my %sslnames = ( key => 'lonnetPrivateKey', host => 'lonnetCertificate', hostname => 'lonnetHostnameCertificate', ca => 'lonnetCertificateAuthority', ); return %sslnames; } sub get_ssldesc { my %ssldesc = ( key => 'Private Key', host => 'Connections Certificate', hostname => 'Replication Certificate', ca => 'LON-CAPA CA Certificate', ); return %ssldesc; } sub get_cert_status { my ($lonHostID,$hostname,$perlvarstatic) = @_; my $currcerts = &LONCAPA::SSL::print_certstatus({$lonHostID => $hostname,},'text','cgi'); my ($lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,%sslstatus); my $output = ''; if ($currcerts eq "$lonHostID:error") { $output .= "No information available for SSL certificates\n"; $sslstatus{'key'} = -1; $sslstatus{'host'} = -1; $sslstatus{'hostname'} = -1; $sslstatus{'ca'} = -1; $lonkeystatus = 'unknown status'; $lonhostcertstatus = 'unknown status'; $lonhostnamecertstatus = 'unknown status'; } else { my %sslnames = &get_sslnames(); my %ssldesc = &get_ssldesc(); my %csr; my ($lonhost,$info) = split(/\:/,$currcerts,2); if ($lonhost eq $lonHostID) { my @items = split(/\&/,$info); foreach my $item (@items) { my ($key,$value) = split(/=/,$item,2); if ($key =~ /^(host(?:|name))\-csr$/) { $csr{$1} = $value; } my @data = split(/,/,$value); if (grep(/^\Q$key\E$/,keys(%sslnames))) { my ($checkcsr,$comparecsr); if (lc($data[0]) eq 'yes') { $output .= "$ssldesc{$key} ".$perlvarstatic->{$sslnames{$key}}." available with status = $data[1]\n"; if ($key eq 'key') { $lonkeystatus = "status: $data[1]"; if ($data[1] =~ /ok$/) { $sslstatus{$key} = 1; } } else { my $setstatus; if (($key eq 'host') || ($key eq 'hostname')) { if ($data[1] eq 'otherkey') { $sslstatus{$key} = 4; $setstatus = 1; if ($key eq 'host') { $lonhostcertstatus = "status: created with different key"; } elsif ($key eq 'hostname') { $lonhostnamecertstatus = "status: created with different key"; } } elsif ($data[1] eq 'nokey') { $sslstatus{$key} = 5; $setstatus = 1; if ($key eq 'host') { $lonhostcertstatus = "status: created with missing key"; } elsif ($key eq 'hostname') { $lonhostnamecertstatus = "status: created with missing key"; } } if ($setstatus) { $comparecsr = 1; } } unless ($setstatus) { if ($data[1] eq 'expired') { $sslstatus{$key} = 2; if (($key eq 'host') || ($key eq 'hostname')) { $comparecsr = 1; } } elsif ($data[1] eq 'future') { $sslstatus{$key} = 3; $sslstatus{$key} = 3; } else { $sslstatus{$key} = 1; } if ($key eq 'host') { $lonhostcertstatus = "status: $data[1]"; } elsif ($key eq 'hostname') { $lonhostnamecertstatus = "status: $data[1]"; } } } } else { $sslstatus{$key} = 0; $output .= "$ssldesc{$key} ".$perlvarstatic->{$sslnames{$key}}." not available\n"; if ($key eq 'key') { $lonkeystatus = 'still needed'; } elsif (($key eq 'host') || ($key eq 'hostname')) { $checkcsr = 1; } } if (($checkcsr) || ($comparecsr)) { my $csrfile = $perlvarstatic->{$sslnames{$key}}; $csrfile =~s /\.pem$/.csr/; my $csrstatus; if (-e $perlvarstatic->{'lonCertificateDirectory'}."/$csrfile") { if (open(PIPE,"openssl req -text -noout -verify -in ".$perlvarstatic->{'lonCertificateDirectory'}."/$csrfile 2>&1 |")) { while() { chomp(); $csrstatus = $_; last; } close(PIPE); if ($comparecsr) { my $csrhash; if (open(PIPE,"openssl x509 -in certificate.crt -pubkey -noout -outform pem | sha256sum")) { $csrhash = ; close(PIPE); } } } $output .= "Certificate signing request for $ssldesc{$key} available with status = $csrstatus\n\n"; if ($key eq 'host') { $lonhostcertstatus = 'awaiting signature'; } else { $lonhostnamecertstatus = 'awaiting signature'; } $sslstatus{$key} = 3; } elsif ($checkcsr) { $output .= "No certificate signing request available for $ssldesc{$key}\n\n"; if ($key eq 'host') { $lonhostcertstatus = 'still needed'; } else { $lonhostnamecertstatus = 'still needed'; } } } } } # FIXME If different key, or missing key, or expired, check if there is a csr that does not match the cert, and that may be awaiting signature. } } return ($output,$lonkeystatus,$lonhostcertstatus,$lonhostnamecertstatus,\%sslstatus); } sub get_new_sslkeypass { my $sslkeypass; my $flag=0; # get Password for SSL key while (!$flag) { $sslkeypass = &make_passphrase(); if ($sslkeypass) { $flag = 1; } else { print "Invalid input (a password is required for the SSL key).\n"; } } return $sslkeypass; } sub make_passphrase { my ($got_passwd,$firstpass,$secondpass,$passwd); my $maxtries = 10; my $trial = 0; while ((!$got_passwd) && ($trial < $maxtries)) { $firstpass = &get_password('Enter a password for the SSL key (at least 6 characters long)'); if (length($firstpass) < 6) { print('Password too short.'."\n". 'Please choose a password with at least six characters.'."\n". 'Please try again.'."\n"); } elsif (length($firstpass) > 30) { print('Password too long.'."\n". 'Please choose a password with no more than thirty characters.'."\n". 'Please try again.'."\n"); } else { my $pbad=0; foreach (split(//,$firstpass)) {if ((ord($_)<32)||(ord($_)>126)){$pbad=1;}} if ($pbad) { print('Password contains invalid characters.'."\n". 'Password must consist of standard ASCII characters.'."\n". 'Please try again.'."\n"); } else { $secondpass = &get_password('Enter password a second time'); if ($firstpass eq $secondpass) { $got_passwd = 1; $passwd = $firstpass; } else { print('Passwords did not match.'."\n". 'Please try again.'."\n"); } } } $trial ++; } return $passwd; } sub get_password { my ($prompt) = @_; local $| = 1; print $prompt.': '; my $newpasswd = ''; ReadMode 'raw'; my $key; while(ord($key = ReadKey(0)) != 10) { if(ord($key) == 127 || ord($key) == 8) { chop($newpasswd); print "\b \b"; } elsif(!ord($key) < 32) { $newpasswd .= $key; print '*'; } } ReadMode 'normal'; print "\n"; return $newpasswd; } sub send_mail { my ($hostname,$recipient,$subj,$file) = @_; my $from = 'www@'.$hostname; my $certmail = "To: $recipient\n". "From: $from\n". "Subject: ".$subj."\n". "Content-type: text/plain\; charset=UTF-8\n". "MIME-Version: 1.0\n\n"; if (open(my $fh,"<$file")) { while (<$fh>) { $certmail .= $_; } close($fh); $certmail .= "\n\n"; if (open(my $mailh, "|/usr/lib/sendmail -oi -t -odb")) { print $mailh $certmail; close($mailh); print "Mail sent ($subj) to $recipient\n"; } else { print "Sending mail ($subj) to $recipient failed.\n"; } } return; } sub mail_csr { my ($types,$lonCluster,$lonHostID,$hostname,$certsdir,$connectcsr,$replicatecsr,$perlvarref) = @_; my ($camail,$flag); if ($lonCluster eq 'production' || $lonCluster eq 'development') { $camail = $perlvarref->{'SSLEmail'}; } else { $flag=0; # get Certificate Authority E-mail while (!$flag) { print(<; chomp($choice); if ($choice ne '') { open(OUT,'>>/tmp/loncapa_updatequery.out'); print(OUT 'Certificate Authority Email Address'."\t".$choice."\n"); close(OUT); $camail=$choice; $flag=1; } else { print "Invalid input (an email address is required).\n"; } } } if ($camail) { my $subj; if (($types eq 'both') || ($types = 'host')) { if (-e "$certsdir/$connectcsr") { $subj = "Certificate Request ($lonHostID)"; print(&send_mail($hostname,$camail,$subj,"$certsdir/$connectcsr")); } } if (($types eq 'both') || ($types = 'hostname')) { if (-e "$certsdir/$replicatecsr") { $subj = "Certificate Request (internal-$hostname)"; print(&send_mail($hostname,$camail,$subj,"$certsdir/$replicatecsr")); } } } } sub ssl_info { print(<; chomp($choice); if ($choice ne '') { if (&Locale::Country::code2country(lc($choice))) { open(OUT,'>>/tmp/loncapa_updatequery.out'); print(OUT 'country'."\t".uc($choice)."\n"); close(OUT); $country=uc($choice); $flag=1; } else { print "Invalid input -- a valid two letter country code is required\n"; } } elsif (($choice eq '') && ($posscountry ne '')) { open(OUT,'>>/tmp/loncapa_updatequery.out'); print(OUT 'country'."\t".$posscountry."\n"); close(OUT); $country = $posscountry; $flag = 1; } else { print "Invalid input -- a country code is required\n"; } } return $country; } sub get_state { # get State or Province my $flag=0; my $state = ''; while (!$flag) { print(<; chomp($choice); if ($choice ne '') { open(OUT,'>>/tmp/loncapa_updatequery.out'); print(OUT 'state'."\t".$choice."\n"); close(OUT); $state=$choice; $flag=1; } else { print "Invalid input (a state or province name is required).\n"; } } return $state; } sub get_city { # get City my $flag=0; my $city = ''; while (!$flag) { print(<; chomp($choice); if ($choice ne '') { open(OUT,'>>/tmp/loncapa_updatequery.out'); print(OUT 'city'."\t".$choice."\n"); close(OUT); $city=$choice; $flag=1; } else { print "Invalid input (a city is required).\n"; } } return $city; } sub confirm_locality { my ($domainDescription,$country,$state,$city) = @_; my $flag = 0; while (!$flag) { print(<; chomp($choice); if ($choice == 1) { print(<; chomp($choice2); $domainDescription=$choice2; } elsif ($choice == 2) { print(<; chomp($choice2); $country = uc($choice2); } elsif ($choice == 3) { print(<; chomp($choice2); $state=$choice2; } elsif ($choice == 4) { print(<; chomp($choice2); $city=$choice2; } elsif ($choice == 5) { $flag=1; $state =~ s{/}{ }g; $city =~ s{/}{ }g; $domainDescription =~ s{/}{ }g; } else { print "Invalid input.\n"; } } return ($domainDescription,$country,$state,$city); } sub make_key { my ($certsdir,$privkey,$sslkeypass) = @_; # generate SSL key if ($certsdir && $privkey) { if (-f "$certsdir/lonKey.enc") { my $mode = 0600; chmod $mode, "$certsdir/lonKey.enc"; } open(PIPE,"openssl genrsa -des3 -passout pass:$sslkeypass -out $certsdir/lonKey.enc 2048 2>&1 |"); close(PIPE); if (-f "$certsdir/$privkey") { my $mode = 0600; chmod $mode, "$certsdir/$privkey"; } open(PIPE,"openssl rsa -in $certsdir/lonKey.enc -passin pass:$sslkeypass -out $certsdir/$privkey -outform PEM |"); close(PIPE); if (-f "$certsdir/lonKey.enc") { my $mode = 0400; chmod $mode, "$certsdir/lonKey.enc"; } if (-f "$certsdir/$privkey") { my $mode = 0400; chmod $mode, "$certsdir/$privkey"; } } else { print "Key creation failed. Missing one or more of: certificates directory, key name\n"; } } sub encrypt_key { my ($certsdir,$privkey,$sslkeypass) = @_; if ($certsdir && $privkey) { if ((-f "$certsdir/$privkey") && (!-f "$certsdir/lonKey.enc")) { open(PIPE,"openssl rsa -des3 -in $certsdir/$privkey -out $certsdir/lonKey.enc |"); } } return; } sub make_host_csr { my ($certsdir,$sslkeypass,$connectcsr,$connectsubj) = @_; # generate SSL csr for hostID if ($certsdir && $connectcsr && $connectsubj) { open(PIPE,"openssl req -key $certsdir/lonKey.enc -passin pass:$sslkeypass -new -batch -subj \"$connectsubj\" -out $certsdir/$connectcsr |"); close(PIPE); } else { print "Creation of certificate signing request failed. Missing one or more of: certificates directory, CSR name, or locality information.\n"; } } sub make_hostname_csr { my ($certsdir,$sslkeypass,$replicatecsr,$replicatesubj) = @_; # generate SSL csr for internal hostname if ($certsdir && $replicatecsr && $replicatesubj) { open(PIPE,"openssl req -key $certsdir/lonKey.enc -passin pass:$sslkeypass -new -batch -subj \"$replicatesubj\" -out $certsdir/$replicatecsr |"); close(PIPE); } else { print "Creation of certificate signing request failed. Missing one or more of: certificates directory, CSR name, or locality information.\n"; } } sub get_mail { my $email; my $flag=0; # get E-mail Address while (!$flag) { print(<; chomp($choice); if (($choice ne '') && ($choice =~ /^[^\@]+\@[^\@]+$/)) { $email=$choice; $flag=1; } else { print "Invalid input (a valid email address is required).\n"; } } return $email; } 500 Internal Server Error

Internal Server Error

The server encountered an internal error or misconfiguration and was unable to complete your request.

Please contact the server administrator at root@localhost to inform them of the time this error occurred, and the actions you performed just before this error.

More information about this error may be available in the server error log.