Security is one of those things that everyone knows they need to do, but it rarely gets done to the level that it should be. This, at least in my experience, is primarily because security makes general, day-to-day tasks more difficult. Take, for instance, rsh. Rsh by itself is a great time saver…admit it…it’s great to just be able to execute commands from your admin host and have the results returned back. You can parse them however you like using standard operating system tools like grep, awk, and sed, and best of all (or perhaps worst…) you don’t have to type the password repeatedly.
However, all of the benefits of rsh can be realized using ssh, it just takes a little more setup. But, I’m not going to get into that today. What if you just want a way to securely execute commands against your NetApp without consuming the sole connection to your your filer via ssh (you have telnet and rsh disabled, right?). What if you don’t want to enable ssh, telnet, or rsh but still want to have a pseudo command line? Assuming you have SSL (https) access enabled, you can use the Perl SDK to access, and execute commands against, your filer almost like you were telnet/ssh’d into it.
The magic comes from the undocumented system-cli SDK command. It allows you to execute almost any command just as though you were sitting at the console.
The great part is that with this, you can accomplish probably 99% or more of all tasks having only one access method enabled to your NetApp: the https/ssl option. SSH, RSH, telnet and HTTP can all be disabled.
I say almost because there are two types of commands that do not work using the below Perl script. The first type is non-terminating commands. These, at least off the top of my head, are primarily the stats show commands with the –i option specified. With the –i option, the stats command repeats every number of seconds specified. Now, the caveat to this is that you can also specify a –c option that limits the number of occurrences to the number specified. The downside to this is that if you issue a command like stats show –i 5 –c 5 volume:*:read_ops then the command will take 25 seconds, at which point the results, as a whole, will be returned.
This also applies to issuing man commands. Man will not return (at least with the simulator) to STDOUT, so system-cli doesn’t capture the output.
So, without any more pontificating by me, here is some sample output and the script. If you would like to see additional examples, let me know in the comments.
Welcome to the NetApp rCLI!
Connected to netapp1, OnTAP 7.3.1, using HTTPS
Type "exit" to leave the rCLI.
not_root@netapp1> hostname
netapp1
not_root@netapp1> vol status
Volume State Status Options
vol0 online raid0, flex root, no_atime_update=on,
create_ucode=on,
convert_ucode=on
not_root@netapp1> stats list counters volume
Counters for object name: volume
avg_latency
total_ops
read_data
read_latency
read_ops
write_data
write_latency
write_ops
other_latency
other_ops
not_root@netapp1> priv set advanced
Warning: These advanced commands are potentially dangerous; use
them only when directed to do so by Network Appliance
personnel.
not_root@netapp1*> stats list counters volume
Counters for object name: volume
avg_latency
total_ops
read_data
read_latency
read_ops
write_data
write_latency
write_ops
other_latency
other_ops
nfs_read_data
nfs_read_latency
nfs_read_ops
nfs_write_latency
nfs_write_ops
nfs_other_latency
nfs_other_ops
cifs_read_data
cifs_read_latency
cifs_read_ops
cifs_write_data
cifs_write_latency
cifs_write_ops
cifs_other_latency
cifs_other_ops
not_root@netapp1*> priv set
not_root@netapp1> exit
#!/usr/bin/perl -w # # na-rcli.pl - written by Andrew Sullivan, 2010-02-08 # # Please report bugs and request improvements at http://get-admin.com/blog/?p=947 # # The NetApp SDK can be found here: http://communities.netapp.com/docs/DOC-1110 # # Options are: # --hostname|-H = (mandatory) hostname or IP of NetApp to connect to # --username|-u = (mandatory) username to connect with # --password|-p = (optional) password for user, will be prompted if not supplied # --protocol|-P = (optional) Currently only HTTP and HTTPS are available, HTTPS # is the default # # Examples: # na-rcli.pl --hostname my_filer --username not_root --password some_secret # This will result in you connecting to the host using the HTTPS protocol. # # na-rcli.pl -H my_filer -u not_root -P HTTP # Short method of connecting, will prompt for password and use HTTP connection # # TODO: # Add support for batch mode # Add support for SSH as the transport method # # You may need to uncomment this line and correct the path if the NetApp Perl SDK # is not available in your default Perl library path. #use lib "./NetApp"; use strict; use Getopt::Long qw(:config no_ignore_case); use NaServer; use NaElement; main( ); sub main { my $opts = parse_options(); $opts->{ 'privState' } = "admin"; my $server = getFiler( $opts->{ 'hostname' }, $opts->{ 'username' }, $opts->{ 'password' }, $opts->{ 'protocol' } ); # some nice data to print for the user my $ontap = ( getOnTAP( $server ) =~ /NetApp Release (.*?):/ ? $1 : "unknown" ); my $hostname = executeCli( $server, $opts, "hostname" ); chomp( $hostname ); $opts->{ 'hostname' } = $hostname; print "\n\tWelcome to the NetApp rCLI!\n"; print "Connected to " . $hostname . ", OnTAP " . $ontap . ", using " . $opts->{ 'protocol' } . "\n"; print "\tType \"exit\" to leave the rCLI." . "\n\n"; print prompt( $opts ); # loop until we break on purpose, accepting input from the user. Ctrl-C will exit the program. # Fortunately, there is no state...each command is a seperate entity which reconnects and # reauthenticates against the NetApp each time. This means we don't have to trap interrupts # in order to cleanup after ourselves and prevent lingering connections. while ( my $line = <STDIN> ) { chomp($line); # exit if requested if ( $line =~ /[Ee][Xx][Ii][Tt]/ ) { last; } # passthrough if empty line if ( $line eq "" || ! $line ) { next; } # execute our request my $result = executeCli( $server, $opts, $line ); # show the goods print $result; } continue { # check for one of the priv elevation states #print " Continue: " . $line . "\n"; if ( $line =~ /^priv set\s*(.*)$/ ) { my $state = $1; chomp($state); if ( $state ne "admin" && $state ne "diag" && $state ne "advanced" ) { $state = "admin"; } $opts->{ 'privState' } = $state; } # show the prompt each time print prompt( $opts ); } } sub prompt { my ($opts) = @_; my $priv = ($opts->{ 'privState' } eq "advanced" || $opts->{ 'privState' } eq "diag") ? '*' : ''; # create the default prompt return $opts->{ 'username' } . "@" . $opts->{ 'hostname' } . $priv . "> "; } sub getFiler { my ($hostname, $username, $password, $protocol) = @_; my $s = NaServer->new($hostname, 1, 3); $s->set_style('LOGIN'); $s->set_admin_user($username, $password); $s->set_transport_type($protocol); return $s; } sub executeCli { my ($server, $opts, $command) = @_; # form our request my $request = NaElement->new('system-cli'); my $args = NaElement->new('args'); my $executable = split_string( $command ); for my $arg ( @{ $executable } ) { $args->child_add_string('arg', $arg); } $request->child_add($args); # elevate our priv level, if needed if ( $opts->{ 'privState' } ne "admin" ) { $request->child_add_string('priv', $opts->{ 'privState' }); } # execute and print an error or the result from the NetApp my $result = $server->invoke_elem($request); if ($result->results_errno != 0) { print STDERR 'Invoke failed! Reason: ' . $result->results_reason() . "\n"; print STDERR 'Exiting rCLI...'; exit(1); } else { return $result->child_get_string('cli-output'); } } # extracting out the passed command, taking into account quoted strings that should # be passed as a single argument sub split_string { # loosely based on http://www.perlmonks.org/?node_id=552969 my $text = shift; my @new = (); push(@new, $+) while $text =~ /( # groups the phrase inside double quotes "([^\"\\]*(?:\\.[^\"\\]*)*)"\s? # groups the phrase inside single quotes | '([^\'\\]*(?:\\.[^\'\\]*)*)'\s? # unquoted strings | ([^\s]+)\s? )/gx; return \@new; } sub getOnTAP { my ($server) = @_; my $request = NaElement->new('system-get-version'); my $result = $server->invoke_elem($request); if ($result->results_errno != 0) { print STDERR 'Invoke failed! Reason: ' . $result->results_reason() . "\n"; exit(1); } else { return $result->child_get_string('version'); } } sub parse_options { my %options = ( 'hostname' => '', 'username' => '', 'password' => '', 'protocol' => 'HTTPS', 'help' => 0 ); GetOptions( \%options, 'hostname|H=s', 'username|u=s', 'password|p:s', 'protocol|P:s', 'help|h' ); if (! $options{ 'hostname' } || ! $options{ 'username' } || $options{ 'help' }) { print_usage(); exit(1); } $options{'protocol'} = uc( $options{'protocol'} ); # default to HTTP protocol if ( $options{'protocol'} ne "HTTP" || $options{'protocol'} ne "HTTPS" ) { $options{'protocol'} = "HTTP"; } if (! $options{ 'password' }) { print "Enter password: "; if ( $^O eq "MSWin32" ) { require Term::ReadKey; Term::ReadKey->import( qw(ReadMode) ); Term::ReadKey->import( qw(ReadLine) ); ReadMode('noecho'); chomp( $options{ 'password' } = ReadLine(0) ); ReadMode('normal'); } else { system("stty -echo") and die "ERROR: stty failed\n"; chomp ( $options{ 'password' } = <STDIN> ); system("stty echo") and die "ERROR: stty failed\n"; } print "\n"; } return \%options; } sub print_usage { print <<EOU Missing or incorrect arguments! na-rcli.pl --hostname|-H <hostname> --username|-u <username> [ --password|-p <password> ] [ --protocol|-P HTTP|HTTPS ] na-rcli.pl --help|-h EOU }
Martin | 15-Mar-10 at 6:57 pm | Permalink
This is just awesome.
It works, but I see this error pop up:
readline() on unopened filehandle S at /usr/local/admin/scripts/lib/perl/NetApp/NaServer.pm line 467
Please email me if it’s easy to fix.
Andrew | 02-Apr-10 at 10:58 pm | Permalink
@Martin,
Sorry for a very delayed response, hope you won’t hold it against me : ) What version of the SDK are you using? There seems to be some inconsistencies between the versions and I’m trying to narrow down the problem versions.
Thanks for reading!
Andrew
Devon | 09-Jul-10 at 1:21 pm | Permalink
Very interesting script. My apologies if I’m missing the point, but I have to ask, why? I have ssh set up for my filers, with rsh and telnet turned off as well. What I do instead from RHEL5 server is set an extremely short alias (for me it’s the last 2 digits of the serial number) to the ssh @ command and simply type
12 lun show
12 aggr show_space -g
etc. Your issues of commands that return constant output like sysstat -x 1 or a lun stats, etc, they work in real time like you’re on the controller, yet I’m not hogging the session. I also have the added benefit of all the unix shell utilities as well, so if I want to search for a lun for a server, it’s just
12 lun show | grep
and all of them show up in a list. Hate how the volumes don’t come in alphabetical order?
12 df -h -s | sort
etc etc etc. I also have the ability to up arrow, home, then change the first 2 digits to execute on another controller, or just do ^12^13 to substitute controller 12 for 13 and execute the same command. This makes swapping between controllers very easy.
Devon
Andrew | 13-Jul-10 at 9:27 am | Permalink
Devon,
Thanks for reading!
Admittedly, there isn’t a great number of uses for the script in it’s current form. My main desire when I started creating this script was to see if it is possible to give a pseudo environment for the plethora of scripts that have been written to parse RSH/SSH output from a NetApp.
Eventually I’d like to make it so that rather than RSH/SSH to a filer to get that information, you simply alias a script like this one to “act” like the command line. This ensures that the communication takes place securely (assuming you are using HTTPS)…it also eliminates one of the major hurdles that a lot of people have with getting rid of RSH access…namely, configuring the key based authentication.
Thanks again,
Andrew