diff --git a/jib-core/CHANGELOG.md b/jib-core/CHANGELOG.md
index 203a9a7d52..e06e3c9785 100644
--- a/jib-core/CHANGELOG.md
+++ b/jib-core/CHANGELOG.md
@@ -9,6 +9,8 @@ All notable changes to this project will be documented in this file.
### Fixed
+- `Containerizer#setAllowInsecureRegistries(boolean)` and the `sendCredentialsOverHttp` system property are now effective for authentication service server connections. ([#2074](https://github.com/GoogleContainerTools/jib/pull/2074)
+
## 0.12.0
### Added
diff --git a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPusherIntegrationTest.java b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPusherIntegrationTest.java
index 88e8e7f982..d7c0410b2c 100644
--- a/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPusherIntegrationTest.java
+++ b/jib-core/src/integration-test/java/com/google/cloud/tools/jib/registry/ManifestPusherIntegrationTest.java
@@ -16,7 +16,6 @@
package com.google.cloud.tools.jib.registry;
-import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.api.RegistryException;
@@ -24,6 +23,7 @@
import com.google.cloud.tools.jib.blob.Blobs;
import com.google.cloud.tools.jib.event.EventHandlers;
import com.google.cloud.tools.jib.hash.Digests;
+import com.google.cloud.tools.jib.http.ResponseException;
import com.google.cloud.tools.jib.image.json.ManifestTemplate;
import com.google.cloud.tools.jib.image.json.V22ManifestTemplate;
import java.io.IOException;
@@ -58,9 +58,9 @@ public void testPush_missingBlobs() throws IOException, RegistryException {
Assert.fail("Pushing manifest without its BLOBs should fail");
} catch (RegistryErrorException ex) {
- HttpResponseException httpResponseException = (HttpResponseException) ex.getCause();
+ ResponseException responseException = (ResponseException) ex.getCause();
Assert.assertEquals(
- HttpStatusCodes.STATUS_CODE_BAD_REQUEST, httpResponseException.getStatusCode());
+ HttpStatusCodes.STATUS_CODE_BAD_REQUEST, responseException.getStatusCode());
}
}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/InsecureRegistryException.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/InsecureRegistryException.java
index 82722ff557..588e38f3e9 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/InsecureRegistryException.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/InsecureRegistryException.java
@@ -23,10 +23,11 @@
*/
public class InsecureRegistryException extends RegistryException {
- public InsecureRegistryException(URL insecureUrl) {
+ public InsecureRegistryException(URL insecureUrl, Throwable cause) {
super(
"Failed to verify the server at "
+ insecureUrl
- + " because only secure connections are allowed.");
+ + " because only secure connections are allowed.",
+ cause);
}
}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/api/RegistryUnauthorizedException.java b/jib-core/src/main/java/com/google/cloud/tools/jib/api/RegistryUnauthorizedException.java
index 4a0132143e..602ec3e9fd 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/api/RegistryUnauthorizedException.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/api/RegistryUnauthorizedException.java
@@ -17,6 +17,7 @@
package com.google.cloud.tools.jib.api;
import com.google.api.client.http.HttpResponseException;
+import com.google.cloud.tools.jib.http.ResponseException;
/** Thrown when a registry request was unauthorized and therefore authentication is needed. */
public class RegistryUnauthorizedException extends RegistryException {
@@ -32,7 +33,7 @@ public class RegistryUnauthorizedException extends RegistryException {
* @param cause the cause
*/
public RegistryUnauthorizedException(
- String registry, String repository, HttpResponseException cause) {
+ String registry, String repository, ResponseException cause) {
super("Unauthorized for " + registry + "/" + repository, cause);
this.registry = registry;
this.repository = repository;
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/http/Connection.java b/jib-core/src/main/java/com/google/cloud/tools/jib/http/Connection.java
index 6e678cbe21..f3671a6188 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/http/Connection.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/http/Connection.java
@@ -17,45 +17,70 @@
package com.google.cloud.tools.jib.http;
import com.google.api.client.http.GenericUrl;
+import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpMethods;
import com.google.api.client.http.HttpRequest;
-import com.google.api.client.http.HttpRequestFactory;
-import com.google.api.client.http.HttpResponse;
+import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.apache.ApacheHttpTransport;
+import com.google.cloud.tools.jib.api.LogEvent;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Preconditions;
-import java.io.Closeable;
import java.io.IOException;
+import java.net.ConnectException;
import java.net.URL;
import java.security.GeneralSecurityException;
-import java.util.function.Function;
-import javax.annotation.Nullable;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import javax.net.ssl.SSLException;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.impl.client.DefaultHttpClient;
/**
- * Sends an HTTP {@link Request} and stores the {@link Response}. Clients should not send more than
- * one request.
+ * Thread-safe HTTP client that can automatically failover from secure HTTPS to insecure HTTPS or
+ * HTTP. Intended to be created once and shared to be called at multiple places. Callers should
+ * close the returned {@link Response}.
*
- *
Example usage:
+ *
The failover (if enabled) in the following way:
*
- *
{@code
- * try (Connection connection = new Connection(url)) {
- * Response response = connection.get(request);
- * // ... process the response
- * }
- * }
+ *
+ * - When a port is provided (for example {@code my-registry:5000/my-repo}):
+ *
+ * - Attempts secure HTTPS on the specified port.
+ *
- If (1) fails due to {@link SSLException}, re-attempts secure HTTPS on the specified
+ * port but disabling certificate validation.
+ *
- If (2) fails again due to {@link SSLException}, attempts plain-HTTP on the specified
+ * port.
+ *
+ * - When a port is not provided (for example {@code my-registry/my-repo}):
+ *
+ * - Attempts secure HTTPS on port 443 (default HTTPS port).
+ *
- If (1) fails due to {@link SSLException}, re-attempts secure HTTPS on port 443 but
+ * disabling certificate validation.
+ *
- If (2) fails again due to {@link SSLException}, attempts plain-HTTP on port 80
+ * (default HTTP port).
+ *
- Or, if (1) fails due to non-timeout {@link ConnectException}, attempts plain-HTTP on
+ * port 80.
+ *
+ *
+ *
+ * This failover behavior is similar to how the Docker client works:
+ * https://docs.docker.com/registry/insecure/#deploy-a-plain-http-registry
*/
-public class Connection implements Closeable {
+public class Connection { // TODO: rename to TlsFailoverHttpClient
- /**
- * Returns a factory for {@link Connection}.
- *
- * @return {@link Connection} factory, a function that generates a {@link Connection} to a URL
- */
- public static Function getConnectionFactory() {
+ private static boolean isHttpsProtocol(URL url) {
+ return "https".equals(url.getProtocol());
+ }
+
+ private static URL toHttp(URL url) {
+ GenericUrl httpUrl = new GenericUrl(url);
+ httpUrl.setScheme("http");
+ return httpUrl.toURL();
+ }
+
+ private static HttpTransport getSecureHttpTransport() {
// Do not use NetHttpTransport. It does not process response errors properly.
// See https://github.com/google/google-http-java-client/issues/39
//
@@ -63,22 +88,18 @@ public static Function getConnectionFactory() {
// connection persistence causes the connection to throw NoHttpResponseException.
ApacheHttpTransport transport = new ApacheHttpTransport();
addProxyCredentials(transport);
- return url -> new Connection(url, transport);
+ return transport;
}
- /**
- * Returns a factory for {@link Connection} that does not verify TLS peer verification.
- *
- * @throws GeneralSecurityException if unable to turn off TLS peer verification
- * @return {@link Connection} factory, a function that generates a {@link Connection} to a URL
- */
- public static Function getInsecureConnectionFactory()
- throws GeneralSecurityException {
- // Do not use NetHttpTransport. See comments in getConnectionFactory for details.
- ApacheHttpTransport transport =
- new ApacheHttpTransport.Builder().doNotValidateCertificate().build();
- addProxyCredentials(transport);
- return url -> new Connection(url, transport);
+ private static HttpTransport getInsecureHttpTransport() {
+ try {
+ ApacheHttpTransport insecureTransport =
+ new ApacheHttpTransport.Builder().doNotValidateCertificate().build();
+ addProxyCredentials(insecureTransport);
+ return insecureTransport;
+ } catch (GeneralSecurityException ex) {
+ throw new RuntimeException("platform does not support TLS protocol", ex);
+ }
}
/**
@@ -114,87 +135,157 @@ private static void addProxyCredentials(ApacheHttpTransport transport, String pr
new UsernamePasswordCredentials(proxyUser, proxyPassword));
}
- private HttpRequestFactory requestFactory;
-
- @Nullable private HttpResponse httpResponse;
-
- /** The URL to send the request to. */
- private final GenericUrl url;
+ private final boolean enableHttpAndInsecureFailover;
+ private final boolean sendAuthorizationOverHttp;
+ private final Consumer logger;
+ private final Supplier secureHttpTransportFactory;
+ private final Supplier insecureHttpTransportFactory;
- /**
- * Make sure to wrap with a try-with-resource to ensure that the connection is closed after usage.
- *
- * @param url the url to send the request to
- */
- @VisibleForTesting
- Connection(URL url, HttpTransport transport) {
- this.url = new GenericUrl(url);
- requestFactory = transport.createRequestFactory();
+ public Connection(
+ boolean enableHttpAndInsecureFailover,
+ boolean sendAuthorizationOverHttp,
+ Consumer logger) {
+ this(
+ enableHttpAndInsecureFailover,
+ sendAuthorizationOverHttp,
+ logger,
+ Connection::getSecureHttpTransport,
+ Connection::getInsecureHttpTransport);
}
- @Override
- public void close() throws IOException {
- if (httpResponse == null) {
- return;
- }
-
- httpResponse.disconnect();
+ @VisibleForTesting
+ Connection(
+ boolean enableHttpAndInsecureFailover,
+ boolean sendAuthorizationOverHttp,
+ Consumer logger,
+ Supplier secureHttpTransportFactory,
+ Supplier insecureHttpTransportFactory) {
+ this.enableHttpAndInsecureFailover = enableHttpAndInsecureFailover;
+ this.sendAuthorizationOverHttp = sendAuthorizationOverHttp;
+ this.logger = logger;
+ this.secureHttpTransportFactory = secureHttpTransportFactory;
+ this.insecureHttpTransportFactory = insecureHttpTransportFactory;
}
/**
* Sends the request with method GET.
*
+ * @param url endpoint URL
* @param request the request to send
* @return the response to the sent request
* @throws IOException if sending the request fails
*/
- public Response get(Request request) throws IOException {
- return send(HttpMethods.GET, request);
+ public Response get(URL url, Request request) throws IOException {
+ return call(HttpMethods.GET, url, request);
}
/**
* Sends the request with method POST.
*
+ * @param url endpoint URL
* @param request the request to send
* @return the response to the sent request
* @throws IOException if sending the request fails
*/
- public Response post(Request request) throws IOException {
- return send(HttpMethods.POST, request);
+ public Response post(URL url, Request request) throws IOException {
+ return call(HttpMethods.POST, url, request);
}
/**
* Sends the request with method PUT.
*
+ * @param url endpoint URL
* @param request the request to send
* @return the response to the sent request
* @throws IOException if sending the request fails
*/
- public Response put(Request request) throws IOException {
- return send(HttpMethods.PUT, request);
+ public Response put(URL url, Request request) throws IOException {
+ return call(HttpMethods.PUT, url, request);
}
/**
* Sends the request.
*
* @param httpMethod the HTTP request method
+ * @param url endpoint URL
* @param request the request to send
* @return the response to the sent request
* @throws IOException if building the HTTP request fails.
*/
- public Response send(String httpMethod, Request request) throws IOException {
- Preconditions.checkState(httpResponse == null, "Connection can send only one request");
+ public Response call(String httpMethod, URL url, Request request) throws IOException {
+ if (!isHttpsProtocol(url) && !enableHttpAndInsecureFailover) {
+ throw new SSLException("insecure HTTP connection not allowed: " + url);
+ }
+
+ try {
+ return call(httpMethod, url, request, secureHttpTransportFactory.get());
+
+ } catch (SSLException ex) {
+ if (!enableHttpAndInsecureFailover) {
+ throw ex;
+ }
+
+ try {
+ logInsecureHttpsFailover(url);
+ return call(httpMethod, url, request, insecureHttpTransportFactory.get());
+
+ } catch (SSLException ignored) { // This is usually when the server is plain-HTTP.
+ logHttpFailover(url);
+ return call(httpMethod, toHttp(url), request, secureHttpTransportFactory.get());
+ }
+
+ } catch (ConnectException ex) {
+ // It is observed that Open/Oracle JDKs sometimes throw SocketTimeoutException but other times
+ // ConnectException for connection timeout. (Could be a JDK bug.) Note SocketTimeoutException
+ // does not extend ConnectException (or vice versa), and we want to be consistent to error out
+ // on timeouts: https://github.com/GoogleContainerTools/jib/issues/1895#issuecomment-527544094
+ if (ex.getMessage() != null && ex.getMessage().contains("timed out")) {
+ throw ex;
+ }
+
+ // Fall back to HTTP only if "url" had no port specified (i.e., we tried the default HTTPS
+ // port 443) and we could not connect to 443. It's worth trying port 80.
+ if (enableHttpAndInsecureFailover && isHttpsProtocol(url) && url.getPort() == -1) {
+ logHttpFailover(url);
+ return call(httpMethod, toHttp(url), request, secureHttpTransportFactory.get());
+ }
+ throw ex;
+ }
+ }
+
+ private Response call(String httpMethod, URL url, Request request, HttpTransport httpTransport)
+ throws IOException {
+ boolean clearAuthorization = !isHttpsProtocol(url) && !sendAuthorizationOverHttp;
+
+ HttpHeaders requestHeaders =
+ clearAuthorization
+ ? request.getHeaders().clone().setAuthorization((String) null) // deep clone implemented
+ : request.getHeaders();
HttpRequest httpRequest =
- requestFactory
- .buildRequest(httpMethod, url, request.getHttpContent())
- .setHeaders(request.getHeaders());
+ httpTransport
+ .createRequestFactory()
+ .buildRequest(httpMethod, new GenericUrl(url), request.getHttpContent())
+ .setHeaders(requestHeaders);
if (request.getHttpTimeout() != null) {
httpRequest.setConnectTimeout(request.getHttpTimeout());
httpRequest.setReadTimeout(request.getHttpTimeout());
}
- httpResponse = httpRequest.execute();
- return new Response(httpResponse);
+ try {
+ return new Response(httpRequest.execute());
+ } catch (HttpResponseException ex) {
+ throw new ResponseException(ex, clearAuthorization);
+ }
+ }
+
+ private void logHttpFailover(URL url) {
+ String log = "Failed to connect to " + url + " over HTTPS. Attempting again with HTTP.";
+ logger.accept(LogEvent.info(log));
+ }
+
+ private void logInsecureHttpsFailover(URL url) {
+ String log = "Cannot verify server at " + url + ". Attempting again with no TLS verification.";
+ logger.accept(LogEvent.info(log));
}
}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/http/Response.java b/jib-core/src/main/java/com/google/cloud/tools/jib/http/Response.java
index 3c7f895586..e8bd74ee1d 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/http/Response.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/http/Response.java
@@ -19,12 +19,13 @@
import com.google.api.client.http.GenericUrl;
import com.google.api.client.http.HttpResponse;
import com.google.common.net.HttpHeaders;
+import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.List;
/** Holds an HTTP response. */
-public class Response {
+public class Response implements Closeable {
private final HttpResponse httpResponse;
@@ -75,4 +76,9 @@ public InputStream getBody() throws IOException {
public GenericUrl getRequestUrl() {
return httpResponse.getRequest().getUrl();
}
+
+ @Override
+ public void close() throws IOException {
+ httpResponse.disconnect();
+ }
}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/http/ResponseException.java b/jib-core/src/main/java/com/google/cloud/tools/jib/http/ResponseException.java
new file mode 100644
index 0000000000..d798e01ce0
--- /dev/null
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/http/ResponseException.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2019 Google LLC.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package com.google.cloud.tools.jib.http;
+
+import com.google.api.client.http.HttpHeaders;
+import com.google.api.client.http.HttpResponseException;
+import java.io.IOException;
+
+/** Holds an HTTP response exception. */
+public class ResponseException extends IOException {
+
+ private final HttpResponseException httpResponseException;
+ private final boolean requestAuthorizationCleared;
+
+ ResponseException(
+ HttpResponseException httpResponseException, boolean requestAuthorizationCleared) {
+ super(httpResponseException.getMessage(), httpResponseException);
+ this.httpResponseException = httpResponseException;
+ this.requestAuthorizationCleared = requestAuthorizationCleared;
+ }
+
+ public int getStatusCode() {
+ return httpResponseException.getStatusCode();
+ }
+
+ public String getContent() {
+ return httpResponseException.getContent();
+ }
+
+ public HttpHeaders getHeaders() {
+ return httpResponseException.getHeaders();
+ }
+
+ /**
+ * Returns whether the {@code Authorization} HTTP header was cleared (and thus not sent).
+ *
+ * @return whether the {@code Authorization} HTTP header was cleared
+ */
+ public boolean requestAuthorizationCleared() {
+ return requestAuthorizationCleared;
+ }
+}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetriever.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetriever.java
index eba5a28971..5176cec504 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetriever.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetriever.java
@@ -17,11 +17,12 @@
package com.google.cloud.tools.jib.registry;
import com.google.api.client.http.HttpMethods;
-import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.cloud.tools.jib.api.RegistryAuthenticationFailedException;
import com.google.cloud.tools.jib.http.BlobHttpContent;
+import com.google.cloud.tools.jib.http.Connection;
import com.google.cloud.tools.jib.http.Response;
+import com.google.cloud.tools.jib.http.ResponseException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
@@ -35,11 +36,15 @@ class AuthenticationMethodRetriever
private final RegistryEndpointRequestProperties registryEndpointRequestProperties;
private final String userAgent;
+ private final Connection httpClient;
AuthenticationMethodRetriever(
- RegistryEndpointRequestProperties registryEndpointRequestProperties, String userAgent) {
+ RegistryEndpointRequestProperties registryEndpointRequestProperties,
+ String userAgent,
+ Connection httpClient) {
this.registryEndpointRequestProperties = registryEndpointRequestProperties;
this.userAgent = userAgent;
+ this.httpClient = httpClient;
}
@Nullable
@@ -81,8 +86,7 @@ public String getActionDescription() {
@Override
public Optional handleHttpResponseException(
- HttpResponseException responseException)
- throws HttpResponseException, RegistryErrorException {
+ ResponseException responseException) throws ResponseException, RegistryErrorException {
// Only valid for status code of '401 Unauthorized'.
if (responseException.getStatusCode() != HttpStatusCodes.STATUS_CODE_UNAUTHORIZED) {
throw responseException;
@@ -99,7 +103,7 @@ public Optional handleHttpResponseException(
// Parses the header to retrieve the components.
try {
return RegistryAuthenticator.fromAuthenticationMethod(
- authenticationMethod, registryEndpointRequestProperties, userAgent);
+ authenticationMethod, registryEndpointRequestProperties, userAgent, httpClient);
} catch (RegistryAuthenticationFailedException ex) {
throw new RegistryErrorExceptionBuilder(getActionDescription(), ex)
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/BlobChecker.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/BlobChecker.java
index eb3b3bb1ce..ff4e4c2a4d 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/BlobChecker.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/BlobChecker.java
@@ -17,12 +17,12 @@
package com.google.cloud.tools.jib.registry;
import com.google.api.client.http.HttpMethods;
-import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.blob.BlobDescriptor;
import com.google.cloud.tools.jib.http.BlobHttpContent;
import com.google.cloud.tools.jib.http.Response;
+import com.google.cloud.tools.jib.http.ResponseException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
@@ -60,8 +60,8 @@ public Optional handleResponse(Response response) throws Registr
}
@Override
- public Optional handleHttpResponseException(
- HttpResponseException responseException) throws HttpResponseException {
+ public Optional handleHttpResponseException(ResponseException responseException)
+ throws ResponseException {
if (responseException.getStatusCode() != HttpStatusCodes.STATUS_CODE_NOT_FOUND) {
throw responseException;
}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/ErrorResponseUtil.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/ErrorResponseUtil.java
index 6548317f7a..afe63988a0 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/ErrorResponseUtil.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/ErrorResponseUtil.java
@@ -17,6 +17,7 @@
package com.google.cloud.tools.jib.registry;
import com.google.api.client.http.HttpResponseException;
+import com.google.cloud.tools.jib.http.ResponseException;
import com.google.cloud.tools.jib.json.JsonTemplateMapper;
import com.google.cloud.tools.jib.registry.json.ErrorEntryTemplate;
import com.google.cloud.tools.jib.registry.json.ErrorResponseTemplate;
@@ -32,11 +33,11 @@ public class ErrorResponseUtil {
*
* @param responseException the response exception
* @return the parsed {@link ErrorCodes} if found
- * @throws HttpResponseException rethrows the original exception if an error object could not be
+ * @throws ResponseException rethrows the original exception if an error object could not be
* parsed, if there were multiple error objects, or if the error code is unknown.
*/
- public static ErrorCodes getErrorCode(HttpResponseException responseException)
- throws HttpResponseException {
+ public static ErrorCodes getErrorCode(ResponseException responseException)
+ throws ResponseException {
// Obtain the error response code.
String errorContent = responseException.getContent();
if (errorContent == null) {
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/ManifestPusher.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/ManifestPusher.java
index e0797034b4..381fda1337 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/ManifestPusher.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/ManifestPusher.java
@@ -17,7 +17,6 @@
package com.google.cloud.tools.jib.registry;
import com.google.api.client.http.HttpMethods;
-import com.google.api.client.http.HttpResponseException;
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.api.LogEvent;
import com.google.cloud.tools.jib.blob.Blobs;
@@ -25,6 +24,7 @@
import com.google.cloud.tools.jib.hash.Digests;
import com.google.cloud.tools.jib.http.BlobHttpContent;
import com.google.cloud.tools.jib.http.Response;
+import com.google.cloud.tools.jib.http.ResponseException;
import com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;
import java.io.IOException;
import java.net.MalformedURLException;
@@ -92,8 +92,8 @@ public List getAccept() {
}
@Override
- public DescriptorDigest handleHttpResponseException(HttpResponseException responseException)
- throws HttpResponseException, RegistryErrorException {
+ public DescriptorDigest handleHttpResponseException(ResponseException responseException)
+ throws ResponseException, RegistryErrorException {
// docker registry 2.0 and 2.1 returns:
// 400 Bad Request
// {"errors":[{"code":"TAG_INVALID","message":"manifest tag did not match URI"}]}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java
index 05638595b7..8cd76441ab 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryAuthenticator.java
@@ -18,6 +18,7 @@
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.google.api.client.http.HttpMethods;
+import com.google.api.client.http.HttpStatusCodes;
import com.google.cloud.tools.jib.api.Credential;
import com.google.cloud.tools.jib.api.RegistryAuthenticationFailedException;
import com.google.cloud.tools.jib.blob.Blobs;
@@ -27,6 +28,7 @@
import com.google.cloud.tools.jib.http.Connection;
import com.google.cloud.tools.jib.http.Request;
import com.google.cloud.tools.jib.http.Response;
+import com.google.cloud.tools.jib.http.ResponseException;
import com.google.cloud.tools.jib.json.JsonTemplate;
import com.google.cloud.tools.jib.json.JsonTemplateMapper;
import com.google.common.annotations.VisibleForTesting;
@@ -58,6 +60,7 @@ public class RegistryAuthenticator {
* @param authenticationMethod the {@code WWW-Authenticate} header value
* @param registryEndpointRequestProperties the registry request properties
* @param userAgent the {@code User-Agent} header value to use in later authentication calls
+ * @param httpClient HTTP client
* @return a new {@link RegistryAuthenticator} for authenticating with the registry service
* @throws RegistryAuthenticationFailedException if authentication fails
* @see fromAuthenticationMethod(
String authenticationMethod,
RegistryEndpointRequestProperties registryEndpointRequestProperties,
- String userAgent)
+ String userAgent,
+ Connection httpClient)
throws RegistryAuthenticationFailedException {
// If the authentication method starts with 'basic ' (case insensitive), no registry
// authentication is needed.
@@ -96,7 +100,8 @@ static Optional fromAuthenticationMethod(
String service = serviceMatcher.find() ? serviceMatcher.group(1) : registryUrl;
return Optional.of(
- new RegistryAuthenticator(realm, service, registryEndpointRequestProperties, userAgent));
+ new RegistryAuthenticator(
+ realm, service, registryEndpointRequestProperties, userAgent, httpClient));
}
private static RegistryAuthenticationFailedException newRegistryAuthenticationFailedException(
@@ -138,16 +143,19 @@ private String getToken() {
private final String realm;
private final String service;
private final String userAgent;
+ private final Connection httpClient;
private RegistryAuthenticator(
String realm,
String service,
RegistryEndpointRequestProperties registryEndpointRequestProperties,
- String userAgent) {
+ String userAgent,
+ Connection httpClient) {
this.realm = realm;
this.service = service;
this.registryEndpointRequestProperties = registryEndpointRequestProperties;
this.userAgent = userAgent;
+ this.httpClient = httpClient;
}
/**
@@ -156,9 +164,11 @@ private RegistryAuthenticator(
* @param credential the credential used to authenticate
* @return an {@code Authorization} authenticating the pull
* @throws RegistryAuthenticationFailedException if authentication fails
+ * @throws RegistryCredentialsNotSentException if authentication is failed and credentials were
+ * not sent over plain HTTP
*/
public Authorization authenticatePull(@Nullable Credential credential)
- throws RegistryAuthenticationFailedException {
+ throws RegistryAuthenticationFailedException, RegistryCredentialsNotSentException {
return authenticate(credential, "pull");
}
@@ -168,9 +178,11 @@ public Authorization authenticatePull(@Nullable Credential credential)
* @param credential the credential used to authenticate
* @return an {@code Authorization} authenticating the push
* @throws RegistryAuthenticationFailedException if authentication fails
+ * @throws RegistryCredentialsNotSentException if authentication is failed and credentials were
+ * not sent over plain HTTP
*/
public Authorization authenticatePush(@Nullable Credential credential)
- throws RegistryAuthenticationFailedException {
+ throws RegistryAuthenticationFailedException, RegistryCredentialsNotSentException {
return authenticate(credential, "pull,push");
}
@@ -220,11 +232,13 @@ boolean isOAuth2Auth(@Nullable Credential credential) {
* @param scope the scope of permissions to authenticate for
* @return the {@link Authorization} response
* @throws RegistryAuthenticationFailedException if authentication fails
+ * @throws RegistryCredentialsNotSentException if authentication is failed and credentials were
+ * not sent over plain HTTP
* @see https://docs.docker.com/registry/spec/auth/token/#how-to-authenticate
*/
private Authorization authenticate(@Nullable Credential credential, String scope)
- throws RegistryAuthenticationFailedException {
+ throws RegistryAuthenticationFailedException, RegistryCredentialsNotSentException {
// try authorizing against both the main repository and the source repository too
// to enable cross-repository mounts on pushes
String sourceImageName = registryEndpointRequestProperties.getSourceImageName();
@@ -241,12 +255,12 @@ private Authorization authenticate(@Nullable Credential credential, String scope
private Authorization authenticate(
@Nullable Credential credential, Map repositoryScopes)
- throws RegistryAuthenticationFailedException {
+ throws RegistryAuthenticationFailedException, RegistryCredentialsNotSentException {
String registryUrl = registryEndpointRequestProperties.getServerUrl();
String imageName = registryEndpointRequestProperties.getImageName();
- try (Connection connection =
- Connection.getConnectionFactory()
- .apply(getAuthenticationUrl(credential, repositoryScopes))) {
+ try {
+ URL url = getAuthenticationUrl(credential, repositoryScopes);
+
Request.Builder requestBuilder =
Request.builder()
.setHttpTimeout(JibSystemProperties.getHttpTimeout())
@@ -262,21 +276,29 @@ private Authorization authenticate(
}
String httpMethod = isOAuth2Auth(credential) ? HttpMethods.POST : HttpMethods.GET;
- Response response = connection.send(httpMethod, requestBuilder.build());
+ try (Response response = httpClient.call(httpMethod, url, requestBuilder.build())) {
+
+ AuthenticationResponseTemplate responseJson =
+ JsonTemplateMapper.readJson(response.getBody(), AuthenticationResponseTemplate.class);
- AuthenticationResponseTemplate responseJson =
- JsonTemplateMapper.readJson(response.getBody(), AuthenticationResponseTemplate.class);
+ if (responseJson.getToken() == null) {
+ throw new RegistryAuthenticationFailedException(
+ registryUrl,
+ imageName,
+ "Did not get token in authentication response from "
+ + getAuthenticationUrl(credential, repositoryScopes)
+ + "; parameters: "
+ + getAuthRequestParameters(credential, repositoryScopes));
+ }
+ return Authorization.fromBearerToken(responseJson.getToken());
+ }
- if (responseJson.getToken() == null) {
- throw new RegistryAuthenticationFailedException(
- registryUrl,
- imageName,
- "Did not get token in authentication response from "
- + getAuthenticationUrl(credential, repositoryScopes)
- + "; parameters: "
- + getAuthRequestParameters(credential, repositoryScopes));
+ } catch (ResponseException ex) {
+ if (ex.getStatusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED
+ && ex.requestAuthorizationCleared()) {
+ throw new RegistryCredentialsNotSentException(registryUrl, imageName);
}
- return Authorization.fromBearerToken(responseJson.getToken());
+ throw new RegistryAuthenticationFailedException(registryUrl, imageName, ex);
} catch (IOException ex) {
throw new RegistryAuthenticationFailedException(registryUrl, imageName, ex);
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java
index 834d040c2a..1214f6aa3b 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryClient.java
@@ -28,6 +28,7 @@
import com.google.cloud.tools.jib.event.EventHandlers;
import com.google.cloud.tools.jib.global.JibSystemProperties;
import com.google.cloud.tools.jib.http.Authorization;
+import com.google.cloud.tools.jib.http.Connection;
import com.google.cloud.tools.jib.http.Response;
import com.google.cloud.tools.jib.image.json.BuildableManifestTemplate;
import com.google.cloud.tools.jib.image.json.ManifestTemplate;
@@ -242,8 +243,8 @@ static Multimap decodeTokenRepositoryGrants(String token) {
private final EventHandlers eventHandlers;
@Nullable private final Authorization authorization;
private final RegistryEndpointRequestProperties registryEndpointRequestProperties;
- private final boolean allowInsecureRegistries;
private final String userAgent;
+ private final Connection httpClient;
/**
* Instantiate with {@link #factory}.
@@ -252,6 +253,7 @@ static Multimap decodeTokenRepositoryGrants(String token) {
* @param authorization the {@link Authorization} to access the registry/repository
* @param registryEndpointRequestProperties properties of registry endpoint requests
* @param allowInsecureRegistries if {@code true}, insecure connections will be allowed
+ * @param userAgent {@code User-Agent} header to send with the request
*/
private RegistryClient(
EventHandlers eventHandlers,
@@ -262,8 +264,12 @@ private RegistryClient(
this.eventHandlers = eventHandlers;
this.authorization = authorization;
this.registryEndpointRequestProperties = registryEndpointRequestProperties;
- this.allowInsecureRegistries = allowInsecureRegistries;
this.userAgent = userAgent;
+ this.httpClient =
+ new Connection(
+ allowInsecureRegistries,
+ JibSystemProperties.sendCredentialsOverHttp(),
+ eventHandlers::dispatch);
}
/**
@@ -277,7 +283,8 @@ public Optional getRegistryAuthenticator()
// Gets the WWW-Authenticate header (eg. 'WWW-Authenticate: Bearer
// realm="https://gcr.io/v2/token",service="gcr.io"')
return callRegistryEndpoint(
- new AuthenticationMethodRetriever(registryEndpointRequestProperties, getUserAgent()));
+ new AuthenticationMethodRetriever(
+ registryEndpointRequestProperties, getUserAgent(), httpClient));
}
/**
@@ -465,7 +472,7 @@ private T callRegistryEndpoint(RegistryEndpointProvider registryEndpointP
registryEndpointProvider,
authorization,
registryEndpointRequestProperties,
- allowInsecureRegistries)
+ httpClient)
.call();
}
}
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointCaller.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointCaller.java
index c0a40be4ae..49cc444716 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointCaller.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointCaller.java
@@ -16,8 +16,6 @@
package com.google.cloud.tools.jib.registry;
-import com.google.api.client.http.GenericUrl;
-import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.cloud.tools.jib.api.InsecureRegistryException;
import com.google.cloud.tools.jib.api.LogEvent;
@@ -29,16 +27,14 @@
import com.google.cloud.tools.jib.http.Connection;
import com.google.cloud.tools.jib.http.Request;
import com.google.cloud.tools.jib.http.Response;
+import com.google.cloud.tools.jib.http.ResponseException;
import com.google.cloud.tools.jib.json.JsonTemplateMapper;
import com.google.cloud.tools.jib.registry.json.ErrorEntryTemplate;
import com.google.cloud.tools.jib.registry.json.ErrorResponseTemplate;
import com.google.common.annotations.VisibleForTesting;
import java.io.IOException;
-import java.net.ConnectException;
import java.net.URL;
-import java.security.GeneralSecurityException;
import java.util.Locale;
-import java.util.function.Function;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;
@@ -55,10 +51,6 @@ class RegistryEndpointCaller {
*/
@VisibleForTesting static final int STATUS_CODE_PERMANENT_REDIRECT = 308;
- private static boolean isHttpsProtocol(URL url) {
- return "https".equals(url.getProtocol());
- }
-
// https://github.com/GoogleContainerTools/jib/issues/1316
@VisibleForTesting
static boolean isBrokenPipe(IOException original) {
@@ -82,13 +74,7 @@ static boolean isBrokenPipe(IOException original) {
private final RegistryEndpointProvider registryEndpointProvider;
@Nullable private final Authorization authorization;
private final RegistryEndpointRequestProperties registryEndpointRequestProperties;
- private final boolean allowInsecureRegistries;
-
- /** Makes a {@link Connection} to the specified {@link URL}. */
- private final Function connectionFactory;
-
- /** Makes an insecure {@link Connection} to the specified {@link URL}. */
- @Nullable private Function insecureConnectionFactory;
+ private final Connection httpClient;
/**
* Constructs with parameters for making the request.
@@ -98,26 +84,8 @@ static boolean isBrokenPipe(IOException original) {
* @param registryEndpointProvider the {@link RegistryEndpointProvider} to the endpoint
* @param authorization optional authentication credentials to use
* @param registryEndpointRequestProperties properties of the registry endpoint request
- * @param allowInsecureRegistries if {@code true}, insecure connections will be allowed
+ * @param httpClient HTTP client
*/
- RegistryEndpointCaller(
- EventHandlers eventHandlers,
- String userAgent,
- RegistryEndpointProvider registryEndpointProvider,
- @Nullable Authorization authorization,
- RegistryEndpointRequestProperties registryEndpointRequestProperties,
- boolean allowInsecureRegistries) {
- this(
- eventHandlers,
- userAgent,
- registryEndpointProvider,
- authorization,
- registryEndpointRequestProperties,
- allowInsecureRegistries,
- Connection.getConnectionFactory(),
- null /* might never be used, so create lazily to delay throwing potential GeneralSecurityException */);
- }
-
@VisibleForTesting
RegistryEndpointCaller(
EventHandlers eventHandlers,
@@ -125,17 +93,13 @@ static boolean isBrokenPipe(IOException original) {
RegistryEndpointProvider registryEndpointProvider,
@Nullable Authorization authorization,
RegistryEndpointRequestProperties registryEndpointRequestProperties,
- boolean allowInsecureRegistries,
- Function connectionFactory,
- @Nullable Function insecureConnectionFactory) {
+ Connection httpClient) {
this.eventHandlers = eventHandlers;
this.userAgent = userAgent;
this.registryEndpointProvider = registryEndpointProvider;
this.authorization = authorization;
this.registryEndpointRequestProperties = registryEndpointRequestProperties;
- this.allowInsecureRegistries = allowInsecureRegistries;
- this.connectionFactory = connectionFactory;
- this.insecureConnectionFactory = insecureConnectionFactory;
+ this.httpClient = httpClient;
}
/**
@@ -148,73 +112,7 @@ static boolean isBrokenPipe(IOException original) {
T call() throws IOException, RegistryException {
String apiRouteBase = "https://" + registryEndpointRequestProperties.getServerUrl() + "/v2/";
URL initialRequestUrl = registryEndpointProvider.getApiRoute(apiRouteBase);
- return callWithAllowInsecureRegistryHandling(initialRequestUrl);
- }
-
- private T callWithAllowInsecureRegistryHandling(URL url) throws IOException, RegistryException {
- if (!isHttpsProtocol(url) && !allowInsecureRegistries) {
- throw new InsecureRegistryException(url);
- }
-
- try {
- return call(url, connectionFactory);
-
- } catch (SSLException ex) {
- return handleUnverifiableServerException(url);
-
- } catch (ConnectException ex) {
- // It is observed that Open/Oracle JDKs sometimes throw SocketTimeoutException but other times
- // ConnectException for connection timeout. (Could be a JDK bug.) Note SocketTimeoutException
- // does not extend ConnectException (or vice versa), and we want to be consistent to error out
- // on timeouts: https://github.com/GoogleContainerTools/jib/issues/1895#issuecomment-527544094
- if (ex.getMessage() != null && ex.getMessage().contains("timed out")) {
- throw ex;
- }
-
- if (allowInsecureRegistries && isHttpsProtocol(url) && url.getPort() == -1) {
- // Fall back to HTTP only if "url" had no port specified (i.e., we tried the default HTTPS
- // port 443) and we could not connect to 443. It's worth trying port 80.
- return fallBackToHttp(url);
- }
- throw ex;
- }
- }
-
- private T handleUnverifiableServerException(URL url) throws IOException, RegistryException {
- if (!allowInsecureRegistries) {
- throw new InsecureRegistryException(url);
- }
-
- try {
- eventHandlers.dispatch(
- LogEvent.info(
- "Cannot verify server at " + url + ". Attempting again with no TLS verification."));
- return call(url, getInsecureConnectionFactory());
-
- } catch (SSLException ex) {
- return fallBackToHttp(url);
- }
- }
-
- private T fallBackToHttp(URL url) throws IOException, RegistryException {
- GenericUrl httpUrl = new GenericUrl(url);
- httpUrl.setScheme("http");
- eventHandlers.dispatch(
- LogEvent.info(
- "Failed to connect to " + url + " over HTTPS. Attempting again with HTTP: " + httpUrl));
- return call(httpUrl.toURL(), connectionFactory);
- }
-
- private Function getInsecureConnectionFactory() throws RegistryException {
- try {
- if (insecureConnectionFactory == null) {
- insecureConnectionFactory = Connection.getInsecureConnectionFactory();
- }
- return insecureConnectionFactory;
-
- } catch (GeneralSecurityException ex) {
- throw new RegistryException("cannot turn off TLS peer verification", ex);
- }
+ return call(initialRequestUrl);
}
/**
@@ -225,34 +123,29 @@ private Function getInsecureConnectionFactory() throws Registry
* @throws IOException for most I/O exceptions when making the request
* @throws RegistryException for known exceptions when interacting with the registry
*/
- private T call(URL url, Function connectionFactory)
- throws IOException, RegistryException {
- // Only sends authorization if using HTTPS or explicitly forcing over HTTP.
- boolean sendCredentials = isHttpsProtocol(url) || JibSystemProperties.sendCredentialsOverHttp();
+ private T call(URL url) throws IOException, RegistryException {
String serverUrl = registryEndpointRequestProperties.getServerUrl();
String imageName = registryEndpointRequestProperties.getImageName();
- try (Connection connection = connectionFactory.apply(url)) {
- Request.Builder requestBuilder =
- Request.builder()
- .setUserAgent(userAgent)
- .setHttpTimeout(JibSystemProperties.getHttpTimeout())
- .setAccept(registryEndpointProvider.getAccept())
- .setBody(registryEndpointProvider.getContent());
- if (sendCredentials) {
- requestBuilder.setAuthorization(authorization);
- }
- Response response =
- connection.send(registryEndpointProvider.getHttpMethod(), requestBuilder.build());
+ Request.Builder requestBuilder =
+ Request.builder()
+ .setUserAgent(userAgent)
+ .setHttpTimeout(JibSystemProperties.getHttpTimeout())
+ .setAccept(registryEndpointProvider.getAccept())
+ .setBody(registryEndpointProvider.getContent())
+ .setAuthorization(authorization);
+
+ try (Response response =
+ httpClient.call(registryEndpointProvider.getHttpMethod(), url, requestBuilder.build())) {
return registryEndpointProvider.handleResponse(response);
- } catch (HttpResponseException ex) {
+ } catch (ResponseException ex) {
// First, see if the endpoint provider handles an exception as an expected response.
try {
return registryEndpointProvider.handleHttpResponseException(ex);
- } catch (HttpResponseException responseException) {
+ } catch (ResponseException responseException) {
if (responseException.getStatusCode() == HttpStatusCodes.STATUS_CODE_BAD_REQUEST
|| responseException.getStatusCode() == HttpStatusCodes.STATUS_CODE_NOT_FOUND
|| responseException.getStatusCode()
@@ -264,11 +157,11 @@ private T call(URL url, Function connectionFactory)
throw new RegistryUnauthorizedException(serverUrl, imageName, responseException);
} else if (responseException.getStatusCode() == HttpStatusCodes.STATUS_CODE_UNAUTHORIZED) {
- if (sendCredentials) {
+ if (responseException.requestAuthorizationCleared()) {
+ throw new RegistryCredentialsNotSentException(serverUrl, imageName);
+ } else {
// Credentials are either missing or wrong.
throw new RegistryUnauthorizedException(serverUrl, imageName, responseException);
- } else {
- throw new RegistryCredentialsNotSentException(serverUrl, imageName);
}
} else if (responseException.getStatusCode()
@@ -277,7 +170,7 @@ private T call(URL url, Function connectionFactory)
|| responseException.getStatusCode() == STATUS_CODE_PERMANENT_REDIRECT) {
// 'Location' header can be relative or absolute.
URL redirectLocation = new URL(url, responseException.getHeaders().getLocation());
- return callWithAllowInsecureRegistryHandling(redirectLocation);
+ return call(redirectLocation);
} else {
// Unknown
@@ -285,20 +178,20 @@ private T call(URL url, Function connectionFactory)
}
}
- } catch (SSLException ex) {
- logErrorIfBrokenPipe(ex);
- throw ex;
-
} catch (IOException ex) {
logError("I/O error for image [" + serverUrl + "/" + imageName + "]:");
logError(" " + ex.getMessage());
logErrorIfBrokenPipe(ex);
+
+ if (ex instanceof SSLException) {
+ throw new InsecureRegistryException(url, ex);
+ }
throw ex;
}
}
@VisibleForTesting
- RegistryErrorException newRegistryErrorException(HttpResponseException responseException) {
+ RegistryErrorException newRegistryErrorException(ResponseException responseException) {
RegistryErrorExceptionBuilder registryErrorExceptionBuilder =
new RegistryErrorExceptionBuilder(
registryEndpointProvider.getActionDescription(), responseException);
diff --git a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointProvider.java b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointProvider.java
index 0381352dc3..d6a2e7c502 100644
--- a/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointProvider.java
+++ b/jib-core/src/main/java/com/google/cloud/tools/jib/registry/RegistryEndpointProvider.java
@@ -20,6 +20,7 @@
import com.google.cloud.tools.jib.api.RegistryException;
import com.google.cloud.tools.jib.http.BlobHttpContent;
import com.google.cloud.tools.jib.http.Response;
+import com.google.cloud.tools.jib.http.ResponseException;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
@@ -53,15 +54,15 @@ interface RegistryEndpointProvider {
T handleResponse(Response response) throws IOException, RegistryException;
/**
- * Handles an {@link HttpResponseException} that occurs.
+ * Handles an {@link ResponseException} that occurs.
*
- * @param responseException the {@link HttpResponseException} to handle
+ * @param responseException the {@link ResponseException} to handle
* @throws HttpResponseException {@code responseException} if {@code responseException} could not
* be handled
* @throws RegistryErrorException if there is an error with a remote registry
*/
- default T handleHttpResponseException(HttpResponseException responseException)
- throws HttpResponseException, RegistryErrorException {
+ default T handleHttpResponseException(ResponseException responseException)
+ throws ResponseException, RegistryErrorException {
throw responseException;
}
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/http/ConnectionTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/http/ConnectionTest.java
index e68be69f92..6049f28bf8 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/http/ConnectionTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/http/ConnectionTest.java
@@ -22,73 +22,91 @@
import com.google.api.client.http.HttpRequest;
import com.google.api.client.http.HttpRequestFactory;
import com.google.api.client.http.HttpResponse;
+import com.google.api.client.http.HttpTransport;
+import com.google.cloud.tools.jib.api.LogEvent;
import com.google.cloud.tools.jib.blob.Blobs;
+import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.net.ConnectException;
+import java.net.MalformedURLException;
+import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.atomic.LongAdder;
+import java.util.function.Consumer;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
import org.junit.Assert;
+import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
-import org.mockito.InjectMocks;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
/** Tests for {@link Connection}. */
@RunWith(MockitoJUnitRunner.class)
-public class ConnectionTest {
+public class ConnectionTest { // TODO: rename to TlsFailoverHttpClient
@FunctionalInterface
- private interface SendFunction {
+ private interface CallFunction {
- Response send(Connection connection, Request request) throws IOException;
+ Response call(Connection httpClient, URL url, Request request) throws IOException;
}
+ @Mock private HttpTransport mockHttpTransport;
+ @Mock private HttpTransport mockInsecureHttpTransport;
@Mock private HttpRequestFactory mockHttpRequestFactory;
+ @Mock private HttpRequestFactory mockInsecureHttpRequestFactory;
@Mock private HttpRequest mockHttpRequest;
+ @Mock private HttpRequest mockInsecureHttpRequest;
+ @Mock private HttpResponse mockHttpResponse;
+ @Mock private Consumer logger;
- private final ArgumentCaptor httpHeadersArgumentCaptor =
- ArgumentCaptor.forClass(HttpHeaders.class);
- private final ArgumentCaptor blobHttpContentArgumentCaptor =
- ArgumentCaptor.forClass(BlobHttpContent.class);
+ @Captor private ArgumentCaptor httpHeadersCaptor;
+ @Captor private ArgumentCaptor blobHttpContentCaptor;
+ @Captor private ArgumentCaptor urlCaptor;
- private final GenericUrl fakeUrl = new GenericUrl("http://crepecake/fake/url");
+ private final GenericUrl fakeUrl = new GenericUrl("https://crepecake/fake/url");
private final LongAdder totalByteCount = new LongAdder();
- private Request fakeRequest;
- private HttpResponse mockHttpResponse;
+ @Before
+ public void setUp() throws IOException {
+ ByteArrayInputStream inStream = new ByteArrayInputStream(new byte[] {'b', 'o', 'd', 'y'});
+ Mockito.when(mockHttpResponse.getContent()).thenReturn(inStream);
+ }
- @InjectMocks
- private final Connection testConnection =
- Connection.getConnectionFactory().apply(fakeUrl.toURL());
+ private Connection newHttpClient(boolean insecure, boolean authOverHttp) throws IOException {
+ setUpMocks(mockHttpTransport, mockHttpRequestFactory, mockHttpRequest);
+ if (insecure) {
+ setUpMocks(
+ mockInsecureHttpTransport, mockInsecureHttpRequestFactory, mockInsecureHttpRequest);
+ }
+ return new Connection(
+ insecure, authOverHttp, logger, () -> mockHttpTransport, () -> mockInsecureHttpTransport);
+ }
@Test
public void testGet() throws IOException {
- setUpMocksAndFakes(null);
- testSend(HttpMethods.GET, Connection::get);
+ verifyCall(HttpMethods.GET, Connection::get);
}
@Test
public void testPost() throws IOException {
- setUpMocksAndFakes(null);
- testSend(HttpMethods.POST, Connection::post);
+ verifyCall(HttpMethods.POST, Connection::post);
}
@Test
public void testPut() throws IOException {
- setUpMocksAndFakes(null);
- testSend(HttpMethods.PUT, Connection::put);
+ verifyCall(HttpMethods.PUT, Connection::put);
}
@Test
public void testHttpTimeout_doNotSetByDefault() throws IOException {
- setUpMocksAndFakes(null);
- try (Connection connection = testConnection) {
- connection.send(HttpMethods.GET, fakeRequest);
- }
+ try (Response ignored = newHttpClient(false, false).get(fakeUrl.toURL(), fakeRequest(null))) {}
Mockito.verify(mockHttpRequest, Mockito.never()).setConnectTimeout(Mockito.anyInt());
Mockito.verify(mockHttpRequest, Mockito.never()).setReadTimeout(Mockito.anyInt());
@@ -96,66 +114,283 @@ public void testHttpTimeout_doNotSetByDefault() throws IOException {
@Test
public void testHttpTimeout() throws IOException {
- setUpMocksAndFakes(5982);
- try (Connection connection = testConnection) {
- connection.send(HttpMethods.GET, fakeRequest);
- }
+ Connection httpClient = newHttpClient(false, false);
+ try (Response ignored = httpClient.get(fakeUrl.toURL(), fakeRequest(5982))) {}
Mockito.verify(mockHttpRequest).setConnectTimeout(5982);
Mockito.verify(mockHttpRequest).setReadTimeout(5982);
}
- private void setUpMocksAndFakes(Integer httpTimeout) throws IOException {
- fakeRequest =
- Request.builder()
- .setAccept(Arrays.asList("fake.accept", "another.fake.accept"))
- .setUserAgent("fake user agent")
- .setBody(
- new BlobHttpContent(
- Blobs.from("crepecake"), "fake.content.type", totalByteCount::add))
- .setAuthorization(Authorization.fromBasicCredentials("fake-username", "fake-secret"))
- .setHttpTimeout(httpTimeout)
- .build();
+ private Request fakeRequest(Integer httpTimeout) {
+ return Request.builder()
+ .setAccept(Arrays.asList("fake.accept", "another.fake.accept"))
+ .setUserAgent("fake user agent")
+ .setBody(
+ new BlobHttpContent(Blobs.from("crepecake"), "fake.content.type", totalByteCount::add))
+ .setAuthorization(Authorization.fromBasicCredentials("fake-username", "fake-secret"))
+ .setHttpTimeout(httpTimeout)
+ .build();
+ }
+ private void setUpMocks(
+ HttpTransport mockHttpTransport,
+ HttpRequestFactory mockHttpRequestFactory,
+ HttpRequest mockHttpRequest)
+ throws IOException {
+ Mockito.when(mockHttpTransport.createRequestFactory()).thenReturn(mockHttpRequestFactory);
Mockito.when(
- mockHttpRequestFactory.buildRequest(
- Mockito.anyString(), Mockito.eq(fakeUrl), Mockito.any(BlobHttpContent.class)))
+ mockHttpRequestFactory.buildRequest(Mockito.any(), urlCaptor.capture(), Mockito.any()))
.thenReturn(mockHttpRequest);
- Mockito.when(mockHttpRequest.setHeaders(Mockito.any(HttpHeaders.class)))
+ Mockito.when(mockHttpRequest.setHeaders(httpHeadersCaptor.capture()))
.thenReturn(mockHttpRequest);
Mockito.when(mockHttpRequest.setConnectTimeout(Mockito.anyInt())).thenReturn(mockHttpRequest);
Mockito.when(mockHttpRequest.setReadTimeout(Mockito.anyInt())).thenReturn(mockHttpRequest);
- mockHttpResponse = Mockito.mock(HttpResponse.class);
Mockito.when(mockHttpRequest.execute()).thenReturn(mockHttpResponse);
}
- private void testSend(String httpMethod, SendFunction sendFunction) throws IOException {
- try (Connection connection = testConnection) {
- sendFunction.send(connection, fakeRequest);
- }
-
- Mockito.verify(mockHttpRequest).setHeaders(httpHeadersArgumentCaptor.capture());
- Mockito.verify(mockHttpResponse).disconnect();
+ private void verifyCall(String httpMethod, CallFunction callFunction) throws IOException {
+ Connection httpClient = newHttpClient(false, false);
+ try (Response ignored = callFunction.call(httpClient, fakeUrl.toURL(), fakeRequest(null))) {}
Assert.assertEquals(
- "fake.accept,another.fake.accept", httpHeadersArgumentCaptor.getValue().getAccept());
- Assert.assertEquals("fake user agent", httpHeadersArgumentCaptor.getValue().getUserAgent());
+ "fake.accept,another.fake.accept", httpHeadersCaptor.getValue().getAccept());
+ Assert.assertEquals("fake user agent", httpHeadersCaptor.getValue().getUserAgent());
// Base64 representation of "fake-username:fake-secret"
Assert.assertEquals(
"Basic ZmFrZS11c2VybmFtZTpmYWtlLXNlY3JldA==",
- httpHeadersArgumentCaptor.getValue().getAuthorization());
+ httpHeadersCaptor.getValue().getAuthorization());
Mockito.verify(mockHttpRequestFactory)
- .buildRequest(
- Mockito.eq(httpMethod), Mockito.eq(fakeUrl), blobHttpContentArgumentCaptor.capture());
- Assert.assertEquals("fake.content.type", blobHttpContentArgumentCaptor.getValue().getType());
+ .buildRequest(Mockito.eq(httpMethod), Mockito.eq(fakeUrl), blobHttpContentCaptor.capture());
+ Assert.assertEquals("fake.content.type", blobHttpContentCaptor.getValue().getType());
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
- blobHttpContentArgumentCaptor.getValue().writeTo(byteArrayOutputStream);
+ blobHttpContentCaptor.getValue().writeTo(byteArrayOutputStream);
- Assert.assertEquals(
- "crepecake", new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8));
+ Assert.assertEquals("crepecake", byteArrayOutputStream.toString(StandardCharsets.UTF_8.name()));
Assert.assertEquals("crepecake".length(), totalByteCount.longValue());
}
+
+ @Test
+ public void testGet_nonHttpsServer_insecureConnectionAndFailoverDisabled()
+ throws MalformedURLException, IOException {
+ Connection httpClient = newHttpClient(false, false);
+ try (Response response = httpClient.get(new URL("http://plain.http"), fakeRequest(null))) {
+ Assert.fail("Should disallow non-HTTP attempt");
+ } catch (SSLException ex) {
+ Assert.assertEquals(
+ "insecure HTTP connection not allowed: http://plain.http", ex.getMessage());
+ }
+ }
+
+ @Test
+ public void testCall_secureClientOnUnverifiableServer() throws IOException {
+ Connection httpClient = newHttpClient(false, false);
+
+ Mockito.when(mockHttpRequest.execute()).thenThrow(new SSLPeerUnverifiedException("unverified"));
+
+ try (Response response = httpClient.get(new URL("https://insecure"), fakeRequest(null))) {
+ Assert.fail("Secure caller should fail if cannot verify server");
+ } catch (SSLException ex) {
+ Assert.assertEquals("unverified", ex.getMessage());
+ Mockito.verifyNoInteractions(logger);
+ }
+ }
+
+ @Test
+ public void testGet_insecureClientOnUnverifiableServer() throws IOException {
+ Connection insecureHttpClient = newHttpClient(true, false);
+
+ Mockito.when(mockHttpRequest.execute()).thenThrow(new SSLPeerUnverifiedException(""));
+
+ try (Response response =
+ insecureHttpClient.get(new URL("https://insecure"), fakeRequest(null))) {
+ byte[] bytes = new byte[4];
+ Assert.assertEquals(4, response.getBody().read(bytes));
+ Assert.assertEquals("body", new String(bytes, StandardCharsets.UTF_8));
+ }
+
+ Assert.assertEquals(2, urlCaptor.getAllValues().size());
+ Assert.assertEquals(new GenericUrl("https://insecure"), urlCaptor.getAllValues().get(0));
+ Assert.assertEquals(new GenericUrl("https://insecure"), urlCaptor.getAllValues().get(1));
+
+ String log =
+ "Cannot verify server at https://insecure. Attempting again with no TLS verification.";
+ Mockito.verify(logger).accept(LogEvent.info(log));
+ Mockito.verifyNoMoreInteractions(logger);
+ }
+
+ @Test
+ public void testGet_insecureClientOnHttpServer() throws IOException {
+ Connection insecureHttpClient = newHttpClient(true, false);
+
+ Mockito.when(mockHttpRequest.execute())
+ .thenThrow(new SSLException("")) // server is not HTTPS
+ .thenReturn(mockHttpResponse);
+ Mockito.when(mockInsecureHttpRequest.execute())
+ .thenThrow(new SSLException("")); // server is not HTTPS
+
+ try (Response response =
+ insecureHttpClient.get(new URL("https://insecure"), fakeRequest(null))) {
+ byte[] bytes = new byte[4];
+ Assert.assertEquals(4, response.getBody().read(bytes));
+ Assert.assertEquals("body", new String(bytes, StandardCharsets.UTF_8));
+ }
+
+ Mockito.verify(mockHttpRequest, Mockito.times(2)).execute();
+ Mockito.verify(mockInsecureHttpRequest, Mockito.times(1)).execute();
+
+ Assert.assertEquals(3, urlCaptor.getAllValues().size());
+ Assert.assertEquals(new GenericUrl("https://insecure"), urlCaptor.getAllValues().get(0));
+ Assert.assertEquals(new GenericUrl("https://insecure"), urlCaptor.getAllValues().get(1));
+ Assert.assertEquals(new GenericUrl("http://insecure"), urlCaptor.getAllValues().get(2));
+
+ String log1 =
+ "Cannot verify server at https://insecure. Attempting again with no TLS verification.";
+ String log2 = "Failed to connect to https://insecure over HTTPS. Attempting again with HTTP.";
+ Mockito.verify(logger).accept(LogEvent.info(log1));
+ Mockito.verify(logger).accept(LogEvent.info(log2));
+ Mockito.verifyNoMoreInteractions(logger);
+ }
+
+ @Test
+ public void testGet_insecureClientOnHttpServerAndNoPortSpecified() throws IOException {
+ Connection insecureHttpClient = newHttpClient(true, false);
+
+ Mockito.when(mockHttpRequest.execute())
+ .thenThrow(new ConnectException()) // server is not listening on 443
+ .thenReturn(mockHttpResponse); // respond when connected through 80
+
+ try (Response response =
+ insecureHttpClient.get(new URL("https://insecure"), fakeRequest(null))) {
+ byte[] bytes = new byte[4];
+ Assert.assertEquals(4, response.getBody().read(bytes));
+ Assert.assertEquals("body", new String(bytes, StandardCharsets.UTF_8));
+ }
+
+ Mockito.verify(mockHttpRequest, Mockito.times(2)).execute();
+ Mockito.verifyNoInteractions(mockInsecureHttpRequest);
+
+ Assert.assertEquals(2, urlCaptor.getAllValues().size());
+ Assert.assertEquals(new GenericUrl("https://insecure"), urlCaptor.getAllValues().get(0));
+ Assert.assertEquals(new GenericUrl("http://insecure"), urlCaptor.getAllValues().get(1));
+
+ String log = "Failed to connect to https://insecure over HTTPS. Attempting again with HTTP.";
+ Mockito.verify(logger).accept(LogEvent.info(log));
+ Mockito.verifyNoMoreInteractions(logger);
+ }
+
+ @Test
+ public void testGet_secureClientOnNonListeningServerAndNoPortSpecified() throws IOException {
+ Connection httpClient = newHttpClient(false, false);
+
+ Mockito.when(mockHttpRequest.execute())
+ .thenThrow(new ConnectException("my exception")); // server not listening on 443
+
+ try (Response response = httpClient.get(new URL("https://insecure"), fakeRequest(null))) {
+ Assert.fail("Should not fall back to HTTP if port was explicitly given and cannot connect");
+ } catch (ConnectException ex) {
+ Assert.assertEquals("my exception", ex.getMessage());
+
+ Assert.assertEquals(1, urlCaptor.getAllValues().size());
+ Assert.assertEquals(new GenericUrl("https://insecure"), urlCaptor.getValue());
+
+ Mockito.verify(mockHttpRequest, Mockito.times(1)).execute();
+ Mockito.verifyNoInteractions(mockInsecureHttpRequest, logger);
+ }
+ }
+
+ @Test
+ public void testGet_insecureClientOnNonListeningServerAndPortSpecified() throws IOException {
+ Connection insecureHttpClient = newHttpClient(true, false);
+
+ Mockito.when(mockHttpRequest.execute())
+ .thenThrow(new ConnectException("my exception")); // server is not listening on 5000
+
+ try (Response response =
+ insecureHttpClient.get(new URL("https://insecure:5000"), fakeRequest(null))) {
+ Assert.fail("Should not fall back to HTTP if port was explicitly given and cannot connect");
+ } catch (ConnectException ex) {
+ Assert.assertEquals("my exception", ex.getMessage());
+
+ Assert.assertEquals(1, urlCaptor.getAllValues().size());
+ Assert.assertEquals(new GenericUrl("https://insecure:5000"), urlCaptor.getValue());
+
+ Mockito.verify(mockHttpRequest, Mockito.times(1)).execute();
+ Mockito.verifyNoInteractions(mockInsecureHttpRequest, logger);
+ }
+ }
+
+ @Test
+ public void testGet_timeoutFromConnectException() throws IOException {
+ Connection insecureHttpClient = newHttpClient(true, false);
+
+ Mockito.when(mockHttpRequest.execute()).thenThrow(new ConnectException("Connection timed out"));
+
+ try (Response response =
+ insecureHttpClient.get(new URL("https://insecure"), fakeRequest(null))) {
+ Assert.fail("Should not fall back to HTTP if timed out even for ConnectionException");
+ } catch (ConnectException ex) {
+ Assert.assertEquals("Connection timed out", ex.getMessage());
+
+ Assert.assertEquals(1, urlCaptor.getAllValues().size());
+ Assert.assertEquals(new GenericUrl("https://insecure"), urlCaptor.getValue());
+
+ Mockito.verify(mockHttpRequest, Mockito.times(1)).execute();
+ Mockito.verifyNoInteractions(mockInsecureHttpRequest, logger);
+ }
+ }
+
+ @Test
+ public void testGet_doNotSendCredentialsOverHttp() throws IOException {
+ Connection insecureHttpClient = newHttpClient(true, false);
+
+ // make it fall back to HTTP
+ Mockito.when(mockHttpRequest.execute())
+ .thenThrow(new ConnectException()) // server is not listening on 443
+ .thenReturn(mockHttpResponse); // respond when connected through 80
+
+ try (Response response =
+ insecureHttpClient.get(new URL("https://insecure"), fakeRequest(null))) {}
+
+ Assert.assertEquals(2, urlCaptor.getAllValues().size());
+ Assert.assertEquals(new GenericUrl("https://insecure"), urlCaptor.getAllValues().get(0));
+ Assert.assertEquals(new GenericUrl("http://insecure"), urlCaptor.getAllValues().get(1));
+
+ Assert.assertEquals(2, httpHeadersCaptor.getAllValues().size());
+ Assert.assertEquals(
+ "Basic ZmFrZS11c2VybmFtZTpmYWtlLXNlY3JldA==",
+ httpHeadersCaptor.getAllValues().get(0).getAuthorization());
+ Assert.assertNull(httpHeadersCaptor.getAllValues().get(1).getAuthorization());
+ }
+
+ @Test
+ public void testGet_sendCredentialsOverHttp() throws IOException {
+ Connection insecureHttpClient = newHttpClient(true, true); // sendCredentialsOverHttp
+
+ try (Response response =
+ insecureHttpClient.get(new URL("http://plain.http"), fakeRequest(null))) {}
+
+ Assert.assertEquals(1, urlCaptor.getAllValues().size());
+
+ Assert.assertEquals(
+ "Basic ZmFrZS11c2VybmFtZTpmYWtlLXNlY3JldA==",
+ httpHeadersCaptor.getValue().getAuthorization());
+ }
+
+ @Test
+ public void testGet_originalRequestHeaderUntouchedWhenClearingHeader() throws IOException {
+ Connection insecureHttpClient = newHttpClient(true, false);
+
+ Request request = fakeRequest(null);
+ try (Response response = insecureHttpClient.get(new URL("http://plain.http"), request)) {}
+
+ Assert.assertEquals(1, urlCaptor.getAllValues().size());
+ Assert.assertEquals(1, httpHeadersCaptor.getAllValues().size());
+
+ Assert.assertNull(httpHeadersCaptor.getValue().getAuthorization());
+ Assert.assertEquals(
+ "Basic ZmFrZS11c2VybmFtZTpmYWtlLXNlY3JldA==", request.getHeaders().getAuthorization());
+ }
}
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/http/ConnectionWithProxyCredentialsTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/http/ConnectionWithProxyCredentialsTest.java
index 5821f97911..385a05b21b 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/http/ConnectionWithProxyCredentialsTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/http/ConnectionWithProxyCredentialsTest.java
@@ -28,6 +28,7 @@
import org.junit.contrib.java.lang.system.RestoreSystemProperties;
/** Tests for {@link Connection} with setting proxy credentials. */
+// TODO: rename to TlsFailoverHttpClientProxyCredentialsTest
public class ConnectionWithProxyCredentialsTest {
private static final ImmutableList proxyProperties =
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/http/ResponseTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/http/ResponseTest.java
index 0cba195cd0..dd27e0bd7e 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/http/ResponseTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/http/ResponseTest.java
@@ -41,8 +41,8 @@ public void testGetContent() throws IOException {
Mockito.when(httpResponseMock.getContent()).thenReturn(responseInputStream);
- Response response = new Response(httpResponseMock);
-
- Assert.assertArrayEquals(expectedResponse, ByteStreams.toByteArray(response.getBody()));
+ try (Response response = new Response(httpResponseMock)) {
+ Assert.assertArrayEquals(expectedResponse, ByteStreams.toByteArray(response.getBody()));
+ }
}
}
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/http/WithServerConnectionTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/http/WithServerConnectionTest.java
index 6d843b2c7c..ed2cd1063d 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/http/WithServerConnectionTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/http/WithServerConnectionTest.java
@@ -16,6 +16,7 @@
package com.google.cloud.tools.jib.http;
+import com.google.cloud.tools.jib.api.LogEvent;
import com.google.common.io.ByteStreams;
import java.io.IOException;
import java.net.URISyntaxException;
@@ -23,76 +24,95 @@
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Arrays;
+import java.util.function.Consumer;
import javax.net.ssl.SSLException;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.contrib.java.lang.system.RestoreSystemProperties;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
/** Tests for {@link Connection} using an actual local server. */
-public class WithServerConnectionTest {
+@RunWith(MockitoJUnitRunner.class)
+public class WithServerConnectionTest { // TODO: rename to WithServerTlsFailoverHttpClientTest
@Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();
+ @Mock private Consumer logger;
+
+ private final Request request = new Request.Builder().build();
+
@Test
public void testGet()
throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {
+ Connection insecureHttpClient = new Connection(true /*insecure*/, false, logger);
try (TestWebServer server = new TestWebServer(false);
- Connection connection =
- Connection.getConnectionFactory().apply(new URL(server.getEndpoint()))) {
- Response response = connection.send("GET", new Request.Builder().build());
+ Response response = insecureHttpClient.get(new URL(server.getEndpoint()), request)) {
Assert.assertEquals(200, response.getStatusCode());
Assert.assertArrayEquals(
"Hello World!".getBytes(StandardCharsets.UTF_8),
ByteStreams.toByteArray(response.getBody()));
- }
- }
-
- @Test
- public void testErrorOnSecondSend()
- throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {
- try (TestWebServer server = new TestWebServer(false);
- Connection connection =
- Connection.getConnectionFactory().apply(new URL(server.getEndpoint()))) {
- connection.send("GET", new Request.Builder().build());
- try {
- connection.send("GET", new Request.Builder().build());
- Assert.fail("Should fail on the second send");
- } catch (IllegalStateException ex) {
- Assert.assertEquals("Connection can send only one request", ex.getMessage());
- }
+ Mockito.verifyNoInteractions(logger);
}
}
@Test
public void testSecureConnectionOnInsecureHttpsServer()
throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {
+ Connection secureHttpClient = new Connection(false /*secure*/, false, logger);
try (TestWebServer server = new TestWebServer(true);
- Connection connection =
- Connection.getConnectionFactory().apply(new URL(server.getEndpoint()))) {
- try {
- connection.send("GET", new Request.Builder().build());
- Assert.fail("Should fail if cannot verify peer");
- } catch (SSLException ex) {
- Assert.assertNotNull(ex.getMessage());
- }
+ Response ignored = secureHttpClient.get(new URL(server.getEndpoint()), request)) {
+ Assert.fail("Should fail if cannot verify peer");
+
+ } catch (SSLException ex) {
+ Assert.assertNotNull(ex.getMessage());
}
}
@Test
- public void testInsecureConnection()
+ public void testInsecureConnection_insecureHttpsFailover()
throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {
- try (TestWebServer server = new TestWebServer(true);
- Connection connection =
- Connection.getInsecureConnectionFactory().apply(new URL(server.getEndpoint()))) {
- Response response = connection.send("GET", new Request.Builder().build());
+ Connection insecureHttpClient = new Connection(true /*insecure*/, false, logger);
+ try (TestWebServer server = new TestWebServer(true, 2);
+ Response response = insecureHttpClient.get(new URL(server.getEndpoint()), request)) {
Assert.assertEquals(200, response.getStatusCode());
Assert.assertArrayEquals(
"Hello World!".getBytes(StandardCharsets.UTF_8),
ByteStreams.toByteArray(response.getBody()));
+
+ String endpoint = server.getEndpoint();
+ String expectedLog =
+ "Cannot verify server at " + endpoint + ". Attempting again with no TLS verification.";
+ Mockito.verify(logger).accept(LogEvent.info(expectedLog));
+ }
+ }
+
+ @Test
+ public void testInsecureConnection_plainHttpFailover()
+ throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {
+ Connection insecureHttpClient = new Connection(true /*insecure*/, false, logger);
+ try (TestWebServer server = new TestWebServer(false, 3)) {
+ String httpsUrl = server.getEndpoint().replace("http://", "https://");
+ try (Response response = insecureHttpClient.get(new URL(httpsUrl), request)) {
+
+ Assert.assertEquals(200, response.getStatusCode());
+ Assert.assertArrayEquals(
+ "Hello World!".getBytes(StandardCharsets.UTF_8),
+ ByteStreams.toByteArray(response.getBody()));
+
+ String expectedLog1 =
+ "Cannot verify server at " + httpsUrl + ". Attempting again with no TLS verification.";
+ String expectedLog2 =
+ "Failed to connect to " + httpsUrl + " over HTTPS. Attempting again with HTTP.";
+ Mockito.verify(logger).accept(LogEvent.info(expectedLog1));
+ Mockito.verify(logger).accept(LogEvent.info(expectedLog2));
+ }
}
}
@@ -107,6 +127,7 @@ public void testProxyCredentialProperties()
+ "Content-Length: 0\n\n";
String targetServerResponse = "HTTP/1.1 200 OK\nContent-Length:12\n\nHello World!";
+ Connection httpClient = new Connection(true /*insecure*/, false, logger);
try (TestWebServer server =
new TestWebServer(false, Arrays.asList(proxyResponse, targetServerResponse), 1)) {
System.setProperty("http.proxyHost", "localhost");
@@ -114,9 +135,7 @@ public void testProxyCredentialProperties()
System.setProperty("http.proxyUser", "user_sys_prop");
System.setProperty("http.proxyPassword", "pass_sys_prop");
- try (Connection connection =
- Connection.getConnectionFactory().apply(new URL("http://does.not.matter"))) {
- Response response = connection.send("GET", new Request.Builder().build());
+ try (Response response = httpClient.get(new URL("http://does.not.matter"), request)) {
Assert.assertThat(
server.getInputRead(),
CoreMatchers.containsString(
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverTest.java
index 0a5bde73fb..9e0ccfc0d6 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/AuthenticationMethodRetrieverTest.java
@@ -18,9 +18,10 @@
import com.google.api.client.http.HttpHeaders;
import com.google.api.client.http.HttpMethods;
-import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
+import com.google.cloud.tools.jib.http.Connection;
import com.google.cloud.tools.jib.http.Response;
+import com.google.cloud.tools.jib.http.ResponseException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
@@ -36,13 +37,15 @@
@RunWith(MockitoJUnitRunner.class)
public class AuthenticationMethodRetrieverTest {
- @Mock private HttpResponseException mockResponseException;
+ @Mock private ResponseException mockResponseException;
@Mock private HttpHeaders mockHeaders;
+ @Mock private Connection httpClient;
private final RegistryEndpointRequestProperties fakeRegistryEndpointRequestProperties =
new RegistryEndpointRequestProperties("someServerUrl", "someImageName");
private final AuthenticationMethodRetriever testAuthenticationMethodRetriever =
- new AuthenticationMethodRetriever(fakeRegistryEndpointRequestProperties, "user-agent");
+ new AuthenticationMethodRetriever(
+ fakeRegistryEndpointRequestProperties, "user-agent", httpClient);
@Test
public void testGetContent() {
@@ -88,13 +91,13 @@ public void testHandleHttpResponseException_invalidStatusCode() throws RegistryE
Assert.fail(
"Authentication method retriever should only handle HTTP 401 Unauthorized errors");
- } catch (HttpResponseException ex) {
+ } catch (ResponseException ex) {
Assert.assertEquals(mockResponseException, ex);
}
}
@Test
- public void tsetHandleHttpResponseException_noHeader() throws HttpResponseException {
+ public void tsetHandleHttpResponseException_noHeader() throws ResponseException {
Mockito.when(mockResponseException.getStatusCode())
.thenReturn(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED);
Mockito.when(mockResponseException.getHeaders()).thenReturn(mockHeaders);
@@ -112,8 +115,7 @@ public void tsetHandleHttpResponseException_noHeader() throws HttpResponseExcept
}
@Test
- public void testHandleHttpResponseException_badAuthenticationMethod()
- throws HttpResponseException {
+ public void testHandleHttpResponseException_badAuthenticationMethod() throws ResponseException {
String authenticationMethod = "bad authentication method";
Mockito.when(mockResponseException.getStatusCode())
@@ -136,7 +138,7 @@ public void testHandleHttpResponseException_badAuthenticationMethod()
@Test
public void testHandleHttpResponseException_pass()
- throws RegistryErrorException, HttpResponseException, MalformedURLException {
+ throws RegistryErrorException, ResponseException, MalformedURLException {
String authenticationMethod =
"Bearer realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"";
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/BlobCheckerTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/BlobCheckerTest.java
index 0e364e79e7..b25f8a5078 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/BlobCheckerTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/BlobCheckerTest.java
@@ -16,11 +16,11 @@
package com.google.cloud.tools.jib.registry;
-import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.blob.BlobDescriptor;
import com.google.cloud.tools.jib.http.Response;
+import com.google.cloud.tools.jib.http.ResponseException;
import com.google.cloud.tools.jib.json.JsonTemplateMapper;
import com.google.cloud.tools.jib.registry.json.ErrorEntryTemplate;
import com.google.cloud.tools.jib.registry.json.ErrorResponseTemplate;
@@ -83,7 +83,7 @@ public void testHandleResponse_noContentLength() {
@Test
public void testHandleHttpResponseException() throws IOException {
- HttpResponseException mockResponseException = Mockito.mock(HttpResponseException.class);
+ ResponseException mockResponseException = Mockito.mock(ResponseException.class);
Mockito.when(mockResponseException.getStatusCode())
.thenReturn(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
@@ -99,7 +99,7 @@ public void testHandleHttpResponseException() throws IOException {
@Test
public void testHandleHttpResponseException_hasOtherErrors() throws IOException {
- HttpResponseException mockResponseException = Mockito.mock(HttpResponseException.class);
+ ResponseException mockResponseException = Mockito.mock(ResponseException.class);
Mockito.when(mockResponseException.getStatusCode())
.thenReturn(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
@@ -114,14 +114,14 @@ public void testHandleHttpResponseException_hasOtherErrors() throws IOException
testBlobChecker.handleHttpResponseException(mockResponseException);
Assert.fail("Non-BLOB_UNKNOWN errors should not be handled");
- } catch (HttpResponseException ex) {
+ } catch (ResponseException ex) {
Assert.assertEquals(mockResponseException, ex);
}
}
@Test
public void testHandleHttpResponseException_notBlobUnknown() throws IOException {
- HttpResponseException mockResponseException = Mockito.mock(HttpResponseException.class);
+ ResponseException mockResponseException = Mockito.mock(ResponseException.class);
Mockito.when(mockResponseException.getStatusCode())
.thenReturn(HttpStatusCodes.STATUS_CODE_NOT_FOUND);
@@ -133,21 +133,21 @@ public void testHandleHttpResponseException_notBlobUnknown() throws IOException
testBlobChecker.handleHttpResponseException(mockResponseException);
Assert.fail("Non-BLOB_UNKNOWN errors should not be handled");
- } catch (HttpResponseException ex) {
+ } catch (ResponseException ex) {
Assert.assertEquals(mockResponseException, ex);
}
}
@Test
public void testHandleHttpResponseException_invalidStatusCode() {
- HttpResponseException mockResponseException = Mockito.mock(HttpResponseException.class);
+ ResponseException mockResponseException = Mockito.mock(ResponseException.class);
Mockito.when(mockResponseException.getStatusCode()).thenReturn(-1);
try {
testBlobChecker.handleHttpResponseException(mockResponseException);
Assert.fail("Non-404 status codes should not be handled");
- } catch (HttpResponseException ex) {
+ } catch (ResponseException ex) {
Assert.assertEquals(mockResponseException, ex);
}
}
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ErrorResponseUtilTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ErrorResponseUtilTest.java
index 5c8981338a..f440e61a83 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ErrorResponseUtilTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ErrorResponseUtilTest.java
@@ -16,7 +16,7 @@
package com.google.cloud.tools.jib.registry;
-import com.google.api.client.http.HttpResponseException;
+import com.google.cloud.tools.jib.http.ResponseException;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -28,10 +28,10 @@
@RunWith(MockitoJUnitRunner.class)
public class ErrorResponseUtilTest {
- @Mock HttpResponseException responseException;
+ @Mock private ResponseException responseException;
@Test
- public void testGetErrorCode_knownErrorCode() throws HttpResponseException {
+ public void testGetErrorCode_knownErrorCode() throws ResponseException {
Mockito.when(responseException.getContent())
.thenReturn(
"{\"errors\":[{\"code\":\"MANIFEST_INVALID\",\"message\":\"manifest invalid\",\"detail\":{}}]}");
@@ -49,7 +49,7 @@ public void testGetErrorCode_unknownErrorCode() {
try {
ErrorResponseUtil.getErrorCode(responseException);
Assert.fail();
- } catch (HttpResponseException ex) {
+ } catch (ResponseException ex) {
Assert.assertSame(responseException, ex);
}
}
@@ -66,7 +66,7 @@ public void testGetErrorCode_multipleErrors() {
try {
ErrorResponseUtil.getErrorCode(responseException);
Assert.fail();
- } catch (HttpResponseException ex) {
+ } catch (ResponseException ex) {
Assert.assertSame(responseException, ex);
}
}
@@ -79,7 +79,7 @@ public void testGetErrorCode_invalidErrorObject() {
try {
ErrorResponseUtil.getErrorCode(responseException);
Assert.fail();
- } catch (HttpResponseException ex) {
+ } catch (ResponseException ex) {
Assert.assertSame(responseException, ex);
}
}
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ManifestPusherTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ManifestPusherTest.java
index 87ca1cdb2c..e9ff217e7c 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ManifestPusherTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/ManifestPusherTest.java
@@ -16,13 +16,13 @@
package com.google.cloud.tools.jib.registry;
-import com.google.api.client.http.HttpResponseException;
import com.google.cloud.tools.jib.api.DescriptorDigest;
import com.google.cloud.tools.jib.api.LogEvent;
import com.google.cloud.tools.jib.event.EventHandlers;
import com.google.cloud.tools.jib.hash.Digests;
import com.google.cloud.tools.jib.http.BlobHttpContent;
import com.google.cloud.tools.jib.http.Response;
+import com.google.cloud.tools.jib.http.ResponseException;
import com.google.cloud.tools.jib.image.json.V22ManifestTemplate;
import com.google.cloud.tools.jib.json.JsonTemplateMapper;
import com.google.common.io.Resources;
@@ -156,9 +156,8 @@ public void testGetAccept() {
/** Docker Registry 2.0 and 2.1 return 400 / TAG_INVALID. */
@Test
- public void testHandleHttpResponseException_dockerRegistry_tagInvalid()
- throws HttpResponseException {
- HttpResponseException exception = Mockito.mock(HttpResponseException.class);
+ public void testHandleHttpResponseException_dockerRegistry_tagInvalid() throws ResponseException {
+ ResponseException exception = Mockito.mock(ResponseException.class);
Mockito.when(exception.getStatusCode()).thenReturn(HttpStatus.SC_BAD_REQUEST);
Mockito.when(exception.getContent())
.thenReturn(
@@ -180,8 +179,8 @@ public void testHandleHttpResponseException_dockerRegistry_tagInvalid()
/** Docker Registry 2.2 returns a 400 / MANIFEST_INVALID. */
@Test
public void testHandleHttpResponseException_dockerRegistry_manifestInvalid()
- throws HttpResponseException {
- HttpResponseException exception = Mockito.mock(HttpResponseException.class);
+ throws ResponseException {
+ ResponseException exception = Mockito.mock(ResponseException.class);
Mockito.when(exception.getStatusCode()).thenReturn(HttpStatus.SC_BAD_REQUEST);
Mockito.when(exception.getContent())
.thenReturn(
@@ -202,8 +201,8 @@ public void testHandleHttpResponseException_dockerRegistry_manifestInvalid()
/** Quay.io returns an undocumented 415 / MANIFEST_INVALID. */
@Test
- public void testHandleHttpResponseException_quayIo() throws HttpResponseException {
- HttpResponseException exception = Mockito.mock(HttpResponseException.class);
+ public void testHandleHttpResponseException_quayIo() throws ResponseException {
+ ResponseException exception = Mockito.mock(ResponseException.class);
Mockito.when(exception.getStatusCode()).thenReturn(HttpStatus.SC_UNSUPPORTED_MEDIA_TYPE);
Mockito.when(exception.getContent())
.thenReturn(
@@ -225,13 +224,13 @@ public void testHandleHttpResponseException_quayIo() throws HttpResponseExceptio
@Test
public void testHandleHttpResponseException_otherError() throws RegistryErrorException {
- HttpResponseException exception = Mockito.mock(HttpResponseException.class);
+ ResponseException exception = Mockito.mock(ResponseException.class);
Mockito.when(exception.getStatusCode()).thenReturn(HttpStatus.SC_UNAUTHORIZED);
try {
testManifestPusher.handleHttpResponseException(exception);
Assert.fail();
- } catch (HttpResponseException ex) {
+ } catch (ResponseException ex) {
Assert.assertSame(exception, ex);
}
}
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java
index 74fab1eb8c..268dda3475 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryAuthenticatorTest.java
@@ -18,33 +18,57 @@
import com.google.cloud.tools.jib.api.Credential;
import com.google.cloud.tools.jib.api.RegistryAuthenticationFailedException;
+import com.google.cloud.tools.jib.http.Connection;
+import com.google.cloud.tools.jib.http.Response;
+import com.google.cloud.tools.jib.http.ResponseException;
import com.google.cloud.tools.jib.http.TestWebServer;
+import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
+import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.util.Collections;
import org.hamcrest.CoreMatchers;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.junit.MockitoJUnitRunner;
/** Tests for {@link RegistryAuthenticator}. */
+@RunWith(MockitoJUnitRunner.class)
public class RegistryAuthenticatorTest {
private final RegistryEndpointRequestProperties registryEndpointRequestProperties =
new RegistryEndpointRequestProperties("someserver", "someimage");
+ @Mock private Connection httpClient;
+ @Mock private Response response;
+
+ @Captor private ArgumentCaptor urlCaptor;
+
private RegistryAuthenticator registryAuthenticator;
@Before
- public void setUp() throws RegistryAuthenticationFailedException {
+ public void setUp() throws RegistryAuthenticationFailedException, IOException {
registryAuthenticator =
RegistryAuthenticator.fromAuthenticationMethod(
"Bearer realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"",
registryEndpointRequestProperties,
- "user-agent")
+ "user-agent",
+ httpClient)
.get();
+
+ ByteArrayInputStream tokenJson =
+ new ByteArrayInputStream("{\"token\":\"my_token\"}".getBytes(StandardCharsets.UTF_8));
+ Mockito.when(response.getBody()).thenReturn(tokenJson);
+ Mockito.when(httpClient.call(Mockito.any(), urlCaptor.capture(), Mockito.any()))
+ .thenReturn(response);
}
@Test
@@ -54,7 +78,8 @@ public void testFromAuthenticationMethod_bearer()
RegistryAuthenticator.fromAuthenticationMethod(
"Bearer realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"",
registryEndpointRequestProperties,
- "user-agent")
+ "user-agent",
+ httpClient)
.get();
Assert.assertEquals(
new URL("https://somerealm?service=someservice&scope=repository:someimage:scope"),
@@ -65,7 +90,8 @@ public void testFromAuthenticationMethod_bearer()
RegistryAuthenticator.fromAuthenticationMethod(
"bEaReR realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"",
registryEndpointRequestProperties,
- "user-agent")
+ "user-agent",
+ httpClient)
.get();
Assert.assertEquals(
new URL("https://somerealm?service=someservice&scope=repository:someimage:scope"),
@@ -131,21 +157,24 @@ public void testFromAuthenticationMethod_basic() throws RegistryAuthenticationFa
RegistryAuthenticator.fromAuthenticationMethod(
"Basic realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"",
registryEndpointRequestProperties,
- "user-agent")
+ "user-agent",
+ httpClient)
.isPresent());
Assert.assertFalse(
RegistryAuthenticator.fromAuthenticationMethod(
"BASIC realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"",
registryEndpointRequestProperties,
- "user-agent")
+ "user-agent",
+ httpClient)
.isPresent());
Assert.assertFalse(
RegistryAuthenticator.fromAuthenticationMethod(
"bASIC realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"",
registryEndpointRequestProperties,
- "user-agent")
+ "user-agent",
+ httpClient)
.isPresent());
}
@@ -155,7 +184,8 @@ public void testFromAuthenticationMethod_noBearer() {
RegistryAuthenticator.fromAuthenticationMethod(
"realm=\"https://somerealm\",service=\"someservice\",scope=\"somescope\"",
registryEndpointRequestProperties,
- "user-agent");
+ "user-agent",
+ httpClient);
Assert.fail("Authentication method without 'Bearer ' or 'Basic ' should fail");
} catch (RegistryAuthenticationFailedException ex) {
@@ -169,7 +199,10 @@ public void testFromAuthenticationMethod_noBearer() {
public void testFromAuthenticationMethod_noRealm() {
try {
RegistryAuthenticator.fromAuthenticationMethod(
- "Bearer scope=\"somescope\"", registryEndpointRequestProperties, "user-agent");
+ "Bearer scope=\"somescope\"",
+ registryEndpointRequestProperties,
+ "user-agent",
+ httpClient);
Assert.fail("Authentication method without 'realm' should fail");
} catch (RegistryAuthenticationFailedException ex) {
@@ -186,7 +219,8 @@ public void testFromAuthenticationMethod_noService()
RegistryAuthenticator.fromAuthenticationMethod(
"Bearer realm=\"https://somerealm\"",
registryEndpointRequestProperties,
- "user-agent")
+ "user-agent",
+ httpClient)
.get();
Assert.assertEquals(
@@ -197,14 +231,16 @@ public void testFromAuthenticationMethod_noService()
@Test
public void testUserAgent()
- throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {
+ throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException,
+ RegistryCredentialsNotSentException {
try (TestWebServer server = new TestWebServer(false)) {
try {
RegistryAuthenticator authenticator =
RegistryAuthenticator.fromAuthenticationMethod(
"Bearer realm=\"" + server.getEndpoint() + "\"",
registryEndpointRequestProperties,
- "Competent-Agent")
+ "Competent-Agent",
+ new Connection(true, false, ignored -> {}))
.get();
authenticator.authenticatePush(null);
} catch (RegistryAuthenticationFailedException ex) {
@@ -217,48 +253,53 @@ public void testUserAgent()
@Test
public void testSourceImage_differentSourceRepository()
- throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {
- try (TestWebServer server = new TestWebServer(false, 2)) {
- try {
- RegistryEndpointRequestProperties registryEndpointRequestProperties =
- new RegistryEndpointRequestProperties("someserver", "someimage", "anotherimage");
- RegistryAuthenticator authenticator =
- RegistryAuthenticator.fromAuthenticationMethod(
- "Bearer realm=\"" + server.getEndpoint() + "\"",
- registryEndpointRequestProperties,
- "Competent-Agent")
- .get();
- authenticator.authenticatePush(null);
- } catch (RegistryAuthenticationFailedException ex) {
- // Doesn't matter if auth fails. We only examine what we sent.
- }
- Assert.assertThat(
- server.getInputRead(),
- CoreMatchers.containsString(
- "scope=repository:someimage:pull,push&scope=repository:anotherimage:pull "));
- }
+ throws RegistryCredentialsNotSentException, RegistryAuthenticationFailedException {
+ RegistryAuthenticator authenticator =
+ RegistryAuthenticator.fromAuthenticationMethod(
+ "Bearer realm=\"https://1.2.3.4:5\"",
+ new RegistryEndpointRequestProperties("someserver", "someimage", "anotherimage"),
+ "Competent-Agent",
+ httpClient)
+ .get();
+ authenticator.authenticatePush(null);
+ Assert.assertThat(
+ urlCaptor.getValue().toString(),
+ CoreMatchers.endsWith(
+ "scope=repository:someimage:pull,push&scope=repository:anotherimage:pull"));
}
@Test
public void testSourceImage_sameSourceRepository()
- throws IOException, InterruptedException, GeneralSecurityException, URISyntaxException {
- try (TestWebServer server = new TestWebServer(false)) {
- try {
- RegistryEndpointRequestProperties registryEndpointRequestProperties =
- new RegistryEndpointRequestProperties("someserver", "someimage", "someimage");
- RegistryAuthenticator authenticator =
- RegistryAuthenticator.fromAuthenticationMethod(
- "Bearer realm=\"" + server.getEndpoint() + "\"",
- registryEndpointRequestProperties,
- "Competent-Agent")
- .get();
- authenticator.authenticatePush(null);
- } catch (RegistryAuthenticationFailedException ex) {
- // Doesn't matter if auth fails. We only examine what we sent.
- }
- Assert.assertThat(
- server.getInputRead(),
- CoreMatchers.containsString("service=someserver&scope=repository:someimage:pull,push "));
+ throws RegistryCredentialsNotSentException, RegistryAuthenticationFailedException {
+ RegistryAuthenticator authenticator =
+ RegistryAuthenticator.fromAuthenticationMethod(
+ "Bearer realm=\"https://1.2.3.4:5\"",
+ new RegistryEndpointRequestProperties("someserver", "someimage", "someimage"),
+ "Competent-Agent",
+ httpClient)
+ .get();
+ authenticator.authenticatePush(null);
+ Assert.assertThat(
+ urlCaptor.getValue().toString(),
+ CoreMatchers.endsWith("service=someserver&scope=repository:someimage:pull,push"));
+ }
+
+ @Test
+ public void testAuthorizationCleared() throws RegistryAuthenticationFailedException, IOException {
+ ResponseException responseException = Mockito.mock(ResponseException.class);
+ Mockito.when(responseException.getStatusCode()).thenReturn(401);
+ Mockito.when(responseException.requestAuthorizationCleared()).thenReturn(true);
+ Mockito.when(httpClient.call(Mockito.any(), Mockito.any(), Mockito.any()))
+ .thenThrow(responseException);
+
+ try {
+ registryAuthenticator.authenticatePush(null);
+ Assert.fail();
+ } catch (RegistryCredentialsNotSentException ex) {
+ Assert.assertEquals(
+ "Required credentials for someserver/someimage were not sent because the connection was "
+ + "over HTTP",
+ ex.getMessage());
}
}
}
diff --git a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryEndpointCallerTest.java b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryEndpointCallerTest.java
index 20e5f542e9..bfebf7455b 100644
--- a/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryEndpointCallerTest.java
+++ b/jib-core/src/test/java/com/google/cloud/tools/jib/registry/RegistryEndpointCallerTest.java
@@ -17,8 +17,6 @@
package com.google.cloud.tools.jib.registry;
import com.google.api.client.http.HttpHeaders;
-import com.google.api.client.http.HttpResponse;
-import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.cloud.tools.jib.api.InsecureRegistryException;
import com.google.cloud.tools.jib.api.LogEvent;
@@ -32,18 +30,17 @@
import com.google.cloud.tools.jib.http.Request;
import com.google.cloud.tools.jib.http.RequestWrapper;
import com.google.cloud.tools.jib.http.Response;
+import com.google.cloud.tools.jib.http.ResponseException;
import com.google.common.io.CharStreams;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
-import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.SocketException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.List;
-import java.util.function.Function;
import javax.annotation.Nullable;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
@@ -101,216 +98,64 @@ public String getActionDescription() {
}
}
- private static HttpResponse mockHttpResponse(int statusCode, @Nullable HttpHeaders headers)
- throws IOException {
- HttpResponse mock = Mockito.mock(HttpResponse.class);
+ private static ResponseException mockResponseException(
+ int statusCode, @Nullable HttpHeaders headers) {
+ ResponseException mock = Mockito.mock(ResponseException.class);
Mockito.when(mock.getStatusCode()).thenReturn(statusCode);
- Mockito.when(mock.parseAsString()).thenReturn("");
Mockito.when(mock.getHeaders()).thenReturn(headers != null ? headers : new HttpHeaders());
return mock;
}
- private static HttpResponse mockRedirectHttpResponse(String redirectLocation) throws IOException {
- int code307 = HttpStatusCodes.STATUS_CODE_TEMPORARY_REDIRECT;
- return mockHttpResponse(code307, new HttpHeaders().setLocation(redirectLocation));
- }
-
@Rule public final RestoreSystemProperties systemPropertyRestorer = new RestoreSystemProperties();
@Mock private EventHandlers mockEventHandlers;
- @Mock private Connection mockConnection;
- @Mock private Connection mockInsecureConnection;
+ @Mock private Connection mockHttpClient;
@Mock private Response mockResponse;
- @Mock private Function mockConnectionFactory;
- @Mock private Function mockInsecureConnectionFactory;
- private RegistryEndpointCaller secureEndpointCaller;
+ private RegistryEndpointCaller endpointCaller;
@Before
public void setUp() throws IOException {
- secureEndpointCaller = createRegistryEndpointCaller(false, -1);
+ endpointCaller =
+ new RegistryEndpointCaller<>(
+ mockEventHandlers,
+ "userAgent",
+ new TestRegistryEndpointProvider(),
+ Authorization.fromBasicToken("token"),
+ new RegistryEndpointRequestProperties("serverUrl", "imageName"),
+ mockHttpClient);
- Mockito.when(mockConnectionFactory.apply(Mockito.any())).thenReturn(mockConnection);
- Mockito.when(mockInsecureConnectionFactory.apply(Mockito.any()))
- .thenReturn(mockInsecureConnection);
Mockito.when(mockResponse.getBody())
.thenReturn(new ByteArrayInputStream("body".getBytes(StandardCharsets.UTF_8)));
}
@Test
public void testCall_secureCallerOnUnverifiableServer() throws IOException, RegistryException {
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(Mockito.mock(SSLPeerUnverifiedException.class)); // unverifiable HTTPS server
+ Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), Mockito.any()))
+ .thenThrow(Mockito.mock(SSLPeerUnverifiedException.class));
try {
- secureEndpointCaller.call();
- Assert.fail("Secure caller should fail if cannot verify server");
+ endpointCaller.call();
+ Assert.fail("Should throw InsecureRegistryException when getting SSLException");
} catch (InsecureRegistryException ex) {
Assert.assertEquals(
- "Failed to verify the server at https://serverUrl/v2/api because only secure connections are allowed.",
+ "Failed to verify the server at https://serverUrl/v2/api because only secure connections "
+ + "are allowed.",
ex.getMessage());
}
}
- @Test
- public void testCall_insecureCallerOnUnverifiableServer() throws IOException, RegistryException {
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(Mockito.mock(SSLPeerUnverifiedException.class)); // unverifiable HTTPS server
- Mockito.when(mockInsecureConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenReturn(mockResponse); // OK with non-verifying connection
-
- RegistryEndpointCaller insecureCaller = createRegistryEndpointCaller(true, -1);
- Assert.assertEquals("body", insecureCaller.call());
-
- ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(URL.class);
- Mockito.verify(mockConnectionFactory).apply(urlCaptor.capture());
- Assert.assertEquals(new URL("https://serverUrl/v2/api"), urlCaptor.getAllValues().get(0));
-
- Mockito.verify(mockInsecureConnectionFactory).apply(urlCaptor.capture());
- Assert.assertEquals(new URL("https://serverUrl/v2/api"), urlCaptor.getAllValues().get(1));
-
- Mockito.verifyNoMoreInteractions(mockConnectionFactory);
- Mockito.verifyNoMoreInteractions(mockInsecureConnectionFactory);
-
- Mockito.verify(mockEventHandlers)
- .dispatch(
- LogEvent.info(
- "Cannot verify server at https://serverUrl/v2/api. Attempting again with no TLS verification."));
- }
-
- @Test
- public void testCall_insecureCallerOnHttpServer() throws IOException, RegistryException {
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(Mockito.mock(SSLPeerUnverifiedException.class)) // server is not HTTPS
- .thenReturn(mockResponse);
- Mockito.when(mockInsecureConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(Mockito.mock(SSLPeerUnverifiedException.class)); // server is not HTTPS
-
- RegistryEndpointCaller insecureEndpointCaller = createRegistryEndpointCaller(true, -1);
- Assert.assertEquals("body", insecureEndpointCaller.call());
-
- ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(URL.class);
- Mockito.verify(mockConnectionFactory, Mockito.times(2)).apply(urlCaptor.capture());
- Assert.assertEquals(new URL("https://serverUrl/v2/api"), urlCaptor.getAllValues().get(0));
- Assert.assertEquals(new URL("http://serverUrl/v2/api"), urlCaptor.getAllValues().get(1));
-
- Mockito.verify(mockInsecureConnectionFactory).apply(urlCaptor.capture());
- Assert.assertEquals(new URL("https://serverUrl/v2/api"), urlCaptor.getAllValues().get(2));
-
- Mockito.verifyNoMoreInteractions(mockConnectionFactory);
- Mockito.verifyNoMoreInteractions(mockInsecureConnectionFactory);
-
- Mockito.verify(mockEventHandlers)
- .dispatch(
- LogEvent.info(
- "Cannot verify server at https://serverUrl/v2/api. Attempting again with no TLS verification."));
- Mockito.verify(mockEventHandlers)
- .dispatch(
- LogEvent.info(
- "Failed to connect to https://serverUrl/v2/api over HTTPS. Attempting again with HTTP: http://serverUrl/v2/api"));
- }
-
- @Test
- public void testCall_insecureCallerOnHttpServerAndNoPortSpecified()
- throws IOException, RegistryException {
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(Mockito.mock(ConnectException.class)) // server is not listening on 443
- .thenReturn(mockResponse); // respond when connected through 80
-
- RegistryEndpointCaller insecureEndpointCaller = createRegistryEndpointCaller(true, -1);
- Assert.assertEquals("body", insecureEndpointCaller.call());
-
- ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(URL.class);
- Mockito.verify(mockConnectionFactory, Mockito.times(2)).apply(urlCaptor.capture());
- Assert.assertEquals(new URL("https://serverUrl/v2/api"), urlCaptor.getAllValues().get(0));
- Assert.assertEquals(new URL("http://serverUrl/v2/api"), urlCaptor.getAllValues().get(1));
-
- Mockito.verifyNoMoreInteractions(mockConnectionFactory);
- Mockito.verifyNoMoreInteractions(mockInsecureConnectionFactory);
-
- Mockito.verify(mockEventHandlers)
- .dispatch(
- LogEvent.info(
- "Failed to connect to https://serverUrl/v2/api over HTTPS. Attempting again with HTTP: http://serverUrl/v2/api"));
- }
-
- @Test
- public void testCall_secureCallerOnNonListeningServerAndNoPortSpecified()
- throws IOException, RegistryException {
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(Mockito.mock(ConnectException.class)); // server is not listening on 443
-
- try {
- secureEndpointCaller.call();
- Assert.fail("Should not fall back to HTTP if not allowInsecureRegistries");
- } catch (ConnectException ex) {
- Assert.assertNull(ex.getMessage());
- }
-
- ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(URL.class);
- Mockito.verify(mockConnectionFactory).apply(urlCaptor.capture());
- Assert.assertEquals(new URL("https://serverUrl/v2/api"), urlCaptor.getAllValues().get(0));
-
- Mockito.verifyNoMoreInteractions(mockConnectionFactory);
- Mockito.verifyNoMoreInteractions(mockInsecureConnectionFactory);
- }
-
- @Test
- public void testCall_insecureCallerOnNonListeningServerAndPortSpecified()
- throws IOException, RegistryException {
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(Mockito.mock(ConnectException.class)); // server is not listening on 5000
-
- RegistryEndpointCaller insecureEndpointCaller =
- createRegistryEndpointCaller(true, 5000);
- try {
- insecureEndpointCaller.call();
- Assert.fail("Should not fall back to HTTP if port was explicitly given and cannot connect");
- } catch (ConnectException ex) {
- Assert.assertNull(ex.getMessage());
- }
-
- ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(URL.class);
- Mockito.verify(mockConnectionFactory).apply(urlCaptor.capture());
- Assert.assertEquals(new URL("https://serverUrl:5000/v2/api"), urlCaptor.getAllValues().get(0));
-
- Mockito.verifyNoMoreInteractions(mockConnectionFactory);
- Mockito.verifyNoMoreInteractions(mockInsecureConnectionFactory);
- }
-
- @Test
- public void testCall_timeoutFromConnectException() throws IOException, RegistryException {
- ConnectException mockConnectException = Mockito.mock(ConnectException.class);
- Mockito.when(mockConnectException.getMessage()).thenReturn("Connection timed out");
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(mockConnectException) // server times out on 443
- .thenReturn(mockResponse); // respond when connected through 80
-
- try {
- RegistryEndpointCaller insecureEndpointCaller =
- createRegistryEndpointCaller(true, -1);
- insecureEndpointCaller.call();
- Assert.fail("Should not fall back to HTTP if timed out even for ConnectionException");
-
- } catch (ConnectException ex) {
- Assert.assertSame(mockConnectException, ex);
- Mockito.verify(mockConnection).send(Mockito.anyString(), Mockito.any());
- }
- }
-
@Test
public void testCall_noHttpResponse() throws IOException, RegistryException {
- NoHttpResponseException mockNoHttpResponseException =
- Mockito.mock(NoHttpResponseException.class);
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(mockNoHttpResponseException);
+ NoHttpResponseException mockNoResponseException = Mockito.mock(NoHttpResponseException.class);
+ setUpRegistryResponse(mockNoResponseException);
try {
- secureEndpointCaller.call();
+ endpointCaller.call();
Assert.fail("Call should have failed");
} catch (NoHttpResponseException ex) {
- Assert.assertSame(mockNoHttpResponseException, ex);
+ Assert.assertSame(mockNoResponseException, ex);
}
}
@@ -321,20 +166,13 @@ public void testCall_unauthorized() throws IOException, RegistryException {
@Test
public void testCall_credentialsNotSentOverHttp() throws IOException, RegistryException {
- HttpResponse redirectResponse = mockRedirectHttpResponse("http://newlocation");
- HttpResponse unauthroizedResponse =
- mockHttpResponse(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED, null);
-
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(Mockito.mock(SSLPeerUnverifiedException.class)) // server is not HTTPS
- .thenThrow(new HttpResponseException(redirectResponse)) // redirect to HTTP
- .thenThrow(new HttpResponseException(unauthroizedResponse)); // final response
- Mockito.when(mockInsecureConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(Mockito.mock(SSLPeerUnverifiedException.class)); // server is not HTTPS
-
- RegistryEndpointCaller insecureEndpointCaller = createRegistryEndpointCaller(true, -1);
+ ResponseException unauthorizedException =
+ mockResponseException(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED, null);
+ Mockito.when(unauthorizedException.requestAuthorizationCleared()).thenReturn(true);
+ setUpRegistryResponse(unauthorizedException);
+
try {
- insecureEndpointCaller.call();
+ endpointCaller.call();
Assert.fail("Call should have failed");
} catch (RegistryCredentialsNotSentException ex) {
@@ -346,39 +184,20 @@ public void testCall_credentialsNotSentOverHttp() throws IOException, RegistryEx
@Test
public void testCall_credentialsForcedOverHttp() throws IOException, RegistryException {
- HttpResponse redirectResponse = mockRedirectHttpResponse("http://newlocation");
-
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(Mockito.mock(SSLPeerUnverifiedException.class)) // server is not HTTPS
- .thenThrow(new HttpResponseException(redirectResponse)) // redirect to HTTP
- .thenReturn(mockResponse); // final response
- Mockito.when(mockInsecureConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(Mockito.mock(SSLPeerUnverifiedException.class)); // server is not HTTPS
-
+ ResponseException unauthorizedException =
+ mockResponseException(HttpStatusCodes.STATUS_CODE_UNAUTHORIZED, null);
+ setUpRegistryResponse(unauthorizedException);
System.setProperty(JibSystemProperties.SEND_CREDENTIALS_OVER_HTTP, "true");
- RegistryEndpointCaller insecureEndpointCaller = createRegistryEndpointCaller(true, -1);
- Assert.assertEquals("body", insecureEndpointCaller.call());
- ArgumentCaptor urlCaptor = ArgumentCaptor.forClass(URL.class);
- Mockito.verify(mockConnectionFactory, Mockito.times(3)).apply(urlCaptor.capture());
- Assert.assertEquals(new URL("https://serverUrl/v2/api"), urlCaptor.getAllValues().get(0));
- Assert.assertEquals(new URL("http://serverUrl/v2/api"), urlCaptor.getAllValues().get(1));
- Assert.assertEquals(new URL("http://newlocation"), urlCaptor.getAllValues().get(2));
-
- Mockito.verify(mockInsecureConnectionFactory).apply(urlCaptor.capture());
- Assert.assertEquals(new URL("https://serverUrl/v2/api"), urlCaptor.getAllValues().get(3));
-
- Mockito.verifyNoMoreInteractions(mockConnectionFactory);
- Mockito.verifyNoMoreInteractions(mockInsecureConnectionFactory);
+ try {
+ endpointCaller.call();
+ Assert.fail("Call should have failed");
- Mockito.verify(mockEventHandlers)
- .dispatch(
- LogEvent.info(
- "Cannot verify server at https://serverUrl/v2/api. Attempting again with no TLS verification."));
- Mockito.verify(mockEventHandlers)
- .dispatch(
- LogEvent.info(
- "Failed to connect to https://serverUrl/v2/api over HTTPS. Attempting again with HTTP: http://serverUrl/v2/api"));
+ } catch (RegistryCredentialsNotSentException ex) {
+ throw new AssertionError("should have sent credentials", ex);
+ } catch (RegistryUnauthorizedException ex) {
+ Assert.assertEquals("Unauthorized for serverUrl/imageName", ex.getMessage());
+ }
}
@Test
@@ -403,19 +222,16 @@ public void testCall_methodNotAllowed() throws IOException, RegistryException {
@Test
public void testCall_unknown() throws IOException, RegistryException {
- HttpResponse mockHttpResponse =
- mockHttpResponse(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, null);
- HttpResponseException httpResponseException = new HttpResponseException(mockHttpResponse);
-
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(httpResponseException);
+ ResponseException responseException =
+ mockResponseException(HttpStatusCodes.STATUS_CODE_SERVER_ERROR, null);
+ setUpRegistryResponse(responseException);
try {
- secureEndpointCaller.call();
+ endpointCaller.call();
Assert.fail("Call should have failed");
- } catch (HttpResponseException ex) {
- Assert.assertSame(httpResponseException, ex);
+ } catch (ResponseException ex) {
+ Assert.assertSame(responseException, ex);
}
}
@@ -434,32 +250,13 @@ public void testCall_permanentRedirect() throws IOException, RegistryException {
verifyRetriesWithNewLocation(RegistryEndpointCaller.STATUS_CODE_PERMANENT_REDIRECT);
}
- @Test
- public void testCall_disallowInsecure() throws IOException, RegistryException {
- // Mocks a response for temporary redirect to a new location.
- HttpResponse redirectResponse = mockRedirectHttpResponse("http://newlocation");
-
- HttpResponseException redirectException = new HttpResponseException(redirectResponse);
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(redirectException);
-
- try {
- secureEndpointCaller.call();
- Assert.fail("Call should have failed");
-
- } catch (InsecureRegistryException ex) {
- // pass
- }
- }
-
@Test
public void testCall_logErrorOnIoExceptions() throws IOException, RegistryException {
IOException ioException = new IOException("detailed exception message");
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(ioException);
+ setUpRegistryResponse(ioException);
try {
- secureEndpointCaller.call();
+ endpointCaller.call();
Assert.fail();
} catch (IOException ex) {
@@ -476,11 +273,10 @@ public void testCall_logErrorOnIoExceptions() throws IOException, RegistryExcept
@Test
public void testCall_logErrorOnBrokenPipe() throws IOException, RegistryException {
IOException ioException = new IOException("this is due to broken pipe");
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(ioException);
+ setUpRegistryResponse(ioException);
try {
- secureEndpointCaller.call();
+ endpointCaller.call();
Assert.fail();
} catch (IOException ex) {
@@ -503,11 +299,11 @@ public void testCall_logErrorOnBrokenPipe() throws IOException, RegistryExceptio
@Test
public void testHttpTimeout_propertyNotSet() throws IOException, RegistryException {
ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class);
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), requestCaptor.capture()))
+ Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), requestCaptor.capture()))
.thenReturn(mockResponse);
System.clearProperty(JibSystemProperties.HTTP_TIMEOUT);
- secureEndpointCaller.call();
+ endpointCaller.call();
// We fall back to the default timeout:
// https://github.com/GoogleContainerTools/jib/pull/656#discussion_r203562639
@@ -517,11 +313,11 @@ public void testHttpTimeout_propertyNotSet() throws IOException, RegistryExcepti
@Test
public void testHttpTimeout_stringValue() throws IOException, RegistryException {
ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class);
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), requestCaptor.capture()))
+ Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), requestCaptor.capture()))
.thenReturn(mockResponse);
System.setProperty(JibSystemProperties.HTTP_TIMEOUT, "random string");
- secureEndpointCaller.call();
+ endpointCaller.call();
Assert.assertEquals(20000, new RequestWrapper(requestCaptor.getValue()).getHttpTimeout());
}
@@ -529,11 +325,11 @@ public void testHttpTimeout_stringValue() throws IOException, RegistryException
@Test
public void testHttpTimeout_negativeValue() throws IOException, RegistryException {
ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class);
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), requestCaptor.capture()))
+ Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), requestCaptor.capture()))
.thenReturn(mockResponse);
System.setProperty(JibSystemProperties.HTTP_TIMEOUT, "-1");
- secureEndpointCaller.call();
+ endpointCaller.call();
// We let the negative value pass through:
// https://github.com/GoogleContainerTools/jib/pull/656#discussion_r203562639
@@ -543,11 +339,11 @@ public void testHttpTimeout_negativeValue() throws IOException, RegistryExceptio
@Test
public void testHttpTimeout_0accepted() throws IOException, RegistryException {
ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class);
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), requestCaptor.capture()))
+ Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), requestCaptor.capture()))
.thenReturn(mockResponse);
System.setProperty(JibSystemProperties.HTTP_TIMEOUT, "0");
- secureEndpointCaller.call();
+ endpointCaller.call();
Assert.assertEquals(0, new RequestWrapper(requestCaptor.getValue()).getHttpTimeout());
}
@@ -555,11 +351,11 @@ public void testHttpTimeout_0accepted() throws IOException, RegistryException {
@Test
public void testHttpTimeout() throws IOException, RegistryException {
ArgumentCaptor requestCaptor = ArgumentCaptor.forClass(Request.class);
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), requestCaptor.capture()))
+ Mockito.when(mockHttpClient.call(Mockito.any(), Mockito.any(), requestCaptor.capture()))
.thenReturn(mockResponse);
System.setProperty(JibSystemProperties.HTTP_TIMEOUT, "7593");
- secureEndpointCaller.call();
+ endpointCaller.call();
Assert.assertEquals(7593, new RequestWrapper(requestCaptor.getValue()).getHttpTimeout());
}
@@ -594,13 +390,13 @@ public void testIsBrokenPipe_terminatesWhenCauseIsOriginal() {
@Test
public void testNewRegistryErrorException_jsonErrorOutput() {
- HttpResponseException httpException = Mockito.mock(HttpResponseException.class);
+ ResponseException httpException = Mockito.mock(ResponseException.class);
Mockito.when(httpException.getContent())
.thenReturn(
"{\"errors\": [{\"code\": \"MANIFEST_UNKNOWN\", \"message\": \"manifest unknown\"}]}");
RegistryErrorException registryException =
- secureEndpointCaller.newRegistryErrorException(httpException);
+ endpointCaller.newRegistryErrorException(httpException);
Assert.assertSame(httpException, registryException.getCause());
Assert.assertEquals(
"Tried to actionDescription but failed because: manifest unknown | If this is a bug, "
@@ -610,13 +406,13 @@ public void testNewRegistryErrorException_jsonErrorOutput() {
@Test
public void testNewRegistryErrorException_nonJsonErrorOutput() {
- HttpResponseException httpException = Mockito.mock(HttpResponseException.class);
+ ResponseException httpException = Mockito.mock(ResponseException.class);
// Registry returning non-structured error output
Mockito.when(httpException.getContent()).thenReturn(">>>>> (404) page not found <<<<<");
Mockito.when(httpException.getStatusCode()).thenReturn(404);
RegistryErrorException registryException =
- secureEndpointCaller.newRegistryErrorException(httpException);
+ endpointCaller.newRegistryErrorException(httpException);
Assert.assertSame(httpException, registryException.getCause());
Assert.assertEquals(
"Tried to actionDescription but failed because: registry returned error code 404; "
@@ -633,19 +429,16 @@ public void testNewRegistryErrorException_nonJsonErrorOutput() {
*/
private void verifyThrowsRegistryUnauthorizedException(int httpStatusCode)
throws IOException, RegistryException {
- HttpResponse mockHttpResponse = mockHttpResponse(httpStatusCode, null);
- HttpResponseException httpResponseException = new HttpResponseException(mockHttpResponse);
-
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(httpResponseException);
+ ResponseException responseException = mockResponseException(httpStatusCode, null);
+ setUpRegistryResponse(responseException);
try {
- secureEndpointCaller.call();
+ endpointCaller.call();
Assert.fail("Call should have failed");
} catch (RegistryUnauthorizedException ex) {
Assert.assertEquals("serverUrl/imageName", ex.getImageReference());
- Assert.assertSame(httpResponseException, ex.getHttpResponseException());
+ Assert.assertSame(responseException, ex.getCause());
}
}
@@ -655,16 +448,13 @@ private void verifyThrowsRegistryUnauthorizedException(int httpStatusCode)
*/
private void verifyThrowsRegistryErrorException(int httpStatusCode)
throws IOException, RegistryException {
- HttpResponse errorResponse = mockHttpResponse(httpStatusCode, null);
- Mockito.when(errorResponse.parseAsString())
+ ResponseException errorResponse = mockResponseException(httpStatusCode, null);
+ Mockito.when(errorResponse.getContent())
.thenReturn("{\"errors\":[{\"code\":\"code\",\"message\":\"message\"}]}");
- HttpResponseException httpResponseException = new HttpResponseException(errorResponse);
-
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(httpResponseException);
+ setUpRegistryResponse(errorResponse);
try {
- secureEndpointCaller.call();
+ endpointCaller.call();
Assert.fail("Call should have failed");
} catch (RegistryErrorException ex) {
@@ -682,39 +472,28 @@ private void verifyThrowsRegistryErrorException(int httpStatusCode)
private void verifyRetriesWithNewLocation(int httpStatusCode)
throws IOException, RegistryException {
// Mocks a response for temporary redirect to a new location.
- HttpResponse redirectResponse =
- mockHttpResponse(httpStatusCode, new HttpHeaders().setLocation("https://newlocation"));
-
- // Has mockConnection.send throw first, then succeed.
- HttpResponseException redirectException = new HttpResponseException(redirectResponse);
- Mockito.when(mockConnection.send(Mockito.eq("httpMethod"), Mockito.any()))
- .thenThrow(redirectException)
+ ResponseException redirectException =
+ mockResponseException(httpStatusCode, new HttpHeaders().setLocation("https://newlocation"));
+
+ // Make httpClient.call() throw first, then succeed.
+ setUpRegistryResponse(redirectException);
+ Mockito.when(
+ mockHttpClient.call(
+ Mockito.eq("httpMethod"),
+ Mockito.eq(new URL("https://newlocation")),
+ Mockito.any()))
.thenReturn(mockResponse);
- Assert.assertEquals("body", secureEndpointCaller.call());
+ Assert.assertEquals("body", endpointCaller.call());
+ }
- // Checks that the URL was changed to the new location.
- ArgumentCaptor urlArgumentCaptor = ArgumentCaptor.forClass(URL.class);
- Mockito.verify(mockConnectionFactory, Mockito.times(2)).apply(urlArgumentCaptor.capture());
- Assert.assertEquals(
- new URL("https://serverUrl/v2/api"), urlArgumentCaptor.getAllValues().get(0));
- Assert.assertEquals(new URL("https://newlocation"), urlArgumentCaptor.getAllValues().get(1));
-
- Mockito.verifyNoMoreInteractions(mockConnectionFactory);
- Mockito.verifyNoMoreInteractions(mockInsecureConnectionFactory);
- }
-
- private RegistryEndpointCaller createRegistryEndpointCaller(
- boolean allowInsecure, int port) {
- return new RegistryEndpointCaller<>(
- mockEventHandlers,
- "userAgent",
- new TestRegistryEndpointProvider(),
- Authorization.fromBasicToken("token"),
- new RegistryEndpointRequestProperties(
- (port == -1 ? "serverUrl" : "serverUrl:" + port), "imageName"),
- allowInsecure,
- mockConnectionFactory,
- mockInsecureConnectionFactory);
+ private void setUpRegistryResponse(Exception exceptionToThrow)
+ throws MalformedURLException, IOException {
+ Mockito.when(
+ mockHttpClient.call(
+ Mockito.eq("httpMethod"),
+ Mockito.eq(new URL("https://serverUrl/v2/api")),
+ Mockito.any()))
+ .thenThrow(exceptionToThrow);
}
}
diff --git a/jib-gradle-plugin/CHANGELOG.md b/jib-gradle-plugin/CHANGELOG.md
index e40f378907..4b346e1db8 100644
--- a/jib-gradle-plugin/CHANGELOG.md
+++ b/jib-gradle-plugin/CHANGELOG.md
@@ -11,6 +11,7 @@ All notable changes to this project will be documented in this file.
- Fixed reporting parent build file when `skaffold init` is run on multi-module projects. ([#2091](https://github.com/GoogleContainerTools/jib/pull/2091))
- Now correctly uses the `war` task if it is enabled and the `bootWar` task is disabled for Spring WAR projects. ([#2096](https://github.com/GoogleContainerTools/jib/issues/2096))
+- `allowInsecureRegistries` and the `sendCredentialsOverHttp` system property are now effective for authentication service server connections. ([#2074](https://github.com/GoogleContainerTools/jib/pull/2074)
## 1.7.0
diff --git a/jib-maven-plugin/CHANGELOG.md b/jib-maven-plugin/CHANGELOG.md
index 4e4522e35a..0b1bb415a3 100644
--- a/jib-maven-plugin/CHANGELOG.md
+++ b/jib-maven-plugin/CHANGELOG.md
@@ -10,6 +10,7 @@ All notable changes to this project will be documented in this file.
### Fixed
- Fixed reporting wrong module name when `skaffold init` is run on multi-module projects ([#2088](https://github.com/GoogleContainerTools/jib/issues/2088)).
+- `` and the `sendCredentialsOverHttp` system property are now effective for authentication service server connections. ([#2074](https://github.com/GoogleContainerTools/jib/pull/2074)
## 1.7.0
diff --git a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunner.java b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunner.java
index a913c1e264..0ca73689ad 100644
--- a/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunner.java
+++ b/jib-plugins-common/src/main/java/com/google/cloud/tools/jib/plugins/common/JibBuildRunner.java
@@ -16,7 +16,6 @@
package com.google.cloud.tools.jib.plugins.common;
-import com.google.api.client.http.HttpResponseException;
import com.google.api.client.http.HttpStatusCodes;
import com.google.cloud.tools.jib.api.CacheDirectoryCreationException;
import com.google.cloud.tools.jib.api.Containerizer;
@@ -28,6 +27,7 @@
import com.google.cloud.tools.jib.api.RegistryAuthenticationFailedException;
import com.google.cloud.tools.jib.api.RegistryException;
import com.google.cloud.tools.jib.api.RegistryUnauthorizedException;
+import com.google.cloud.tools.jib.http.ResponseException;
import com.google.cloud.tools.jib.registry.RegistryCredentialsNotSentException;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Verify;
@@ -251,10 +251,10 @@ public JibContainer runBuild()
throw new BuildStepsExecutionException(helpfulSuggestions.forCredentialsNotSent(), ex);
} catch (RegistryAuthenticationFailedException ex) {
- if (ex.getCause() instanceof HttpResponseException) {
+ if (ex.getCause() instanceof ResponseException) {
handleRegistryUnauthorizedException(
new RegistryUnauthorizedException(
- ex.getServerUrl(), ex.getImageName(), (HttpResponseException) ex.getCause()),
+ ex.getServerUrl(), ex.getImageName(), (ResponseException) ex.getCause()),
helpfulSuggestions);
} else {
// Unknown cause