1515package com .obs .services .internal .security ;
1616
1717import java .io .IOException ;
18+ import java .util .HashMap ;
19+ import java .util .Map ;
1820import java .util .concurrent .TimeUnit ;
1921
22+ import com .obs .log .ILogger ;
23+ import com .obs .log .LoggerBuilder ;
24+ import com .obs .services .AbstractClient ;
2025import com .obs .services .internal .Constants ;
2126import com .obs .services .internal .utils .PropertyManager ;
27+ import com .obs .services .model .HttpMethodEnum ;
28+
2229import okhttp3 .Call ;
30+ import okhttp3 .MediaType ;
2331import okhttp3 .OkHttpClient ;
2432import okhttp3 .Request ;
33+ import okhttp3 .RequestBody ;
2534import okhttp3 .Response ;
2635
36+
2737public class EcsSecurityUtils {
2838 /**
2939 * Default root url for the openstack metadata apis.
3040 */
3141 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" ;
3246
3347 /**
3448 * Default endpoint for the ECS Instance Metadata Service.
@@ -46,13 +60,32 @@ public class EcsSecurityUtils {
4660 .writeTimeout (HTTP_CONNECT_TIMEOUT_VALUE , TimeUnit .MILLISECONDS )
4761 .readTimeout (HTTP_CONNECT_TIMEOUT_VALUE , TimeUnit .MILLISECONDS ).build ();
4862
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 );
4969 /**
5070 * Returns the temporary security credentials (access, secret,
5171 * securitytoken, and expires_at) associated with the IAM roles on the
5272 * instance.
5373 */
5474 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+ }
5689 }
5790
5891 /**
@@ -85,7 +118,7 @@ private static String getResourceWithDetail(String endpoint) throws IOException
85118 }
86119
87120 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
89122 + "; Content : " + content ;
90123 throw new IllegalArgumentException (errorMessage );
91124 }
@@ -97,4 +130,84 @@ private static String getResourceWithDetail(String endpoint) throws IOException
97130 }
98131 }
99132 }
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+ }
100213}
0 commit comments