Changeset View
Changeset View
Standalone View
Standalone View
src/future/aws/PhutilAWSFuture.php
<?php | <?php | ||||
abstract class PhutilAWSFuture extends FutureProxy { | abstract class PhutilAWSFuture extends FutureProxy { | ||||
private $future; | private $future; | ||||
private $awsAccessKey; | private $awsAccessKey; | ||||
private $awsPrivateKey; | private $awsPrivateKey; | ||||
private $awsSessionToken; | |||||
private $awsSessionExpiry; | |||||
private $awsRegion; | private $awsRegion; | ||||
private $builtRequest; | private $builtRequest; | ||||
private $params; | private $params; | ||||
private $useIAM = false; | |||||
private $useSSL = false; | |||||
abstract public function getServiceName(); | abstract public function getServiceName(); | ||||
public function __construct() { | public function __construct() { | ||||
parent::__construct(null); | parent::__construct(null); | ||||
} | } | ||||
public function setAWSKeys($access, $private) { | public final function getAWSAccessKey() { | ||||
$this->awsAccessKey = $access; | if ($this->useIAM) { | ||||
$this->awsPrivateKey = $private; | $this->refreshInstanceProfileCredentials(); | ||||
return $this; | |||||
} | } | ||||
public function getAWSAccessKey() { | |||||
return $this->awsAccessKey; | return $this->awsAccessKey; | ||||
} | } | ||||
public function getAWSPrivateKey() { | public final function getAWSPrivateKey() { | ||||
if ($this->useIAM) { | |||||
$this->refreshInstanceProfileCredentials(); | |||||
} | |||||
return $this->awsPrivateKey; | return $this->awsPrivateKey; | ||||
} | } | ||||
public function getAWSRegion() { | public final function getAWSSessionToken() { | ||||
return $this->awsSessionToken; | |||||
} | |||||
public final function getAWSSessionExpiry() { | |||||
return $this->awsSessionExpiry; | |||||
} | |||||
public final function setAWSKeys($access, $private) { | |||||
$this->awsAccessKey = $access; | |||||
$this->awsPrivateKey = $private; | |||||
$this->awsSessionToken = null; | |||||
$this->awsSessionExpiry = null; | |||||
return $this; | |||||
} | |||||
public final function getAWSRegion() { | |||||
return $this->awsRegion; | return $this->awsRegion; | ||||
} | } | ||||
public function setAWSRegion($region) { | public final function setAWSRegion($region) { | ||||
$this->awsRegion = $region; | $this->awsRegion = $region; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function getHost() { | public function getHost() { | ||||
$host = $this->getServiceName().'.'.$this->awsRegion.'.amazonaws.com'; | return $this->getServiceName().'.'.$this->awsRegion.'.amazonaws.com'; | ||||
return $host; | } | ||||
public function getPath() { | |||||
return ''; | |||||
} | } | ||||
public function setRawAWSQuery($action, array $params = array()) { | public final function setRawAWSQuery($action, array $params = array()) { | ||||
$this->params = $params; | $this->params = $params; | ||||
$this->params['Action'] = $action; | $this->params['Action'] = $action; | ||||
return $this; | return $this; | ||||
} | } | ||||
protected function getProxiedFuture() { | /** | ||||
* Set whether to use instance profile credentials for authentication. See | |||||
* http://docs.aws.amazon.com/IAM/latest/UserGuide/instance-profiles.html for | |||||
* more information | |||||
* | |||||
* @param bool True to use instance profile credentials, otherwise false. | |||||
* @return this | |||||
*/ | |||||
public final function setUseIAM($use_iam) { | |||||
$this->useIAM = $use_iam; | |||||
if ($this->useIAM) { | |||||
$this->awsAccessKey = null; | |||||
$this->awsPrivateKey = null; | |||||
} | |||||
return $this; | |||||
} | |||||
public final function setUseSSL($use_ssl) { | |||||
$this->useSSL = $use_ssl; | |||||
return $this; | |||||
} | |||||
protected final function getProxiedFuture() { | |||||
if (!$this->future) { | if (!$this->future) { | ||||
$params = $this->params; | $params = $this->params; | ||||
if (!$this->params) { | if (!$this->params) { | ||||
throw new Exception('You must setRawAWSQuery()!'); | throw new Exception('You must setRawAWSQuery()!'); | ||||
} | } | ||||
if (!$this->getAWSAccessKey()) { | if (!$this->getAWSAccessKey() && !$this->useIAM) { | ||||
throw new Exception('You must setAWSKeys()!'); | throw new Exception('You must setAWSKeys() or setUseIAM(true)!'); | ||||
} | } | ||||
$params['AWSAccessKeyId'] = $this->getAWSAccessKey(); | $params['AWSAccessKeyId'] = $this->getAWSAccessKey(); | ||||
$params['Version'] = '2011-12-15'; | $params['Version'] = '2011-12-15'; | ||||
$params['Timestamp'] = date('c'); | $params['Timestamp'] = date('c'); | ||||
if ($this->useIAM) { | |||||
$this->refreshInstanceProfileCredentials(); | |||||
$params['SecurityToken'] = $this->awsSessionToken; | |||||
} | |||||
$params = $this->sign($params); | $params = $this->sign($params); | ||||
$protocol = $this->useSSL ? 'https' : 'http'; | |||||
$uri = new PhutilURI('http://'.$this->getHost().'/'); | $uri = new PhutilURI($protocol.'://'.$this->getHost().'/'); | ||||
$uri->setQueryParams($params); | $uri->setQueryParams($params); | ||||
if ($this->useSSL) { | |||||
$this->future = new HTTPSFuture($uri); | |||||
} else { | |||||
$this->future = new HTTPFuture($uri); | $this->future = new HTTPFuture($uri); | ||||
} | } | ||||
} | |||||
return $this->future; | return $this->future; | ||||
} | } | ||||
protected function didReceiveResult($result) { | protected final function didReceiveResult($result) { | ||||
list($status, $body, $headers) = $result; | list($status, $body, $headers) = $result; | ||||
try { | try { | ||||
$xml = @(new SimpleXMLElement($body)); | $xml = @(new SimpleXMLElement($body)); | ||||
} catch (Exception $ex) { | } catch (Exception $ex) { | ||||
$xml = null; | $xml = null; | ||||
} | } | ||||
Show All 17 Lines | protected final function didReceiveResult($result) { | ||||
return $xml; | return $xml; | ||||
} | } | ||||
/** | /** | ||||
* http://bit.ly/wU0JFh | * http://bit.ly/wU0JFh | ||||
*/ | */ | ||||
private function sign(array $params) { | private function sign(array $params) { | ||||
$params['SignatureMethod'] = 'HmacSHA256'; | $params['SignatureMethod'] = 'HmacSHA256'; | ||||
$params['SignatureVersion'] = '2'; | $params['SignatureVersion'] = '2'; | ||||
ksort($params); | ksort($params); | ||||
$pstr = array(); | $pstr = array(); | ||||
foreach ($params as $key => $value) { | foreach ($params as $key => $value) { | ||||
$pstr[] = rawurlencode($key).'='.rawurlencode($value); | $pstr[] = rawurlencode($key).'='.rawurlencode($value); | ||||
} | } | ||||
$pstr = implode('&', $pstr); | $pstr = implode('&', $pstr); | ||||
$sign = "GET"."\n". | $sign = implode("\n", array( | ||||
strtolower($this->getHost())."\n". | 'GET', | ||||
"/"."\n". | strtolower($this->getHost()), | ||||
$pstr; | '/', | ||||
$pstr, | |||||
$hash = hash_hmac( | )); | ||||
'sha256', | |||||
$sign, | |||||
$this->getAWSPrivateKey(), | |||||
$raw_ouput = true); | |||||
$hash = hash_hmac('sha256', $sign, $this->getAWSPrivateKey(), true); | |||||
$params['Signature'] = base64_encode($hash); | $params['Signature'] = base64_encode($hash); | ||||
return $params; | return $params; | ||||
} | } | ||||
/** | |||||
* Returns a URI which can be used to retrieve instance metadata from an AWS | |||||
* EC2 instance. See | |||||
* http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/ec2-instance-metadata.html | |||||
* for more information. | |||||
* | |||||
* @return PhutilURI | |||||
*/ | |||||
protected final function getInstanceMetadataURL() { | |||||
return new PhutilURI('http://169.254.169.254/latest/meta-data/'); | |||||
} | |||||
/** | |||||
* Refresh credentials from the instance profile. | |||||
* | |||||
* @return this | |||||
*/ | |||||
public function refreshInstanceProfileCredentials() { | |||||
if ($this->useIAM) { | |||||
$this->awsAccessKey = null; | |||||
$this->awsPrivateKey = null; | |||||
$this->awsSessionToken = null; | |||||
$this->awsSessionExpiry = null; | |||||
if (time() > $this->awsSessionExpiry) { | |||||
hach-que: Setting `$this->awsSessionExpiry` to null right before comparing it? | |||||
// Instance profile credentials have expired, retrieve a new token. | |||||
$this->setInstanceProfileCredentials(); | |||||
} | |||||
} | |||||
return $this; | |||||
} | |||||
/** | |||||
* Set credentials from the instance profile. | |||||
* | |||||
* http://bit.ly/1mLDoQ3 | |||||
*/ | |||||
private function setInstanceProfileCredentials() { | |||||
$url = $this->getInstanceMetadataURL()->appendPath( | |||||
'/iam/security-credentials/'); | |||||
// Get role. | |||||
$future = new HTTPFuture($url); | |||||
list($response, $headers) = $future->resolvex(); | |||||
$credentials = trim($response); | |||||
// Get credentials. | |||||
$future = new HTTPFuture($url->appendPath($credentials)); | |||||
list($response, $headers) = $future->resolvex(); | |||||
$response = phutil_json_decode($response); | |||||
if ($response['Code'] !== 'Success') { | |||||
throw new RuntimeException( | |||||
pht('Unexpected response code: "%s"', $response['Code'])); | |||||
} | |||||
$this->awsAccessKey = $response['AccessKeyId']; | |||||
$this->awsPrivateKey = $response['SecretAccessKey']; | |||||
$this->awsSessionToken = $response['Token']; | |||||
$this->awsSessionExpiry = strtotime($response['Expiration']); | |||||
return $this; | |||||
} | |||||
} | } |
Setting $this->awsSessionExpiry to null right before comparing it?