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 }