Video Screencast Help
Symantec to Separate Into Two Focused, Industry-Leading Technology Companies. Learn more.
Backup and Recovery Community Blog

NBU Pre-7.5: Controlling Restores

Created: 02 Apr 2012 • Updated: 02 Apr 2012
AlanTLR's picture
0 0 Votes
Login to vote

Intro

With NetBackup 7.5 and OpsCenter 7.5 demos, I've seen that restores are much more manageable for ops than before.  I'd long awaited the day that I could have a simple restore administrator look for files and restore them without having to integrate seven layers of internal support.  Now, a simple call to the helpdesk can be routed to an op who can handle a simple file restore.  At least, I hope that's what can happen.

I'm more concerned about security than anything else.  Anyone who can handle sensitive backup data also has the potential to destroy and/or own a system that is being restored.  For example, I could make a well-placed file that gets backed up--let's call it myfile.txt--and restore it over a nice simple critical file like /etc/passwd or /etc/sudoers.  I haven't seen if the new OpsCenter has this sort of limitation (I'm still going over the new features) yet, but I can tell you how I've handled this sort of control in the past.

So, if any of you are still using 7.1 or before, I will pose this problem: How do I allow an op to restore and control the restore securely and safely?  Many of you who say, "you don't," you are correct!  For sensitive data, one would let any trusted administrator complete the restore.  For those of you who agree, you may stop reading, unless you're just curious.

Problem: How to allow an op to restore files securely and safely.

First problem to tackle is the security.  I can't give someone root access, so I use sudo, as I can track the access logs via the log server a lot easier than I can with pfexec.  Also, I want to be able to create this script to be used for webmin's Custom Command module.  Again, I want to stress that this is use at your own risk.  If you do use sudo or webmin, set up logging to a central logging server and monitor it closely.

The second problem is the programming language.  Since I've decided to use webmin, I'll use perl.  In addition to this, I need to know the command-line commands to (a) create a restore job, and (b) search for files.  The command to search for files is 'bplist.'

Now that I've got a platform set up, I want to enable more security.  There are three things I want to control: who can do the restores, which files/directories can be restored, and the netbackup clients that can be accessed.  For ease of revision control for the script, these will be set up via external files.  The hosts file will be /usr/local/etc/surestore.conf.  I can also include the directories, colon-delimited (host:dir1:dir2), but for now, I'm just doing the hosts, as all the directories are going to be the same for all hosts, at least for me.

Solution

Using the platform mentioned above, I start with the following pseudocode:

open the hosts/directory file, save them in a variable array.
get the following from the user:
  source client
  destination client
  files to be restored
  destination directory where files will be restored
  backup start date to search for the files
  backup end date to search for the files
  whether or not we wish to overwrite the files
verify if the source/destination client is allowed
verify if the source/destination directory is allowed
create the restore command based on the user input
start the job
mail the results to the backup admin

To check if a source/destination client is good, I created a simple subroutine called 'check_client'.

sub check_client
{
  my $CHECK_OK=$BAD;
  my $CHECK_CL=$_[0];
  my $client;
  foreach $client (@ALLOWED_CLIENTS)
  {
    chomp($client);
    if ( "$client" eq "$CHECK_CL" )
    {
      $CHECK_OK=$GOOD;
    }
  }
  if ( $CHECK_OK == $BAD )
  {
    print "$BASENAME: $CHECK_CL not in allowed list!\n";
    exit($CHECK_OK);
  }
}

Note in the above subroutine, I have a variable called @ALLOWED_CLIENTS.  This is the list of clients that are allowable for restores.  Variable $CHECK_OK gets set to $GOOD only if the client $client (passed as the first argument to the subroutine) is in that list.  These are variables that I tend to initialize in the global scope.

#!/bin/perl
use File::Basename;
use Cwd;
use strict;

my $VERBOSE=0;
my $fullname=$0;
my $BASENAME=basename($fullname);
my $USERNAME=getpwuid( $< );
my ($SEC,$MIN,$HOUR,$DAY,$MONTH,$YEAR,$WDAY,$YDAY,$ISDST)=localtime(time);
$YEAR += 1900;
$MONTH = sprintf("%02d", $MONTH+1);
$DAY = sprintf("%02d", $DAY);
$HOUR = sprintf("%02d", $HOUR);
$MIN = sprintf("%02d", $MIN);
$SEC = sprintf("%02d", $SEC);
my $TSTAMP="${YEAR}${MONTH}${DAY}${HOUR}${MIN}${SEC}";
my $RENAME_FILE="/tmp/${BASENAME}.$USERNAME.rename.$TSTAMP";
my $TMPFILE="/tmp/${BASENAME}.$USERNAME.$TSTAMP";
my $LOG_FILE="/opt/openv/netbackup/logs/bprestore/bprest.${USERNAME}.$TSTAMP";
my $ALLOWED_CLIENTS_FILE="/usr/local/etc/surestore.conf";
my $ENDDATE="${MONTH}/${DAY}/${YEAR}";
my $CWD=getcwd();

my $DEST_DIR;
my $CHECK_CL;
my $CHECK_OK;
my $DEST;
my $INPUT;
my $OVERWRITE;
my $SOURCE;
my $STARTDATE;
my $client;
my $key;
my $line;

# CONSTANTS
my $BAD=1;
my $GOOD=0;

my $epochYesterday = time - 24 * 60 * 60;  # subtract secs in day from current epoch time
my ($STARTYEAR, $STARTMON, $STARTDAY) = (localtime($epochYesterday))[5,4,3];
$STARTYEAR+=1900;
$STARTMON=sprintf("%02d",$STARTMON+1);
$STARTDAY=sprintf("%02d",$STARTDAY);
$STARTDATE="${STARTMON}/${STARTDAY}/${YEAR}";
chdir("/tmp");

my @ALLOWED_CLIENTS;
my @ALLOWED_DIRS=("u01","u02","tmp");
my $DIRALLOWED="FALSE";

I initialized my dates because I need them in a specific format for the bprestore to understand them.  I also initialize the $LOG_FILE variable so that I know what is being restored.  The $TMPFILE and $RENAME_FILE we'll get to a little bit later.  I also change directory to /tmp, just to be on the safe side.

Also, in this example, I'm taking a shortcut and just creating global allowed directories across all my hosts, as I know they're going to be the same for all the hosts that I'm dealing with.  Now, I just need to get user input.  Crudely, I want my session(s) to look like this:

/usr/local/sbin $ sudo ./surestore.pl
Enter NBU source client to restore FROM: restorehost1
Enter NBU destination client to restore TO: restorehost2
Enter the list of files/directories to be restored.  Hit ENTER on a blank line to end.
/tmp/passwd

Enter NBU destination DIRECTORY: /etc/
DEST_DIRPREFIX: etc
ERROR: The prefix must begin with an allowed top-level directory structure (e.g. /u01, /u02).
The Administrator has been notified.


/usr/local/sbin $ sudo ./surestore.pl
Enter NBU source client to restore FROM: restorehost1
Enter NBU destination client to restore TO: restorehost2
Enter the list of files/directories to be restored.  Hit ENTER on a blank line to end.
/tmp/passwd

Enter NBU destination DIRECTORY: /u01
Enter 'y' if you wish to overwrite existing files: n
Enter Backups Start date to search for files [04/01/2012]:
Enter Backups END date to search for files [04/02/2012]:
Source client: restorehost1
Destination client: restorehost2
Files/Directories to restore:
Directory on Destination Client: /u01
Overwrite: n
Start Date: 04/01/2012
End Date: 04/02/2012
If the above are correct, enter 'y' to continue (any other exits): n

I give the user a chance to review everything before launching the command.  So the interface program now looks like this:

print "Enter NBU source client to restore FROM: ";
$SOURCE = <>;
chomp($SOURCE);
check_client $SOURCE;

print "Enter NBU destination client to restore TO: ";
$DEST = <>;
chomp($DEST);
check_client $DEST;

  print "Enter the list of files/directories to be restored.  Hit ENTER on a blank line to end.\n";
  if ( -e $TMPFILE )
  {
    $TMPFILE="${TMPFILE}-$$";
  }

  open(TFL,">$TMPFILE");
  my $GETDIRS="TRUE";
  while ( $GETDIRS eq "TRUE" )
  {
    my $LINE_DATA = <>;
    chomp($LINE_DATA);
    if ( $LINE_DATA )
    {
      print TFL "$LINE_DATA\n";
    } else
    {
      $GETDIRS="FALSE";
    }
  }
  close(TFL);

print "Enter NBU destination DIRECTORY: ";
$DEST_DIR=<>;
chomp($DEST_DIR);

my @STEMP = split(/[\/]/,$DEST_DIR);
my $DEST_DIRPREFIX=$STEMP[1];

foreach my $dir (@ALLOWED_DIRS)
{
  if ( $DEST_DIRPREFIX eq $dir ) { $DIRALLOWED = "TRUE"; }
}

if ( $DIRALLOWED eq "FALSE" )
{
  print "DEST_DIRPREFIX: $DEST_DIRPREFIX\n";
  print "The prefix must begin with an allowed top-level directory structure (e.g. /u01, /u02).\n";
  exit(-1);
}

print "Enter 'y' if you wish to overwrite existing files: ";
$INPUT=<>;
chomp($INPUT);
if ( $INPUT eq "" ) { $OVERWRITE = "N"; } else { $OVERWRITE = $INPUT; }

print "Enter Backups Start date to search for files [$STARTDATE]: ";
$INPUT=<>;
chomp($INPUT);
if ( $INPUT ne "" ) { $STARTDATE = $INPUT; }

print "Enter Backups END date to search for files [$ENDDATE]: ";
$INPUT=<>;
chomp($INPUT);
if ( $INPUT ne "" ) { $ENDDATE = $INPUT; }

print "Source client: $SOURCE\n";
print "Destination client: $DEST\n";
print "Files/Directories to restore: \n";
open(TFL,"<$TMPFILE") or die "Error opening temp file: $!";
while ( $line = <TMPFILE>)
{
  print $line;
}
close(TFL);
print "Directory on Destination Client: $DEST_DIR\n";
print "Overwrite: $OVERWRITE\n";
print "Start Date: $STARTDATE\n";
print "End Date: $ENDDATE\n";
print "If the above are correct, enter 'y' to continue (any other exits): ";
my $YN = <>;
chomp($YN);

if ( $YN ne "y" && $YN ne "Y" )
{
  unlink($TMPFILE) or warn "Could not remove file: $!";
  exit(3);
}

So, I only check the destination directory for the allowed directories, and not the source directory.  If I really want, I can also check the source directory by adding, after the chomp($LINE_DATA) command:

@STEMP = split(/[\/]/,$LINE_DATA);
$SRC_DIRPREFIX=$STEMP[1];

foreach my $dir (@ALLOWED_DIRS)
{
  if ( $SRC_DIRPREFIX eq $dir ) { $DIRALLOWED = "TRUE"; }
}

if ( $DIRALLOWED eq "FALSE" )
{
  print "SRC_DIRPREFIX: $SRC_DIRPREFIX\n";
  print "The prefix must begin with an allowed top-level directory structure (e.g. /u01, /u02).\n";
  exit(-1);
}

But in my catchall, I exit out of the script, rather than ask again.  Also, I haven't added the email to the admins.  This is a simple one-liner that can be added, either by calling an external 'mailx' command, or using Perl's mail modules.  I'll let you decide which one's better for you.  For simplicity, I'll just put `echo "$LINE_DATA" | mailx -s "SURESTORE ERROR" unix-admins@myhost.net`, just before the exit(-1).  For the destination directory, it's the same, except just change $LINE_DATA to $DEST_DIR.

Now that I've captured all the information I need to restore, I can create the bprestore command:

if ( $OVERWRITE eq "y" || $OVERWRITE eq "Y" )
{
  $CMD=open(CMD,"/usr/openv/netbackup/bin/bprestore -H -y -B -L $LOG_FILE -R $RENAME_FILE -C $SOURCE -D $DEST -S nbumaster -s $STARTD
ATE -e $ENDDATE -f $TMPFILE |");
  while ($line = <CMD>) { print "$line\n"; }
  close(CMD);
} else
{
  $CMD=open(CMD,"/usr/openv/netbackup/bin/bprestore -H -y -K -B -L $LOG_FILE -R $RENAME_FILE -C $SOURCE -D $DEST -S nbumaster -s $STA
RTDATE -e $ENDDATE -f $TMPFILE |");
  while (<CMD>) { print "$line\n"; }
  close(CMD);
}

print "Progress is in ${LOG_FILE}.\n";
unlink($TMPFILE) or warn "Could not remove file: $!";
unlink($RENAME_FILE) or warn "Could not remove file: $!";

chdir("$CWD");

exit(0);

Finally, I can add a few checks for environment variables that I can set with webmin, if I want to use it.  My final program looks like this:

#!/bin/perl
use File::Basename;
use Cwd;
use strict;

my $VERBOSE=0;
my $fullname=$0;
my $BASENAME=basename($fullname);
my $USERNAME=getpwuid( $< );
my ($SEC,$MIN,$HOUR,$DAY,$MONTH,$YEAR,$WDAY,$YDAY,$ISDST)=localtime(time);
$YEAR += 1900;
$MONTH = sprintf("%02d", $MONTH+1);
$DAY = sprintf("%02d", $DAY);
$HOUR = sprintf("%02d", $HOUR);
$MIN = sprintf("%02d", $MIN);
$SEC = sprintf("%02d", $SEC);
my $TSTAMP="${YEAR}${MONTH}${DAY}${HOUR}${MIN}${SEC}";
my $RENAME_FILE="/tmp/${BASENAME}.$USERNAME.rename.$TSTAMP";
my $TMPFILE="/tmp/${BASENAME}.$USERNAME.$TSTAMP";
my $LOG_FILE="/opt/openv/netbackup/logs/bprestore/bprest.${USERNAME}.$TSTAMP";
my $ALLOWED_CLIENTS_FILE="/usr/local/etc/surestore.conf";
my $ENDDATE="${MONTH}/${DAY}/${YEAR}";
my $CWD=getcwd();

my $DEST_DIR;
my $BAD;
my $CHECK_CL;
my $CHECK_OK;
my $DEST;
my $GOOD;
my $INPUT;
my $OVERWRITE;
my $SOURCE;
my $STARTDATE;
my $client;
my $key;
my $line;

chdir("/tmp");

my $epochYesterday = time - 24 * 60 * 60;  # subtract secs in day from current epoch time
my ($STARTYEAR, $STARTMON, $STARTDAY) = (localtime($epochYesterday))[5,4,3];
$STARTYEAR+=1900;
$STARTMON=sprintf("%02d",$STARTMON+1);
$STARTDAY=sprintf("%02d",$STARTDAY);
$STARTDATE="${STARTMON}/${STARTDAY}/${YEAR}";

# CONSTANTS
$BAD=1;
$GOOD=0;

my @ALLOWED_CLIENTS;
my @ALLOWED_DIRS=("u01","u02","tmp");
my $DIRALLOWED="FALSE";

open(CLFILE,"$ALLOWED_CLIENTS_FILE") or die ("Unable to open file: $!");
@ALLOWED_CLIENTS=<CLFILE>;
close(CLFILE);

sub check_client
{
  my $CHECK_OK=$BAD;
  my $CHECK_CL=$_[0];
  my $client;
  foreach $client (@ALLOWED_CLIENTS)
  {
    chomp($client);
    if ( "$client" eq "$CHECK_CL" )
    {
      $CHECK_OK=$GOOD;
    }
  }
  if ( $CHECK_OK == $BAD )
  {
    print "$BASENAME: $CHECK_CL not in allowed list!\n";
    exit($CHECK_OK);
  }
}

if ( ! $ENV{'SOURCE'} )
{
  print "Enter NBU source client to restore FROM: ";
  $SOURCE = <>;
  chomp($SOURCE);
} else
{
  $SOURCE=$ENV{'SOURCE'};
}
check_client $SOURCE;

if ( ! $ENV{'DEST'} )
{
  print "Enter NBU destination client to restore TO: ";
  $DEST = <>;
  chomp($DEST);
} else
{
  $DEST=$ENV{'DEST'};
}
check_client $DEST;

if ( ! $ENV{'FILELIST'} )
{
  print "Enter the list of files/directories to be restored.  Hit ENTER on a blank line to end.\n";
  if ( -e $TMPFILE )
  {
    $TMPFILE="${TMPFILE}-$$";
  }

  open(TFL,">$TMPFILE");
  my $GETDIRS="TRUE";
  while ( $GETDIRS eq "TRUE" )
  {
    my $LINE_DATA = <>;
    chomp($LINE_DATA);
    if ( $LINE_DATA )
    {
      print TFL "$LINE_DATA\n";
    } else
    {
      $GETDIRS="FALSE";
    }
  }
  close(TFL);
} else
{
  if ( -e $TMPFILE )
  {
    $TMPFILE="${TMPFILE}-$$";
  }

  my @FILELIST=split(/\s+/,$ENV{'FILELIST'});
  open(TFL,">$TMPFILE");
  foreach my $item (@FILELIST)
  {
    print TFL "$item\n";
  }
  close(TFL);
}

if ( ! $ENV{'DEST_DIR'} )
{
  print "Enter NBU destination DIRECTORY: ";
  $DEST_DIR=<>;
  chomp($DEST_DIR);
} else
{
  $DEST_DIR=$ENV{'DEST_DIR'};
}

my @STEMP = split(/[\/]/,$DEST_DIR);
my $DEST_DIRPREFIX=$STEMP[1];

foreach my $dir (@ALLOWED_DIRS)
{
  if ( $DEST_DIRPREFIX eq $dir ) { $DIRALLOWED = "TRUE"; }
}

if ( $DIRALLOWED eq "FALSE" )
{
  print "DEST_DIRPREFIX: $DEST_DIRPREFIX\n";
  print "The prefix must begin with an allowed top-level directory structure (e.g. /u01, /u02).\n";
  exit(-1);
}

if ( ! $ENV{'OVERWRITE'} )
{
  print "Enter 'y' if you wish to overwrite existing files: ";
  $INPUT=<>;
  chomp($INPUT);
  if ( $INPUT eq "" ) { $OVERWRITE = "N"; } else { $OVERWRITE = $INPUT; }
} else
{
  $OVERWRITE=$ENV{'OVERWRITE'};
}

if ( ! $ENV{'STARTDATE'} )
{
  print "Enter Backups Start date to search for files [$STARTDATE]: ";
  $INPUT=<>;
  chomp($INPUT);
  if ( $INPUT ne "" ) { $STARTDATE = $INPUT; }
} else
{
  $STARTDATE=$ENV{'STARTDATE'};
}

if ( ! $ENV{'ENDDATE'} )
{
  print "Enter Backups END date to search for files [$ENDDATE]: ";
  $INPUT=<>;
  chomp($INPUT);
  if ( $INPUT ne "" ) { $ENDDATE = $INPUT; }
} else
{
  $ENDDATE=$ENV{'ENDDATE'};
}

 print "Source client: $SOURCE\n";
 print "Destination client: $DEST\n";
 print "Files/Directories to restore: \n";
 open(TFL,"<$TMPFILE") or die "Error opening temp file: $!";
 while ( $line = <TMPFILE>)
 {
   print $line;
 }
 close(TFL);
 print "Directory on Destination Client: $DEST_DIR\n";
 print "Overwrite: $OVERWRITE\n";
 print "Start Date: $STARTDATE\n";
 print "End Date: $ENDDATE\n";

if ( $ENV{'TERM'} )
{
  print "If the above are correct, enter 'y' to continue (any other exits): ";
  my $YN = <>;
  chomp($YN);

  if ( $YN ne "y" && $YN ne "Y" )
  {
    unlink($TMPFILE) or warn "Could not remove file: $!";
    exit(3);
  }
}

open(RFH,">$RENAME_FILE") or die "Could not open for write: $!";
print RFH "change / to $DEST_DIR\n";
close(RFH);

printf "LOG_FILE: $LOG_FILE\n";
printf "RENAME_FILE: $RENAME_FILE\n";
printf "SOURCE: $SOURCE\n";
printf "DEST: $DEST\n";
printf "STARTDATE: $STARTDATE\n";
printf "ENDDATE: $ENDDATE\n";
if ( $OVERWRITE eq "y" || $OVERWRITE eq "Y" )
{
  my $CMD=open(CMD,"/usr/openv/netbackup/bin/bprestore -H -y -B -L $LOG_FILE -R $RENAME_FILE -C $SOURCE -D $DEST -S nbumaster -s $STARTDATE -e $ENDDATE -f $TMPFILE |");
  while ($line = <CMD>) { print "$line\n"; }
  close(CMD);
} else
{
  my $CMD=open(CMD,"/usr/openv/netbackup/bin/bprestore -H -y -K -B -L $LOG_FILE -R $RENAME_FILE -C $SOURCE -D $DEST -S nbumaster -s $STARTDATE -e $ENDDATE -f $TMPFILE |";
  while (<CMD>) { print "$line\n"; }
  close(CMD);
}

unlink($TMPFILE) or warn "Could not remove file: $!";
unlink($RENAME_FILE) or warn "Could not remove file: $!";

chdir("$CWD");
exit(0);

The above program doesn't have the email lines, nor am I checking for the allowed directories in the source files, but as shown before, one can easily add them in.  I didn't cover setting this up for webmin very much; I mostly use this with sudo.  I'll leave that as an exercise for you to do.  Have fun!