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.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
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 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 |
#!/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 "ntWelcome to the NetApp rCLI!n"; print "Connected to " . $hostname . ", OnTAP " . $ontap . ", using " . $opts->{ 'protocol' } . "n"; print "tType "exit" to leave the rCLI." . "nn"; 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 = ) { 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 sets*(.*)$/ ) { 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 failedn"; chomp ( $options{ 'password' } = ); system("stty echo") and die "ERROR: stty failedn"; } print "n"; } return %options; } sub print_usage { print <<EOU Missing or incorrect arguments! na-rcli.pl --hostname|-H --username|-u [ --password|-p ] [ --protocol|-P HTTP|HTTPS ] na-rcli.pl --help|-h EOU } |
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.
@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
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
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