Encrypting confidential files on the install server: Difference between revisions
m (+ category) |
|||
(12 intermediate revisions by one other user not shown) | |||
Line 6: | Line 6: | ||
== Overview == | == 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), | |||
Well, you know how to do that. Choose a reasonable name for the user id ( | |||
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. | 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 | Encrypt your confidential files and add information about file owner and permission. The syntax expected by the decryption script | ||
Line 144: | Line 142: | ||
The scripts uses the perl module Crypt::GPG and expects the public gpg keyring in the directory ''/etc/fai/gnupg''. | 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 | |||
<pre> | |||
#! /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 | |||
... | |||
</pre> | |||
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: | |||
<pre> | |||
#!/usr/bin/perl | |||
#********************************************************************* | |||
# | |||
# fai-secrets-install: 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 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; | |||
} | |||
</pre> | |||
''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. | |||
[[Category:Howto]] |
Latest revision as of 10:32, 17 November 2009
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-secrets-install: 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 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.