Skip to content

Commit 152c851

Browse files
authored
fix: Handle 404 and non 200 Status Code from MDS Identity Token calls (#1636)
* fix: Handle 404 Status Code from MDS Identity Token calls * chore: Fix tests
1 parent 26785bf commit 152c851

File tree

4 files changed

+99
-25
lines changed

4 files changed

+99
-25
lines changed

oauth2_http/java/com/google/auth/oauth2/ComputeEngineCredentials.java

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@
8383
public class ComputeEngineCredentials extends GoogleCredentials
8484
implements ServiceAccountSigner, IdTokenProvider {
8585

86+
static final String METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE =
87+
"Empty content from metadata token server request.";
8688
// Decrease timing margins on GCE.
8789
// This is needed because GCE VMs maintain their own OAuth cache that expires T-4 mins, attempting
8890
// to refresh a token before then, will yield the same stale token. To enable pre-emptive
@@ -366,7 +368,7 @@ public AccessToken refreshAccessToken() throws IOException {
366368
if (content == null) {
367369
// Throw explicitly here on empty content to avoid NullPointerException from parseAs call.
368370
// Mock transports will have success code with empty content by default.
369-
throw new IOException("Empty content from metadata token server request.");
371+
throw new IOException(METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE);
370372
}
371373
GenericData responseData = response.parseAs(GenericData.class);
372374
String accessToken =
@@ -408,9 +410,24 @@ public IdToken idTokenWithAudience(String targetAudience, List<IdTokenProvider.O
408410
documentUrl.set("audience", targetAudience);
409411
HttpResponse response =
410412
getMetadataResponse(documentUrl.toString(), RequestType.ID_TOKEN_REQUEST, true);
413+
int statusCode = response.getStatusCode();
414+
if (statusCode == HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
415+
throw new IOException(
416+
String.format(
417+
"Error code %s trying to get identity token from"
418+
+ " Compute Engine metadata. This may be because the virtual machine instance"
419+
+ " does not have permission scopes specified.",
420+
statusCode));
421+
}
422+
if (statusCode != HttpStatusCodes.STATUS_CODE_OK) {
423+
throw new IOException(
424+
String.format(
425+
"Unexpected Error code %s trying to get identity token from Compute Engine metadata: %s",
426+
statusCode, response.parseAsString()));
427+
}
411428
InputStream content = response.getContent();
412429
if (content == null) {
413-
throw new IOException("Empty content from metadata token server request.");
430+
throw new IOException(METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE);
414431
}
415432
String rawToken = response.parseAsString();
416433
return IdToken.create(rawToken);
@@ -710,7 +727,7 @@ private String getDefaultServiceAccount() throws IOException {
710727
if (content == null) {
711728
// Throw explicitly here on empty content to avoid NullPointerException from parseAs call.
712729
// Mock transports will have success code with empty content by default.
713-
throw new IOException("Empty content from metadata token server request.");
730+
throw new IOException(METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE);
714731
}
715732
GenericData responseData = response.parseAs(GenericData.class);
716733
Map<String, Object> defaultAccount =

oauth2_http/javatests/com/google/auth/oauth2/ComputeEngineCredentialsTest.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,14 @@
3131

3232
package com.google.auth.oauth2;
3333

34+
import static com.google.auth.oauth2.ComputeEngineCredentials.METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE;
3435
import static org.junit.Assert.assertArrayEquals;
3536
import static org.junit.Assert.assertEquals;
3637
import static org.junit.Assert.assertFalse;
3738
import static org.junit.Assert.assertNotNull;
3839
import static org.junit.Assert.assertNull;
3940
import static org.junit.Assert.assertSame;
41+
import static org.junit.Assert.assertThrows;
4042
import static org.junit.Assert.assertTrue;
4143
import static org.junit.Assert.fail;
4244

@@ -420,7 +422,7 @@ public void getRequestMetadata_shouldInvalidateAccessTokenWhenScoped_newAccessTo
420422
@Test
421423
public void getRequestMetadata_missingServiceAccount_throws() {
422424
MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
423-
transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
425+
transportFactory.transport.setStatusCode(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
424426
ComputeEngineCredentials credentials =
425427
ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
426428
try {
@@ -437,7 +439,7 @@ public void getRequestMetadata_missingServiceAccount_throws() {
437439
@Test
438440
public void getRequestMetadata_serverError_throws() {
439441
MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
440-
transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR);
442+
transportFactory.transport.setStatusCode(HttpStatusCodes.STATUS_CODE_SERVER_ERROR);
441443
ComputeEngineCredentials credentials =
442444
ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
443445
try {
@@ -668,7 +670,7 @@ public void sign_getUniverseException() {
668670
ComputeEngineCredentials credentials =
669671
ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
670672

671-
transportFactory.transport.setRequestStatusCode(501);
673+
transportFactory.transport.setStatusCode(501);
672674
Assert.assertThrows(IOException.class, credentials::getUniverseDomain);
673675

674676
byte[] expectedSignature = {0xD, 0xE, 0xA, 0xD};
@@ -962,7 +964,7 @@ public void getUniverseDomain_fromMetadata_non404error_throws() throws IOExcepti
962964
continue;
963965
}
964966
try {
965-
transportFactory.transport.setRequestStatusCode(status);
967+
transportFactory.transport.setStatusCode(status);
966968
credentials.getUniverseDomain();
967969
fail("Should not be able to use credential without exception.");
968970
} catch (GoogleAuthException ex) {
@@ -1095,6 +1097,45 @@ public void idTokenWithAudience_license() throws IOException {
10951097
assertTrue(googleClaim.containsKey("license"));
10961098
}
10971099

1100+
@Test
1101+
public void idTokenWithAudience_404StatusCode() {
1102+
int statusCode = HttpStatusCodes.STATUS_CODE_NOT_FOUND;
1103+
MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
1104+
transportFactory.transport.setStatusCode(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
1105+
ComputeEngineCredentials credentials =
1106+
ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
1107+
IOException exception =
1108+
assertThrows(IOException.class, () -> credentials.idTokenWithAudience("Audience", null));
1109+
assertEquals(
1110+
String.format(
1111+
"Error code %s trying to get identity token from"
1112+
+ " Compute Engine metadata. This may be because the virtual machine instance"
1113+
+ " does not have permission scopes specified.",
1114+
statusCode),
1115+
exception.getMessage());
1116+
}
1117+
1118+
@Test
1119+
public void idTokenWithAudience_emptyContent() {
1120+
MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
1121+
transportFactory.transport.setEmptyContent(true);
1122+
ComputeEngineCredentials credentials =
1123+
ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
1124+
IOException exception =
1125+
assertThrows(IOException.class, () -> credentials.idTokenWithAudience("Audience", null));
1126+
assertEquals(METADATA_RESPONSE_EMPTY_CONTENT_ERROR_MESSAGE, exception.getMessage());
1127+
}
1128+
1129+
@Test
1130+
public void idTokenWithAudience_503StatusCode() {
1131+
MockMetadataServerTransportFactory transportFactory = new MockMetadataServerTransportFactory();
1132+
transportFactory.transport.setStatusCode(HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE);
1133+
ComputeEngineCredentials credentials =
1134+
ComputeEngineCredentials.newBuilder().setHttpTransportFactory(transportFactory).build();
1135+
assertThrows(
1136+
GoogleAuthException.class, () -> credentials.idTokenWithAudience("Audience", null));
1137+
}
1138+
10981139
static class MockMetadataServerTransportFactory implements HttpTransportFactory {
10991140

11001141
MockMetadataServerTransport transport =

oauth2_http/javatests/com/google/auth/oauth2/MockMetadataServerTransport.java

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131

3232
package com.google.auth.oauth2;
3333

34+
import com.google.api.client.http.HttpStatusCodes;
3435
import com.google.api.client.http.LowLevelHttpRequest;
3536
import com.google.api.client.http.LowLevelHttpResponse;
3637
import com.google.api.client.json.GenericJson;
@@ -55,7 +56,7 @@ public class MockMetadataServerTransport extends MockHttpTransport {
5556

5657
// key are scopes as in request url string following "?scopes="
5758
private Map<String, String> scopesToAccessToken;
58-
private Integer requestStatusCode;
59+
private Integer statusCode;
5960

6061
private String serviceAccountEmail;
6162

@@ -91,8 +92,8 @@ public void setAccessToken(String scopes, String accessToken) {
9192
scopesToAccessToken.put(scopes, accessToken);
9293
}
9394

94-
public void setRequestStatusCode(Integer requestStatusCode) {
95-
this.requestStatusCode = requestStatusCode;
95+
public void setStatusCode(Integer statusCode) {
96+
this.statusCode = statusCode;
9697
}
9798

9899
public void setServiceAccountEmail(String serviceAccountEmail) {
@@ -140,14 +141,15 @@ public LowLevelHttpRequest buildRequest(String method, String url) throws IOExce
140141
new MockLowLevelHttpRequest(url) {
141142
@Override
142143
public LowLevelHttpResponse execute() {
143-
if (requestStatusCode != null) {
144+
if (statusCode != null && (statusCode >= 400 && statusCode < 600)) {
144145
return new MockLowLevelHttpResponse()
145-
.setStatusCode(requestStatusCode)
146+
.setStatusCode(statusCode)
146147
.setContent("Metadata Error");
147148
}
148149

149150
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
150151
response.addHeader("Metadata-Flavor", "Google");
152+
response.setStatusCode(HttpStatusCodes.STATUS_CODE_OK);
151153
return response;
152154
}
153155
};
@@ -195,9 +197,9 @@ private MockLowLevelHttpRequest getMockRequestForTokenEndpoint(String url) {
195197
@Override
196198
public LowLevelHttpResponse execute() throws IOException {
197199

198-
if (requestStatusCode != null) {
200+
if (statusCode != null && (statusCode >= 400 && statusCode < 600)) {
199201
return new MockLowLevelHttpResponse()
200-
.setStatusCode(requestStatusCode)
202+
.setStatusCode(statusCode)
201203
.setContent("Token Fetch Error");
202204
}
203205

@@ -224,20 +226,35 @@ public LowLevelHttpResponse execute() throws IOException {
224226

225227
return new MockLowLevelHttpResponse()
226228
.setContentType(Json.MEDIA_TYPE)
229+
.setStatusCode(HttpStatusCodes.STATUS_CODE_OK)
227230
.setContent(refreshText);
228231
}
229232
};
230233
}
231234

232235
private MockLowLevelHttpRequest getMockRequestForIdentityDocument(String url)
233236
throws MalformedURLException, UnsupportedEncodingException {
234-
if (idToken != null) {
237+
if (statusCode != null && statusCode != HttpStatusCodes.STATUS_CODE_OK) {
235238
return new MockLowLevelHttpRequest(url) {
236239
@Override
237-
public LowLevelHttpResponse execute() throws IOException {
240+
public LowLevelHttpResponse execute() {
241+
return new MockLowLevelHttpResponse().setStatusCode(statusCode);
242+
}
243+
};
244+
} else if (idToken != null) {
245+
return new MockLowLevelHttpRequest(url) {
246+
@Override
247+
public LowLevelHttpResponse execute() {
238248
return new MockLowLevelHttpResponse().setContent(idToken);
239249
}
240250
};
251+
} else if (emptyContent) {
252+
return new MockLowLevelHttpRequest(url) {
253+
@Override
254+
public LowLevelHttpResponse execute() {
255+
return new MockLowLevelHttpResponse();
256+
}
257+
};
241258
}
242259

243260
// https://cloud.google.com/compute/docs/instances/verifying-instance-identity#token_format
@@ -299,15 +316,15 @@ public LowLevelHttpResponse execute() throws IOException {
299316
// Create the JSON response
300317
GenericJson content = new GenericJson();
301318
content.setFactory(OAuth2Utils.JSON_FACTORY);
302-
if (requestStatusCode == 200) {
319+
if (statusCode == HttpStatusCodes.STATUS_CODE_OK) {
303320
content.put(SecureSessionAgent.S2A_JSON_KEY, s2aContentMap);
304321
}
305322
String contentText = content.toPrettyString();
306323

307324
MockLowLevelHttpResponse response = new MockLowLevelHttpResponse();
308325

309-
if (requestStatusCode != null) {
310-
response.setStatusCode(requestStatusCode);
326+
if (statusCode != null) {
327+
response.setStatusCode(statusCode);
311328
}
312329
if (emptyContent == true) {
313330
return response.setZeroContent();

oauth2_http/javatests/com/google/auth/oauth2/SecureSessionAgentTest.java

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public void getS2AAddress_validAddress() {
5757
S2A_PLAINTEXT_ADDRESS,
5858
SecureSessionAgent.S2A_MTLS_ADDRESS_JSON_KEY,
5959
S2A_MTLS_ADDRESS));
60-
transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_OK);
60+
transportFactory.transport.setStatusCode(HttpStatusCodes.STATUS_CODE_OK);
6161

6262
SecureSessionAgent s2aUtils =
6363
SecureSessionAgent.newBuilder().setHttpTransportFactory(transportFactory).build();
@@ -77,8 +77,7 @@ public void getS2AAddress_queryEndpointResponseErrorCode_emptyAddress() {
7777
S2A_PLAINTEXT_ADDRESS,
7878
SecureSessionAgent.S2A_MTLS_ADDRESS_JSON_KEY,
7979
S2A_MTLS_ADDRESS));
80-
transportFactory.transport.setRequestStatusCode(
81-
HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE);
80+
transportFactory.transport.setStatusCode(HttpStatusCodes.STATUS_CODE_SERVICE_UNAVAILABLE);
8281

8382
SecureSessionAgent s2aUtils =
8483
SecureSessionAgent.newBuilder().setHttpTransportFactory(transportFactory).build();
@@ -98,7 +97,7 @@ public void getS2AAddress_queryEndpointResponseEmpty_emptyAddress() {
9897
S2A_PLAINTEXT_ADDRESS,
9998
SecureSessionAgent.S2A_MTLS_ADDRESS_JSON_KEY,
10099
S2A_MTLS_ADDRESS));
101-
transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_OK);
100+
transportFactory.transport.setStatusCode(HttpStatusCodes.STATUS_CODE_OK);
102101
transportFactory.transport.setEmptyContent(true);
103102

104103
SecureSessionAgent s2aUtils =
@@ -119,7 +118,7 @@ public void getS2AAddress_queryEndpointResponseInvalidPlaintextJsonKey_plaintext
119118
S2A_PLAINTEXT_ADDRESS,
120119
SecureSessionAgent.S2A_MTLS_ADDRESS_JSON_KEY,
121120
S2A_MTLS_ADDRESS));
122-
transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_OK);
121+
transportFactory.transport.setStatusCode(HttpStatusCodes.STATUS_CODE_OK);
123122

124123
SecureSessionAgent s2aUtils =
125124
SecureSessionAgent.newBuilder().setHttpTransportFactory(transportFactory).build();
@@ -139,7 +138,7 @@ public void getS2AAddress_queryEndpointResponseInvalidMtlsJsonKey_mtlsEmptyAddre
139138
S2A_PLAINTEXT_ADDRESS,
140139
INVALID_JSON_KEY,
141140
S2A_MTLS_ADDRESS));
142-
transportFactory.transport.setRequestStatusCode(HttpStatusCodes.STATUS_CODE_OK);
141+
transportFactory.transport.setStatusCode(HttpStatusCodes.STATUS_CODE_OK);
143142

144143
SecureSessionAgent s2aUtils =
145144
SecureSessionAgent.newBuilder().setHttpTransportFactory(transportFactory).build();

0 commit comments

Comments
 (0)