1 /** 2 Access to the EC2 metadata service 3 */ 4 5 module vibe.aws.ec2meta; 6 7 import std.datetime; 8 import std.range; 9 import std.string; 10 import std.typecons; 11 12 import vibe.core.log; 13 import vibe.data.json; 14 import vibe.http.client; 15 import vibe.stream.operations; 16 17 import vibe.aws.aws; 18 import vibe.aws.credentials; 19 20 class EC2RoleException : AWSException 21 { 22 this(bool retriable, string message) 23 { 24 super("EC2RoleException", retriable, message); 25 } 26 } 27 28 /** 29 Obtain AWS credentials from an EC2 role 30 */ 31 class EC2Role : AWSCredentialSource 32 { 33 private static immutable string metadataURL = "http://169.254.169.254/latest/meta-data/"; 34 string m_role; 35 private Nullable!AWSCredentials m_cachedCredentials; 36 private SysTime m_expiry; 37 38 this(string role=null) 39 { 40 m_role = role; 41 } 42 43 @property string role() 44 { 45 return m_role; 46 } 47 48 @property bool cachedCredentialsAvailable() 49 { 50 return !m_cachedCredentials.isNull && Clock.currTime < m_expiry ; 51 } 52 53 AWSCredentials credentials(string credScope) 54 { 55 if (cachedCredentialsAvailable) 56 return m_cachedCredentials; 57 58 detectRole(); 59 60 logInfo("Retrieving " ~ metadataURL ~ "iam/security-credentials/" ~ m_role); 61 auto resp = requestHTTP(metadataURL ~ "iam/security-credentials/" ~ m_role); 62 if (resp.statusCode == 404) 63 throw new EC2RoleException(false, "No such EC2 role for this instance: " ~ m_role); 64 65 if (resp.statusCode != 200) 66 throw new EC2RoleException(false, "Error getting credentials for role: " ~ m_role ~ ": " ~ resp.statusPhrase); 67 68 auto bdy = resp.bodyReader.readAllUTF8(); 69 Json jsonResponse = parseJsonString(bdy); 70 71 auto accessKeyID = jsonResponse["AccessKeyId"].get!string; 72 auto accessKeySecret = jsonResponse["SecretAccessKey"].get!string; 73 auto sessionToken = jsonResponse["Token"].get!string; 74 75 auto expiryDT = SysTime.fromISOExtString(jsonResponse["Expiration"].get!string); 76 77 m_cachedCredentials = AWSCredentials(accessKeyID, accessKeySecret, sessionToken); 78 m_expiry = expiryDT; 79 80 return m_cachedCredentials; 81 } 82 83 void credentialsInvalid(string credScope, AWSCredentials creds, string reason) 84 { 85 // Not much we can do about that, error it out 86 m_cachedCredentials.nullify(); 87 throw new EC2RoleException(false, 88 format("EC2 credentials for role %s rejected for scope %s: %s", 89 m_role, 90 credScope, 91 reason)); 92 } 93 94 /** 95 Try to auto-detect the instance's role name from the metadata service 96 */ 97 private void detectRole() 98 { 99 if (m_role != null && !m_role.empty) return; 100 101 logInfo("Retrieving " ~ metadataURL ~ "iam/security-credentials/"); 102 auto resp = requestHTTP(metadataURL ~ "iam/security-credentials/"); 103 if (resp.statusCode != 200) 104 throw new EC2RoleException(false, "Error autodetecting EC2 role name: " ~ resp.statusPhrase); 105 auto bdy = resp.bodyReader.readAllUTF8(); 106 if (bdy == "") 107 throw new EC2RoleException(false, "Error autodetecting EC2 role name: no role found"); 108 logDebug("Response: " ~ bdy); 109 m_role = bdy; 110 } 111 } 112 113 unittest { 114 // Should parse 115 auto t = SysTime.fromISOExtString("2015-01-23T04:07:43Z"); 116 }