15
15
package com .obs .services .internal .security ;
16
16
17
17
import java .io .IOException ;
18
+ import java .util .HashMap ;
19
+ import java .util .Map ;
18
20
import java .util .concurrent .TimeUnit ;
19
21
22
+ import com .obs .log .ILogger ;
23
+ import com .obs .log .LoggerBuilder ;
24
+ import com .obs .services .AbstractClient ;
20
25
import com .obs .services .internal .Constants ;
21
26
import com .obs .services .internal .utils .PropertyManager ;
27
+ import com .obs .services .model .HttpMethodEnum ;
28
+
22
29
import okhttp3 .Call ;
30
+ import okhttp3 .MediaType ;
23
31
import okhttp3 .OkHttpClient ;
24
32
import okhttp3 .Request ;
33
+ import okhttp3 .RequestBody ;
25
34
import okhttp3 .Response ;
26
35
36
+
27
37
public class EcsSecurityUtils {
28
38
/**
29
39
* Default root url for the openstack metadata apis.
30
40
*/
31
41
private static final String OPENSTACK_METADATA_ROOT = "/openstack/latest" ;
42
+ /**
43
+ * Default root url for the metadata apis.
44
+ */
45
+ private static final String METADATA_ROOT = "/meta-data/latest" ;
32
46
33
47
/**
34
48
* Default endpoint for the ECS Instance Metadata Service.
@@ -46,13 +60,32 @@ public class EcsSecurityUtils {
46
60
.writeTimeout (HTTP_CONNECT_TIMEOUT_VALUE , TimeUnit .MILLISECONDS )
47
61
.readTimeout (HTTP_CONNECT_TIMEOUT_VALUE , TimeUnit .MILLISECONDS ).build ();
48
62
63
+ private static final String METADATA_TOKEN_HEADER_KEY = "X-Metadata-Token" ;
64
+ private static final String METADATA_TOKEN_TTL = METADATA_TOKEN_HEADER_KEY + "-Ttl-Seconds" ;
65
+ public static final int DEFAULT_METADATA_TOKEN_TTL_SECONDS = 21600 ;
66
+ private static final String METADATA_TOKEN_RESOURCE_PATH = METADATA_ROOT + "/api/token" ;
67
+ private static final String OPENSTACK_SECURITY_KEY_RESOURCE_PATH = OPENSTACK_METADATA_ROOT + "/securitykey" ;
68
+ private static final ILogger ILOG = LoggerBuilder .getLogger (AbstractClient .class );
49
69
/**
50
70
* Returns the temporary security credentials (access, secret,
51
71
* securitytoken, and expires_at) associated with the IAM roles on the
52
72
* instance.
53
73
*/
54
74
public static String getSecurityKeyInfoWithDetail () throws IOException {
55
- return getResourceWithDetail (getEndpointForECSMetadataService () + OPENSTACK_METADATA_ROOT + "/securitykey" );
75
+ return getSecurityKeyInfoWithDetail (DEFAULT_METADATA_TOKEN_TTL_SECONDS );
76
+ }
77
+
78
+ public static String getSecurityKeyInfoWithDetail (int metadataTokenTTLSeconds ) throws IOException {
79
+ // try get metadataToken
80
+ String metadataApiToken = getMetadataApiToken (metadataTokenTTLSeconds );
81
+ String securityKeyResourcePath = getEndpointForECSMetadataService () + OPENSTACK_SECURITY_KEY_RESOURCE_PATH ;
82
+ if (metadataApiToken .isEmpty ()) {
83
+ // failed to get metadataToken(404 or 405), use IMDSv1
84
+ return getResourceWithDetail (securityKeyResourcePath );
85
+ } else {
86
+ // succeeded to get metadataToken(2xx), use IMDSv2
87
+ return getResourceWithDetailWithMetaDataToken (securityKeyResourcePath , metadataApiToken );
88
+ }
56
89
}
57
90
58
91
/**
@@ -85,7 +118,7 @@ private static String getResourceWithDetail(String endpoint) throws IOException
85
118
}
86
119
87
120
if (!(res .code () >= 200 && res .code () < 300 )) {
88
- String errorMessage = "Get securityKey form ECS failed, Code : " + res .code () + "; Headers : " + header
121
+ String errorMessage = "Get securityKey from ECS failed, Code : " + res .code () + "; Headers : " + header
89
122
+ "; Content : " + content ;
90
123
throw new IllegalArgumentException (errorMessage );
91
124
}
@@ -97,4 +130,84 @@ private static String getResourceWithDetail(String endpoint) throws IOException
97
130
}
98
131
}
99
132
}
133
+
134
+ private static String getMetadataApiToken (int metadataTokenTTLSeconds ) throws IOException {
135
+ Map <String , String > headers = new HashMap <>();
136
+ headers .put (METADATA_TOKEN_TTL , String .valueOf (metadataTokenTTLSeconds ));
137
+ ECSResult ecsResult =
138
+ executeEcsRequest (getEndpointForECSMetadataService () + METADATA_TOKEN_RESOURCE_PATH
139
+ , headers , HttpMethodEnum .PUT , "" , null );
140
+ if (ecsResult .code == 404 || ecsResult .code == 405 ) {
141
+ ILOG .debug (METADATA_TOKEN_HEADER_KEY + " not supported," + ecsResult );
142
+ return "" ;
143
+ } else if (!(ecsResult .code >= 200 && ecsResult .code < 300 )) {
144
+ String errorMessage = "Get " + METADATA_TOKEN_HEADER_KEY + " with " +
145
+ METADATA_TOKEN_TTL + ":" + metadataTokenTTLSeconds
146
+ + " from ECS failed," + ecsResult ;
147
+ ILOG .error (errorMessage );
148
+ throw new IllegalArgumentException (errorMessage );
149
+ } else {
150
+ ILOG .debug (METADATA_TOKEN_HEADER_KEY + " refreshed succeeded." );
151
+ return ecsResult .content ;
152
+ }
153
+ }
154
+
155
+ private static String getResourceWithDetailWithMetaDataToken (String endpoint , String metadataApiToken ) throws IOException {
156
+ Map <String , String > headers = new HashMap <>();
157
+ headers .put (METADATA_TOKEN_HEADER_KEY , metadataApiToken );
158
+ ECSResult ecsResult = executeEcsRequest (endpoint , headers , HttpMethodEnum .GET , "" , null );
159
+ // if not 2xx, throw exception
160
+ if (!(ecsResult .code >= 200 && ecsResult .code < 300 )) {
161
+ String errorMessage = "Get securityKey by " + METADATA_TOKEN_HEADER_KEY +
162
+ " from ECS failed," + ecsResult ;
163
+ ILOG .error (errorMessage );
164
+ throw new IllegalArgumentException (errorMessage );
165
+ }
166
+ ILOG .debug ("getResourceWithDetailWithMetaDataToken succeeded." );
167
+ return ecsResult .content ;
168
+ }
169
+
170
+ private static ECSResult executeEcsRequest (String url , Map <String , String > headers ,
171
+ HttpMethodEnum httpMethod , String body , MediaType contentType ) throws IOException , IllegalArgumentException {
172
+ Request .Builder builder = new Request .Builder ();
173
+ builder .header ("Accept" , "*/*" );
174
+ headers .forEach (builder ::header );
175
+ Request request ;
176
+ if (httpMethod == HttpMethodEnum .PUT ) {
177
+ request = builder .url (url ).put (RequestBody .create (body , contentType )).build ();
178
+ } else {
179
+ request = builder .url (url ).get ().build ();
180
+ }
181
+ Call c = httpClient .newCall (request );
182
+ try (Response res = c .execute ()) {
183
+ String header = "" ;
184
+ String content = "" ;
185
+ if (res .headers () != null ) {
186
+ header = res .headers ().toString ();
187
+ }
188
+ if (res .body () != null ) {
189
+ content = res .body ().string ();
190
+ }
191
+
192
+ return new ECSResult (res .code (), header , content );
193
+ }
194
+ }
195
+
196
+ private static class ECSResult {
197
+ public final int code ;
198
+ public final String header ;
199
+ public final String content ;
200
+
201
+ public ECSResult (int code , String header , String content ) {
202
+ this .code = code ;
203
+ this .header = header ;
204
+ this .content = content ;
205
+ }
206
+
207
+ @ Override
208
+ public String toString () {
209
+ return " Code : " + code + "; Headers : " + header
210
+ + "; Content : " + content ;
211
+ }
212
+ }
100
213
}
0 commit comments