Hi Anatha (and Alena and others): Thanks for your script. I ended up finding this gem of a perl script: https://github.com/snumano/CloudStack-API/blob/master/generate-url.pl in this really excellent article about signed and (mostly) unsigned api calls: http://www.shapeblue.com/2012/05/10/using-the-api-for-advanced-network-management/ I used that gem of a script for my own perl script below:
#!/usr/bin/perl -w use strict; use warnings; use Digest::SHA qw(hmac_sha1); use Encode; use Getopt::Std; use File::Basename qw(basename); use HTTP::Request; use JSON::XS; use LWP::UserAgent; use MIME::Base64; use URI::Encode; my ($content, $debug, $field, $value); my $my_filename = basename($0, ''); our ($opt_a, $opt_d, $opt_f, $opt_s, $opt_u); $MY_CS_IP_AND_PORT = 'Enter your CS Server Name or IP here followed by :8080'; my $site = "http://$MY_CS_IP_AND_PORT/client/api?"; getopt "adfsu"; if (!defined ($opt_u) || !defined ($opt_a) || !defined ($opt_s) || !defined ($opt_f)){ my $usage = "Usage:\n$my_filename -d <0|1> -f <flag: 1. URL "; $usage = "$usage" . "only, OR 2. URL and Output> -u \"command=<cmd>\" "; $usage = "$usage" . "-a <api_key> -s <secret_key>\n"; die ("$usage"); } ###################################################################### # Process input parameters. ###################################################################### my $api_key = $opt_a; $debug = $opt_d; my $secret_key = $opt_s; my $command = $opt_u; # Flag for output: 1. URL only, 2. Both URL and Output my $flag = $opt_f; # URL encoded for special characters. my $uri = URI::Encode->new(); ###################################################################### ### Step 1 ### Combine parts to form initial URL. ###################################################################### my $query = $command."&apiKey=".$api_key; my @list = split (/&/, $query); foreach (@list){ if (/(.+)\=(.+)/) { $field = $1; # Encode_reserved option is set to 1. $value = $uri->encode($2, 1); $_ = $field."=".$value; } } ###################################################################### ### Step 2 ### Lower-case each parameter and parameter value. ###################################################################### foreach (@list){ $_ = lc ($_); } ###################################################################### ### Step 3 # Sort parameter=parameter_values by parameter and join each # with an ampersand. ###################################################################### my $output = join ("&", sort @list); ###################################################################### ### Step 4 ### HMAC SHA1 encode the URL. ###################################################################### my $digest = hmac_sha1($output, $secret_key); my $base64_encoded = encode_base64 ($digest); chomp($base64_encoded); # Encode_reserved option is set to 1. my $url_encoded = $uri->encode($base64_encoded, 1); ###################################################################### ### Step 5 ### Combine parts for the Final URL. ###################################################################### my $final_url = $site."apikey=".$api_key."&".$command."&signature=".$url_encoded; ###################################################################### ### Step 6 ### Print out the Final URL and/or the output from ### executing the Final URL. ###################################################################### if ($flag == 1 || $flag == 2){ if (defined $debug) { if ($debug != 0) { print "FINAL URL:\n"; } } print "$final_url"; if ($flag == 1){ exit; } if ($flag == 2) { my $agent = LWP::UserAgent->new(env_proxy => 1,keep_alive => 1, timeout => 30); my $header = HTTP::Request->new(GET => $final_url); my $request = HTTP::Request->new('GET', $final_url, $header); my $response = $agent->request($request); # Check the outcome of the response if ($response->is_success){ if (defined $debug) { if ($debug != 0) { print "\nHEADERS:\n"; print $response->headers_as_string; } } print "\nCONTENT:\n"; $content = $response->as_string; print $content; exit 0; } elsif ($response->is_error){ print "\nError My Final URL: $final_url\n"; print $response->error_as_HTML; exit 1; } } } Regards, Evan -----Original Message----- From: Anantha Kasetty [mailto:[email protected]] Sent: Saturday, June 09, 2012 6:52 AM To: [email protected] Subject: Re: Having trouble getting a CloudStack API URL with api_key and generated signature to work from the browser (Firefox). Had to dust up my long forgotten Perl skills, here is a sample program use URI::Escape; use Digest::SHA qw(hmac_sha1_base64); sub hmac_digest { my $uri = shift @_; my $key = shift @_; $digest = hmac_sha1_base64($uri, $key); #@#$%! perl does not pad the output of base64 while (length($digest) % 4) { $digest .= '='; } return $digest; } sub escape_hash { my %hash = @_; my @pairs; for my $key (sort keys %hash) { push @pairs, join "=", map { uri_escape($_) } lc($key), lc($hash{$key}); } return join "&", @pairs; } my $secret_key = 'kNd2VxlXxCXwyJGlidr0ZcmcqXSH2refwxZTStD6If4vJu4QmJPOIui0rgr88mDI6DuGQTzP9e QNOjlZBTReKg' ; my $uri = escape_hash( 'apikey'=>'8v_gevjjgdjbbhibmlle4yyhkseqhrefztnv4up2fu3k9y12th7lscsn6-7seago 1ycctnute1ot0v7npcus8q', 'command'=>'listZones', 'response'=>'json'); $signature = uri_escape(hmac_digest($uri, $secret_key)); $uri .= "&signature=$signature"; print "$uri\n"; Let me know if this works for you. regards, Anantha Kasetty On 6/8/12 8:40 PM, "Alena Prokharchyk" <[email protected]> wrote: >On 6/8/12 7:08 PM, "Evan Miller" <[email protected]> wrote: > >>Hi Alena: >> >>-----Original Message----- >>From: Alena Prokharchyk [mailto:[email protected]] >>Sent: Friday, June 08, 2012 3:56 PM >>To: [email protected] >>Subject: Re: Having trouble getting a CloudStack API URL with api_key and >>generated signature to work from the browser (Firefox). >> >>On 6/8/12 12:23 PM, "Evan Miller" <[email protected]> wrote: >> >>>Hi: >>> >>>I am not doing something quite right yet generating a good >>>CloudStack API URL. >>> >>>I still get this same error from the browser when >>>I try to execute the final url: >>> >>>{ "listvirtualmachinesresponse" : {"errorcode" : 401, "errortext" : >>>"unable to verify user credentials"} } >>> >>>Here is what my perl script is doing: >>> >>>Original Command String: >>> >>>apiKey=8v_GEvJJgDjbbHIBmlle4yyHKseQhRefztnv4UP2fU3K9y12TH7lscsn6-7SEaGO1 >>>y >>>C >>>CTnUtE1oT0v7npCuS8Q&command=listVirtualMachines&response=json >>> >>>Sorted (by field), lower-case Command String: >>> >>>apikey=8v_gevjjgdjbbhibmlle4yyhkseqhrefztnv4up2fu3k9y12th7lscsn6-7seago1 >>>y >>>c >>>ctnute1ot0v7npcus8q&command=listvirtualmachines&response=json >>> >>>Encoded, sorted, lower-case Command String: >>> >>>apikey%3D8v_gevjjgdjbbhibmlle4yyhkseqhrefztnv4up2fu3k9y12th7lscsn6-7seag >>>o >>>1 >>>ycctnute1ot0v7npcus8q%26command%3Dlistvirtualmachines%26response%3Djson >> >>Here is the problem - you have to encode just parameter values, not the >>parameters themselves and definitely not "=" and "&" special chars. >> >>You can look at the java code sample I wrote for the dev bootcamp, here >>is >>the link in the source tree: >> >>http://git.cloud.com/cgit/cloudstack-oss/tree/test/src/com/cloud/test/dem >>o >>/Demo.java?h=3.0.x >> >> >>- Begin - >> >>I don't really know Java, but I did see your numbered steps >>at the bottom. >> >>I still get an error for the final url: >> >>{ "listvirtualmachinesresponse" : {"errorcode" : 401, "errortext" : >>"unable to verify user credentials and/or request signature"} } >> >>Here is what I tried ... continuing in perl. >>I encoded just the parameters (not the field values). >>The parameters didn't really require any encoding. >>No special characters, but I did it anyway. >>So, after encoding, the parameters didn't change. >>I did encode the signature before appending to the >>final url. >> >>Here's the flow ... >> >>Original Command String: >> >>apiKey=8v_GEvJJgDjbbHIBmlle4yyHKseQhRefztnv4UP2fU3K9y12TH7lscsn6-7SEaGO1y >>C >>CTnUtE1oT0v7npCuS8Q&command=listVirtualMachines&response=json >> >>Command String with Encoded Parameters: >> >>apiKey=8v_GEvJJgDjbbHIBmlle4yyHKseQhRefztnv4UP2fU3K9y12TH7lscsn6-7SEaGO1y >>C >>CTnUtE1oT0v7npCuS8Q&command=listVirtualMachines&response=json >> >>Sorted (by field), lower-case, encoded Command String: >> >>apikey=8v_gevjjgdjbbhibmlle4yyhkseqhrefztnv4up2fu3k9y12th7lscsn6-7seago1y >>c >>ctnute1ot0v7npcus8q&command=listvirtualmachines&response=json >> >>SHA1 HEX String: >> f8d4d96dd59c3bd562dc32586539fa9162c5ed70 >> >>SHA1 Base64 Signature (using SHA1 HEX String): >> 3wOrhy/SstxN+NbdoT8h/bkla2E >> >>Encoded Signature: >> 3wOrhy%2FSstxN%2BNbdoT8h%2Fbkla2E >> >>Final URL: >> >>http://10.217.5.192:8080/client/api?command=listVirtualMachines&apiKey=8v >>_ >>GEvJJgDjbbHIBmlle4yyHKseQhRefztnv4UP2fU3K9y12TH7lscsn6-7SEaGO1yCCTnUtE1oT >>0 >>v7npCuS8Q&response=json&signature=3wOrhy%2FSstxN%2BNbdoT8h%2Fbkla2E >> >>Does the syntax of the final url, at least, look right? >> >>If so, then there must be something wrong with the signature. >> >>Regards, >>Evan > > > >The syntax looks right to me. > >-Alena. > >> >>- End - >> >> >>-Alena. >> >>> >>>SHA1 HEX String: >>> 9066d795102c0cf8a12322507887122b6b4a6095 >>> >>>SHA1 Base64 Signature (using SHA1 HEX String and Secret Key): >>> SvDq03i4Tql9qkXuZwUDi3HfbH4 >>> >>>Final URL: >>> >>>http://10.217.5.192:8080/client/api?command=listVirtualMachines&apiKey=8 >>>v >>>_ >>>GEvJJgDjbbHIBmlle4yyHKseQhRefztnv4UP2fU3K9y12TH7lscsn6-7SEaGO1yCCTnUtE1o >>>T >>>0 >>>v7npCuS8Q&response=json&signature=SvDq03i4Tql9qkXuZwUDi3HfbH4 >>> >>>I am generating the SHA1 HEX String like so: >>> $digest = sha1_hex ($encode, $secret_key); >>>using this module: >>> use Digest::SHA qw(sha1 sha1_hex sha1_base64); >>> >>>I am generating the SHA1 Base64 Signature like so: >>> $signature = sha1_base64 ($digest, $secret_key); >>> >>> >>>Curious - Is SHA1 HEX different from HMAC SHA1? >>> >>> >>>Regards, >>>Evan >>> >>>-----Original Message----- >>>From: Prasanna Santhanam [mailto:[email protected]] >>>Sent: Friday, June 08, 2012 1:20 AM >>>To: [email protected] >>>Subject: Re: Having trouble getting a CloudStack API URL with api_key >>>and >>>generated signature to work from the browser (Firefox). >>> >>>On Thu, Jun 07, 2012 at 10:38:20PM -0400, Evan Miller wrote: >>>> Hi: >>>> >>>> Right now, I am just testing a simple, final API-based url with >>>>signature in a browser. >>>> I am getting the following authentication related error in the >>>>browser: >>>> >>>> { "listvirtualmachinesresponse" : {"errorcode" : 401, "errortext" : >>>> "unable to verify user credentials and/or request signature"} } >>>> >>>> Here is the final API-based url: >>>> >>>> >>>> http://10.217.5.192:8080/client/api?command=listVirtualMachines&apiKey >>>> =8v_GEvJJgDjbbHIBmlle4yyHKseQhRefztnv4UP2fU3K9y12TH7lscsn6-7SEaGO1yCCT >>>> nUtE1oT0v7npCuS8Q&response=json&signature=1ca7bc1bbc67b8f578c7d094c523 >>>> 537571ee17b1 >>>> >>>> Here is how I built that final url: >>>> >>>> Using perl, it is based on the following pieces: >>>> >>>> my $cs_ip = '10.217.5.192'; >>>> my $base_url = 'http://' . "$cs_ip" . ':8080'; my $api_path = >>>> '/client/api?'; my $api_key = >>>> '8v_gevjjgdjbbhibmlle4yyhkseqhrefztnv4up2fu3k9y12th7lscsn6-7seago1ycct >>>> nute1ot0v7npcus8q'; my $secret_key = >>>> 'kNd2VxlXxCXwyJGlidr0ZcmcqXSH2refwxZTStD6If4vJu4QmJPOIui0rgr88mDI6DuGQ >>>> TzP9eQNOjlZBTReKg'; >>>> >>>> The api_key and secret key come from my account (evan) in domain 1. >>>> The keys were just generated in a new CloudStack GUI session. >>>> The evan account has ROOT Domain privileges. >>>> >>>> Then, following directions in the Developer's Guide ... >>>> >>>> The sorted, lower-case command string ($sorted_lc_cmd) is: >>>> >>>> apikey=8v_gevjjgdjbbhibmlle4yyhkseqhrefztnv4up2fu3k9y12th7lscsn6-7seag >>>> o1ycctnute1ot0v7npcus8q&command=listvirtualmachines&response=json >>>> >>>> >>>> I obtained the hex signature in perl as follows: >>>> >>>> >>>> $signature = hmac_sha1_hex ($sorted_lc_cmd, $secret_key); >>>> >>>> >>>> And, then, put together the final url, as above, with the calculated >>>>signature. >>>> >>>> >>>> What am I doing wrong or missing? >>> >>>Before you obtain the hmac SHA-1 you need to url encode the request url >>>stripping it off reserved characters like "+, !, $" etc. Then you >>>perform >>>the HMAC on the resultant string. This then is passed through a base64 >>>encoder to obtain the signature. It looks like you missed this step. Can >>>you base64 encode the hmac result and give it a shot? >>> >>> >>> >>>-- >>>Prasanna., >>> >>> >> >> >> > >
