Encrypting confidential files on the install server

From FAIWiki
Jump to navigation Jump to search

Motivation

Usually the (read) access restrictions on the files and scripts on the FAI install server are rather weak. So it is a bit tricky to put passwords and other confidential stuff there. We therefore use encryption to store confidential information on the install server. After the installation the confidential files are secured by the login authentication and filesystem permissions, so they can be safely decrypted. As a drawback, someone has to manually login into the freshly installed system and provide the secret key for the encryption.

Overview

A. Generate a gpg key pair for this purpose.

B. Encrypt confidential files, add information about ownership and filesystem permission.

C. Adjust FAI configuration to install the encrypted files in /var/lib/fai/secrets/install/ and the decryption script /usr/local/sbin/fai-secrets-install on the target system and perform installation

D. Log into the freshly installed system (as root) and run fai-secrets-install.

A. Generate a gpg key pair

Well, you know how to do that. Choose a reasonable name for the user id (FAI Installation at ... or something like that), supply a passphrase and note the key id. You can store the public key on the install server (e.g. to encode a random root password at install time) but you don't want to deposit the secret key there.

B. Encrypt confidential files

Encrypt your confidential files and add information about file owner and permission. The syntax expected by the decryption script fai-secrets-install is straightforward and best documented by an example:

##FAI_secret v1.0

type:     file
location: /etc/ssh/ssh_host_dsa_key
owner:    root:root
mode:      600

-----BEGIN PGP MESSAGE-----
Version: GnuPG v1.4.6 (GNU/Linux)

hQIOA6/Tc/NaxvkSEAf/eZRBl/EqLFFKNHBGGOxAGO8PvHEJxDqQq4UVdU58Rfip
HPvBfnNGSsPyskXM1DPfykEMqc/0GH2qGg/hcLJZ4adI2FT7bqfuPcNj33LDJgG1
...
5KvfSE01vPfUcGKERLCHs2XL
=8a9r
-----END PGP MESSAGE-----

The first line (##FAI_secret v1.0) is just a magic cookie. The line "type: file" indicates that the encrypted data is a file content. We have not implemented any other type yet, but one could e.g. imagine a type line that includes a single line of encrypted information into a plain text file.

Here is a helper script /usr/local/sbin/fai-secrets-collect that we install on all our servers. Its purpose is to collect sensitive information of an already installed system which can be put on the install server afterwards. This helps us to preserve ssh host keys during reinstallation::

#!/usr/bin/perl

#*********************************************************************
#
# fai-secrets-collect: collects sensitive system information, encrypts
#                      and writes to /var/lib/fai/secrets/status
#
#  authors:  Thomas Gebhardt <gebhardt_at_hrz.uni-marburg.de>;
#            Andreas Gabriel <gabriel_at_hrz.uni-marburg.de>
#  licence: GPLv2
#  version: 2007-03-06
#
#*********************************************************************

use strict;
use warnings;

#use Getopt::Std;
#use vars qw($opt_d $opt_f $opt_g);

use Env qw(GNUPGHOME HOSTNAME);
use Crypt::GPG;

$HOSTNAME = $HOSTNAME || `hostname`;

#defaults:
my $STATUS_DIR  = "/var/lib/fai/secrets/status";
my $GPG_KEY_ID  = "0x...  here goes your gpg key id";
   $GNUPGHOME   = "/etc/fai/gnupg";   # no "my" here! (tied to ENV variable)

my %SECRETS = (
               "/etc/ssh/ssh_host_dsa_key"      => "ssh_host_dsa_key",
               "/etc/ssh/ssh_host_dsa_key.pub"  => "ssh_host_dsa_key.pub",
               "/etc/ssh/ssh_host_rsa_key"      => "ssh_host_rsa_key",
               "/etc/ssh/ssh_host_rsa_key.pub"  => "ssh_host_rsa_key.pub",
               "/etc/tsm/TSM.PWD"               => "ITSM_password",
               "/etc/exim4/passwd.client"       => "exim4_passwd.client",
               "/etc/libnss-ldap.secret"        => "libnss-ldap.secret",
               "/etc/pam_ldap.secret"           => "pam_ldap.secret",
               ... append files to suit your needs ...
               );


foreach my $file (keys %SECRETS) {
    next unless -r $file;
    my ($mode,$uid,$gid) = (stat $file)[2,4,5];
    $mode &= 07777;      # discard file type info
    $mode = sprintf("%4o",$mode); 
    my $user  = (getpwuid $uid)[0];
    my $group = (getgrgid $gid)[0];

    my $file_content;   # slurp file:
    {
        local( $/, *SECRET ) ;
        open( SECRET, $file ) or die "oh, tested -r just before ???\n";
        $file_content = <SECRET>;
    }

    my $gpg = new Crypt::GPG;
    my $encrypted = $gpg->encrypt ($file_content, $GPG_KEY_ID);

    my $file_dir =  $STATUS_DIR . "/" . $SECRETS{$file};

    mkdir($file_dir) unless( -d $file_dir );

    my $file_status =  $file_dir . "/" . $HOSTNAME ;

    print "encrypting $file as $file_status\n";

    open (ENCRYPTED, "> $file_status")
        or die "can't open $file_status for writing: $?";
    print ENCRYPTED "##FAI_secret v1.0\n";   # magic header
    print ENCRYPTED "\n";
    print ENCRYPTED "type:     file\n";
    print ENCRYPTED "location: $file\n";
    print ENCRYPTED "owner:    $user\:$group\n";
    print ENCRYPTED "mode:     $mode\n";
    print ENCRYPTED "\n";
    print ENCRYPTED $encrypted;
    close ENCRYPTED;

}

print "**********************************************************\n";

print 
"To complete the collection copy all secrets to your fai's
svn working directory with the following command:\n\n";

print "\> scp -r $STATUS_DIR/* \<username\@hostname\>:\<fai_svn_working_dir\>/files/var/lib/fai/secrets/install\n\n";
print "**********************************************************\n\n";

The scripts uses the perl module Crypt::GPG and expects the public gpg keyring in the directory /etc/fai/gnupg.

C. Adjust FAI configuration and perform installation

Configure FAI to install the encrypted data and the decryption script. You know how to do that. Most probably you will write a script like

#! /bin/sh

fcopy -Bi -m root,root,700 /usr/local/sbin/fai-secrets-install
fcopy -Bi -m root,root,700 /usr/local/sbin/fai-secrets-collect

fcopy -Bi -m root,root,600 /var/lib/fai/secrets/install/ssh_host_dsa_key
fcopy -Bi -m root,root,600 /var/lib/fai/secrets/install/ssh_host_dsa_key.pub
...

Be sure to also install all perl modules needed by fai-secrets-install (Crypt::GPG,Term::ReadPassword,Term::ReadLine).

D. Decrypt data with fai-secrets-install

After the installation log into the system and run fai-secrets-install. Here it is:

#!/usr/bin/perl

#*********************************************************************
#
# fai-secret: extract encryted secrets after FAI installation
#  author:  Thomas Gebhardt(gebhardt_at_hrz.uni-marburg.de);
#  licence: GPLv2
#  version: 2006-11-29
#
#*********************************************************************

use strict;
use warnings;

#use Getopt::Std;
#use vars qw($opt_d $opt_f $opt_g);

use Env qw(GNUPGHOME);
use Crypt::GPG;
use Term::ReadPassword;
use Term::ReadLine;
use vars qw($passphrase);

#defaults:
my $SECRETS_DIR = "/var/lib/fai/secrets/install";
my $GPG_KEY_ID  = "0x... your gpg key id goes here";
   $GNUPGHOME   = "/var/lib/fai/secrets/gnupg";   # no "my" here! (tied to ENV variable)
my $GNUPGHOME_REMOTE = "/usr/local/fai/gpg-fai";
my $SSH_USER  = $ARGV[0] || "myaccount@mydesktop.example.com";

my $status;

die "Wrong syntax for SSH_USER \"$SSH_USER\"\n" unless($SSH_USER =~ /^[0-9a-z\-_]+\@[0-9a-z\-_.]+$/i );

# copy gpg keys into local directory:
umask 077;

$status = system ("/bin/mkdir", "-p", $GNUPGHOME);
print "\"/bin/mkdir -p $GNUPGHOME\" exited with error: $?"
     unless $status == 0;

$status = system("/usr/bin/ssh $SSH_USER \"(cd $GNUPGHOME_REMOTE; /bin/tar cf -  *)\" | (cd $GNUPGHOME; /bin/tar xpf - )");
die "copying gpg keys by ssh exited with error: $?"
    unless $status == 0;
system ("/bin/chown", "-R", "root:root", $GNUPGHOME);

opendir(SECRETS_DIR, $SECRETS_DIR) or die "can't opendir $SECRETS_DIR: $!";
while (defined(my $secrets_file = readdir(SECRETS_DIR))) {
    next if $secrets_file =~ /^\.\.?$/;   # skip ".." and "."
    &extract_secrets($SECRETS_DIR . "/" . $secrets_file);
}

# remove gpg keys
$status = system ("/bin/rm", "-r", "-f", $GNUPGHOME);
print "\"/bin/rm -r -f $GNUPGHOME\" exited with error: $?"
     unless $status == 0;

exit;

sub extract_secrets {
    my $file = shift;
    print "Extracting $file ...\n";
    open(FILE, $file) 
         or die "can't open $file for reading: $!";
    my $header = <FILE>; chomp $header;
    if ($header ne "##FAI_secret v1.0"){
        print "  Unknown header: $header\n";
        print "  Skipping $file\n";
        close FILE;
        return
    }
    my ($type, $location, $owner, $mode);
    while (defined(my $line = <FILE>)){
        chomp $line;
        last if $line =~ /^-----BEGIN PGP MESSAGE----/;
        next if $line =~ /^\s*#/;   # skip comments
        next if $line =~ /^\s*$/;   # and blank lines
        if    ($line =~ /^\s*type:\s*(.*?)\s*$/)     {$type = $1;}
        elsif ($line =~ /^\s*location:\s*(.*?)\s*$/) {$location = $1;}
        elsif ($line =~ /^\s*owner:\s*(.*?)\s*$/)    {$owner = $1;}
        elsif ($line =~ /^\s*mode:\s*(.*?)\s*$/)     {$mode = $1;}
        else {print "  can't parse line \"$line\", skipping \n";}
    }
    close FILE;

    if ($type ne "file"){
        print "  Unknown type parameter: $type\n";
        print "  Skipping $file\n";
        return
    }

# get GNUPG passphrase
    unless (defined $passphrase) {
        $passphrase = read_password('Please enter the passphrase of the fai-secret gpg key: ');
        print "\n";
    }

    my $file_content;   # slurp file:
    {
        local( $/, *SECRET ) ;
        open( SECRET, $file ) or die "oh, tested -r just before ???\n";
        $file_content = <SECRET>;
    }

    umask 077;
    my $gpg = new Crypt::GPG;
    $gpg->passphrase($passphrase);
    $gpg->secretkey($GPG_KEY_ID);
    my $decrypted = $gpg->decrypt ($file_content);

    die "Decryption failed; wrong Passphrase?" unless defined $decrypted;

    open( DEST, ">$location" ) or
        die "can't create $location: $?" ;
    print DEST $decrypted ;
    close DEST;

    my $status;
    $status = system("/bin/chown", $owner, $location);
    print "\"/bin/chown $owner $location\" exited with error: $?"
        unless $status == 0;
    $status = system("/bin/chmod", $mode,  $location);
    print "\"/bin/chmod $mode $location\" exited with error: $?"
        unless $status == 0;

}

fai-secrets-install copys the gpg keyrings from the directory /usr/local/fai/gpg-fai/ of a secure remote system (defaults to myaccount@mydesktop.example.com) to /var/lib/fai/secrets/gnupg/, decrypts and installs all files from /var/lib/fai/secrets/install/, and deletes the temporary gpg keyrings afterwards.