Autoset SSH GSSAPIDelegateCredentials on MacOS X

On the Library cluster macs we decided that we should set a specific SSH option for a number of the shared computers on campus (for example: Cardinal). The specific option is GSSAPIDelegateCredentials, and when used with GSSAPIAuthentication and GSSAPIKeyExchange allows users to ssh from our cluster computers to the selected hosts without typing a password (while getting full Kerberos-based services on the new host). We had considered allowing this for all hosts with a stanford.edu address, but concerns about untrusted computers that might have stanford.edu addresses (like all the computers in the Residences), along with some of the details about how names are evaluated for ssh_config meant we had to be a little more savvy. At the same time we were not interested in managing an ever-changing list of hosts.

The solution that we came up with was to decide to trust the hosts listed in the canocial list of stanford hosts found at '/afs/ir.stanford.edu/dev/pubsw/config/ssh/' (that is in afs space). I wrote a script that pulls in this information from the canonical file, and then makes modifications to both /etc/ssh_config and /etc/known_hosts. I am offering this script out to anyone who might like to use it.

Some notes about this script:

This requires that you have an active connection to AFS when it is run. However it does not require that you have a AFS token, so is suitable for running from cron or the like.

This does modify both /etc/ssh_known_hosts and /etc/ssh_config so be aware that there is some risk in this.The modifications that it makes are contained within two lines:

The script assumes it can change anything within those two lines, but does not touch anything else. If the script does not find those two lines, it adds them at the end.

# The goal of this script is to take a ssh_known_hosts file in and to spit out the
# relevant sections for a ssh_config file. Along the way we will also add the items
# in the remote known_host entries into the on-computer one
# This is to allow people to use GSSAPIDelegateCredentials

# We will be looking for sections in the relevent files brackted by:
# #<Start edu.stanford.rescomp section>
# # This area is controlled by a script, and will be overwritten by that script
# #<End edu.stanford.rescomp section>

# first we pull in the ssh_known_hosts file
# we are puling in the one from /afs/ir.stanford.edu/dev/pubsw/config/ssh/
if (open(INPUTFILE, $remoteFileLocation)) {
@inputLines = <INPUTFILE>;
close(INPUTFILE);
} else {
system("/usr/bin/logger 'Could not read the known hosts file. It could be that the afs space is not visible.'");
exit 1;
}

# here we are going to read in the local ssh_known_hosts file and write in the additions

# first we are going to read the file in
my $localKnownHosts;
{
local $/;
if (open(LOCALKNOWNHOSTS, "/etc/ssh_known_hosts")) {
$localKnownHosts = <LOCALKNOWNHOSTS>;
close(LOCALKNOWNHOSTS);

unless ($localKnownHosts =~ /\#\<Start edu\.stanford\.rescomp section\>.*#\<End edu\.stanford\.rescomp section\>/s) {
# if the tag line does not already exist, then we need to add it to the end
$localKnownHosts .= "\n\n#<Start edu.stanford.rescomp section> #<End edu.stanford.rescomp section>\n";
system("/usr/bin/logger 'The required marked-off area in /etc/ssh_known_hosts was not there, so it was added to the end of the file'");
}

# we now know that it has the section we are looking for
# so we need to plug things in

$replacementText = "#<Start edu.stanford.rescomp section>\n#\tThis area is controlled by a script, and will be overwritten by that script.\n#\tThis was last rewritten on $todayText based on the ssh_known_hosts file last changed on $remoteKnownHostsDateText\n\n" . join("", @inputLines) . "\n#<End edu.stanford.rescomp section>";
$localKnownHosts =~ s/\#\<Start edu\.stanford\.rescomp section\>.*#\<End edu\.stanford\.rescomp section\>/$replacementText/s;

# now we are ready to write this file back out
if (open(LOCALKNOWNHOSTS, ">", "/etc/ssh_known_hosts")) {
print LOCALKNOWNHOSTS $localKnownHosts;
close LOCALKNOWNHOSTS;
} else {
# we are unable to write out the known host files
system("/usr/bin/logger 'Unable to write back changes to the /etc/ssh_known_hosts file'");
}

} else {
# here we have failed to open the known_hosts file, so should provide some warning
system("/usr/bin/logger 'Unable to open /etc/ssh_known_hosts for reading, so no changes have been made'");
}
}

# now to work on the ssh_config file
{
$outputBuffer = "";
$inCommentBlock = "yep";
my $lastHostNames; # since there are multiple key types for each group of servers, we only need one line
# now to iterate through the lines and create the entries
# it would be nice if we could put this all on one line, but that seems to not be the case
foreach $line (@inputLines) {

# at this point we should have a block of text ready to inset into the ssh_config file

local $/;

# so we will first read in the file as a template
if (open(LOCALSSHCONFIG, "/etc/ssh_config")) {
$localSshConfig = <LOCALSSHCONFIG>;
close(LOCALSSHCONFIG);

unless ($localSshConfig =~ /\#\<Start edu\.stanford\.rescomp section\>.*#\<End edu\.stanford\.rescomp section\>/s) {
# if the tag line does not already exist, then we need to add it to the end
$localSshConfig .= "\n\n#<Start edu.stanford.rescomp section> #<End edu.stanford.rescomp section>\n";
system("/usr/bin/logger 'The required marked-off area in /etc/ssh_config was not there, so it was added to the end of the file'");
}

# here we know that it has the section we are looking for
# so we need to plug things in

$replacementText = "\n#<Start edu.stanford.rescomp section>\n#\tThis area is controlled by a script, and will be overwritten by that script.\n#\tThis was last rewritten on $todayText based on the ssh_known_hosts file last changed on $remoteKnownHostsDateText\n\n" . $outputBuffer . "\n#<End edu.stanford.rescomp section>\n";
$localSshConfig =~ s/\n\#\<Start edu\.stanford\.rescomp section\>.*#\<End edu\.stanford\.rescomp section\>\n/$replacementText/s;

# now we are ready to write this file back out
if (open(LOCALSSHCONFIG, ">", "/etc/ssh_config")) {
print LOCALSSHCONFIG $localSshConfig;
close LOCALSSHCONFIG;
} else {
# we are unable to write out the known host files
system("/usr/bin/logger 'Unable to write back changes to the /etc/ssh_config file'");
}

} else {
system("/usr/bin/logger 'Unable to open /etc/ssh_config for reading, so no changes have been made'");
}
}