Page MenuHomePhabricator

D9782.id25303.diff
No OneTemporary

D9782.id25303.diff

diff --git a/src/future/aws/PhutilAWSFuture.php b/src/future/aws/PhutilAWSFuture.php
--- a/src/future/aws/PhutilAWSFuture.php
+++ b/src/future/aws/PhutilAWSFuture.php
@@ -5,9 +5,12 @@
private $future;
private $awsAccessKey;
private $awsPrivateKey;
+ private $awsSessionToken;
+ private $awsSessionExpiry;
private $awsRegion;
private $builtRequest;
private $params;
+ private $useIAM = false;
abstract public function getServiceName();
@@ -15,41 +18,63 @@
parent::__construct(null);
}
- public function setAWSKeys($access, $private) {
- $this->awsAccessKey = $access;
- $this->awsPrivateKey = $private;
- return $this;
- }
-
- public function getAWSAccessKey() {
+ public final function getAWSAccessKey() {
return $this->awsAccessKey;
}
- public function getAWSPrivateKey() {
+ public final function getAWSPrivateKey() {
return $this->awsPrivateKey;
}
- public function getAWSRegion() {
+ public final function getAWSSessionToken() {
+ return $this->awsSessionToken;
+ }
+
+ public final function getAWSSessionExpiry() {
+ return $this->awsSessionExpiry;
+ }
+
+ public final 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;
}
- public function setAWSRegion($region) {
+ public final function setAWSRegion($region) {
$this->awsRegion = $region;
return $this;
}
- public function getHost() {
- $host = $this->getServiceName().'.'.$this->awsRegion.'.amazonaws.com';
- return $host;
+ public final function getHost() {
+ return $this->getServiceName().'.'.$this->awsRegion.'.amazonaws.com';
}
- public function setRawAWSQuery($action, array $params = array()) {
+ public final function setRawAWSQuery($action, array $params = array()) {
$this->params = $params;
$this->params['Action'] = $action;
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;
+ return $this;
+ }
+
+ protected final function getProxiedFuture() {
if (!$this->future) {
$params = $this->params;
@@ -57,14 +82,23 @@
throw new Exception('You must setRawAWSQuery()!');
}
- if (!$this->getAWSAccessKey()) {
- throw new Exception('You must setAWSKeys()!');
+ if (!$this->getAWSAccessKey() && !$this->useIAM) {
+ throw new Exception('You must setAWSKeys() or setUseIAM(true)!');
}
$params['AWSAccessKeyId'] = $this->getAWSAccessKey();
$params['Version'] = '2011-12-15';
$params['Timestamp'] = date('c');
+ if ($this->useIAM) {
+ if (time() > $this->awsSessionExpiry) {
+ // Instance profile credentials have expired, retrieve a new token.
+ $this->setInstanceProfileCredentials();
+ }
+
+ $params['SecurityToken'] = $this->awsSessionToken;
+ }
+
$params = $this->sign($params);
$uri = new PhutilURI('http://'.$this->getHost().'/');
@@ -76,7 +110,7 @@
return $this->future;
}
- protected function didReceiveResult($result) {
+ protected final function didReceiveResult(array $result) {
list($status, $body, $headers) = $result;
try {
@@ -110,7 +144,6 @@
* http://bit.ly/wU0JFh
*/
private function sign(array $params) {
-
$params['SignatureMethod'] = 'HmacSHA256';
$params['SignatureVersion'] = '2';
@@ -122,20 +155,60 @@
}
$pstr = implode('&', $pstr);
- $sign = "GET"."\n".
- strtolower($this->getHost())."\n".
- "/"."\n".
- $pstr;
-
- $hash = hash_hmac(
- 'sha256',
- $sign,
- $this->getAWSPrivateKey(),
- $raw_ouput = true);
+ $sign = implode("\n", array(
+ 'GET',
+ strtolower($this->getHost()),
+ '/',
+ $pstr,
+ ));
+ $hash = hash_hmac('sha256', $sign, $this->getAWSPrivateKey(), true);
$params['Signature'] = base64_encode($hash);
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/');
+ }
+
+ /**
+ * 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;
+ }
+
}

File Metadata

Mime Type
text/plain
Expires
Mon, Mar 10, 10:54 AM (2 w, 1 d ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7430837
Default Alt Text
D9782.id25303.diff (5 KB)

Event Timeline