OK, I'm at my wit's end at this point, and really need another set of eyes.
I'm trying to create a really lightweight PHP endpoint to push data into an SQS queue for offline processing. I don't want to leverage the entire AWS SDK if I can help it.
IAM Side
- I created a Group and assigned it AmazonSQSFullAccess Policy
- I created a User and assigned it to the aforementioned Group
- I downloaded the credentials. I've verified them about a dozen times at this point
SQS Side
- Created an SQS queue
- Allow access to everyone via the Permissions widget on the queue (seems redundant given the IAM but what do I know). Push a message in using the code below and it works - yay.
- Restrict access to the single user defined in the IAM via the Permissions widget. Get "Access to the resource $sqs_endpoint denied", where $sqs_endpoint = my endpoint
OK, so I figure it has to be the signature. I download the AWSv4 Test Suite and throw it behind a debug switch. I get valid responses returned. BUT, when I turn debug off - back to the error message.
I've tried base64 encoding the payload in case it was a weird escaping issue, still nothing (plus I'm able to push the data in when I allow access to everyone).
At any rate, here's my code. It's got to be an IAM permission issue, no? I can't seem to square the circle:
<?php
define( 'DEBUG', false );
define( 'AMZN_ALGO', 'AWS4-HMAC-SHA256' );
define( 'HASH_ALGO', 'SHA256' );
function getSignatureKey( $key, $date, $region, $service ) {
$kDate = hash_hmac( HASH_ALGO, $date, 'AWS4' . $key, true );
$kRegion = hash_hmac( HASH_ALGO, $region, $kDate, true );
$kService = hash_hmac( HASH_ALGO, $service, $kRegion, true );
$kSigning = hash_hmac( HASH_ALGO, 'aws4_request', $kService, true );
return $kSigning;
}
// these values are the hashes provided in the test suite to validate your approach
function compareTestSig( $type, $val ) {
$testSigs = [
'sts'=>'2e1cf7ed91881a30569e46552437e4156c823447bf1781b921b5d486c568dd1c',
'creq'=>'9095672bbd1f56dfc5b65f3e153adc8731a4a654192329106275f4c7b24d0b6e',
'authz'=>'1a72ec8f64bd914b0e42e42607c7fbce7fb2c7465f63e3092b3b0d39fa77a6fe'
];
if( $testSigs[$type] === $val ) {
return true;
}
return false;
}
$headers = getallheaders();
/**
* Config
* DEBUG = true are all the values from the AWS 4 Test Suite
*/
if( DEBUG === true ) {
define( 'ACCESS_KEY', 'AKIDEXAMPLE' );
define( 'SECRET_ACCESS_KEY', 'wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY' );
define( 'ENDPOINT', 'https://example.amazonaws.com/' );
$amznDate = '20150830T123600Z';
$nrmlDate = '20150830';
$service = 'service';
$version = '2012-11-05';
$payload = 'Param1=value1';
} else {
define( 'ACCESS_KEY', 'MY_ACCESS_KEY_I_KNOW_IS_CORRECT' );
define( 'SECRET_ACCESS_KEY', 'MY_SECRET_ACCESS_KEY_I_CHECKED_100_TIMES' );
define( 'ENDPOINT', 'https://sqs.us-east-1.amazonaws.com/[ENDPOINT_ID]/[QUEUE_NAME]' );
$amznDate = gmdate( "Ymd\THis\Z" );
$nrmlDate = gmdate( 'Ymd' );
$service = 'sqs';
$version = '2012-11-05';
// create the payload
$pBody = [];
$pBody['Action'] = 'SendMessage';
$pBody['MessageBody'] = json_encode( $headers );
$pBody['Version'] = $version;
$payload = http_build_query( $pBody);
}
$host = parse_url( ENDPOINT, PHP_URL_HOST );
$path = parse_url( ENDPOINT, PHP_URL_PATH );
$region = 'us-east-1';
$contentType = 'application/x-www-form-urlencoded; charset=utf8';
$scope = sprintf( '%s/%s/%s/%s', $nrmlDate, $region, $service, 'aws4_request' );
$payloadHash = hash( HASH_ALGO, $payload );
if( DEBUG ) {
if( compareTestSig( 'creq', $payloadHash ) ) {
var_dump( 'CREQ sig PASS' );
} else {
var_dump( 'CREQ sig NO PASS' );
exit;
}
}
/**
* Sign, seal, deliver.
* First, create the header string
*/
$aggregate = [
'content-type' => $contentType,
'host' => $host,
'x-amz-date' => $amznDate
];
ksort( $aggregate );
$canonHeaders = [];
foreach( $aggregate as $k=>$v ) {
$canonHeaders[] = sprintf( "%s:%s", $k, $v );
}
$signedHeadersString = implode( ';', array_keys( $aggregate ) );
/**
* next, create the canonical request and hash it
*/
$canon = sprintf( "%s
%s
%s
%s
%s
%s",
'POST',
$path,
'',
implode( "
", $canonHeaders ),
$signedHeadersString,
$payloadHash
);
$canonHash = hash( HASH_ALGO, $canon );
if( DEBUG ) {
if( compareTestSig( 'sts', $canonHash ) ) {
var_dump( 'STS sig PASS' );
} else {
var_dump( 'STS sig NO PASS' );
exit;
}
}
$toSign = sprintf( "%s
%s
%s
%s", AMZN_ALGO, $amznDate, $scope, $canonHash );
// signingKey will be bin, signature will be in hex
$signingKey = getSignatureKey( SECRET_ACCESS_KEY, $nrmlDate, $region, $service );
$signature = hash_hmac( HASH_ALGO, $toSign, $signingKey );
if( DEBUG ) {
if( compareTestSig( 'authz', $signature ) ) {
var_dump( 'AUTHZ sig PASS' );
} else {
var_dump( 'AUTHZ sig NOPASS' );
exit;
}
}
/**
* set up the post headers
**/
$postHeaders = [];
$postHeaders['Content-Type'] = $contentType;
$postHeaders['Host'] = $host;
$postHeaders['X-Amz-Date'] = $amznDate;
$postHeaders['Authorization'] = sprintf( "%s Credential=%s, SignedHeaders=%s, Signature=%s",
AMZN_ALGO,
ACCESS_KEY . '/' . $scope,
$signedHeadersString,
$signature
);
//do it
$ch = curl_init();
curl_setopt( $ch, CURLOPT_URL, ENDPOINT );
curl_setopt( $ch, CURLOPT_POST, 1 );
curl_setopt( $ch, CURLOPT_HTTPHEADER, $postHeaders );
curl_setopt( $ch, CURLOPT_POSTFIELDS, $payload );
curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
$output = curl_exec( $ch );
curl_close( $ch );
$xml = simplexml_load_string( $output );
if( empty( $xml ) ) {
var_dump( $output );
die( 'UnknownOperationException' );
}
if(! empty( $xml->Error ) ) {
printf( "ERROR %s : %s", $xml->Error->Code, $xml->Error->Message );
exit;
}
echo 'OK=1;';
exit;
?>