Skip to content

Commit

Permalink
#972: add trustInsecureDownloadRoot parameter to plugins
Browse files Browse the repository at this point in the history
While adding unit tests by default, a bug was discovered when downloading via proxy with BasicAuth. This has also been fixed.
  • Loading branch information
sdoeringNew committed Feb 17, 2022
1 parent f4675f8 commit cbee747
Show file tree
Hide file tree
Showing 13 changed files with 331 additions and 45 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,13 @@ Last public release: [![Maven Central](https://maven-badges.herokuapp.com/maven-

## Changelog

### 1.12.2

* add Dependency: wiremock-jre8 (2.32.0)
* add code coverage to ``DefaultFileDownloader``
* fix bug on download files over proxy with Basic Auth
* add new configuration ``trustInsecureDownloadRoot`` to ignore insecure HTTPS downloads

### 1.12.1

* update Dependency: Jackson (2.13.0), Mockito (4.1.0), JUnit (5.8.1), Hamcrest (2.2; now a direct dependency)
Expand Down
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,9 @@ present).

<!-- optional: where to download node and npm from. Defaults to https://nodejs.org/dist/ -->
<downloadRoot>http://myproxy.example.org/nodejs/</downloadRoot>

<!-- optional: ignore insecure HTTPS download connections -->
<trustInsecureDownloadRoot>false</trustInsecureDownloadRoot>
</configuration>
</plugin>
```
Expand All @@ -120,6 +123,8 @@ You can also specify separate download roots for npm and node as they are stored
<serverId>server001</serverId>
<!-- optional: where to download npm from. Defaults to https://registry.npmjs.org/npm/-/ -->
<npmDownloadRoot>https://myproxy.example.org/npm/</npmDownloadRoot>
<!-- optional: ignore insecure HTTPS download connections -->
<trustInsecureDownloadRoot>false</trustInsecureDownloadRoot>
</configuration>
</plugin>
```
Expand Down Expand Up @@ -166,7 +171,9 @@ https://github.com/eirslett/frontend-maven-plugin/blob/master/frontend-maven-plu
<!-- optional: where to download node from. Defaults to https://nodejs.org/dist/ -->
<nodeDownloadRoot>http://myproxy.example.org/nodejs/</nodeDownloadRoot>
<!-- optional: where to download yarn from. Defaults to https://github.com/yarnpkg/yarn/releases/download/ -->
<yarnDownloadRoot>http://myproxy.example.org/yarn/</yarnDownloadRoot>
<yarnDownloadRoot>http://myproxy.example.org/yarn/</yarnDownloadRoot>
<!-- optional: ignore insecure HTTPS download connections -->
<trustInsecureDownloadRoot>false</trustInsecureDownloadRoot>
</configuration>
</plugin>
```
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public final class InstallNodeAndNpmMojo extends AbstractFrontendMojo {
@Deprecated
private String downloadRoot;

/**
* Will trust insecure download targets. This flag applies to both the {@link #nodeDownloadRoot} and {@link #npmDownloadRoot}.
*/
@Parameter(property = "trustInsecureDownloadRoot", required = false, defaultValue = "false")
private boolean trustInsecureDownloadRoot;

/**
* The version of Node.js to install. IMPORTANT! Most Node.js version names start with 'v', for example 'v0.10.18'
*/
Expand Down Expand Up @@ -81,6 +87,7 @@ public void execute(FrontendPluginFactory factory) throws InstallationException
factory.getNodeInstaller(proxyConfig)
.setNodeVersion(nodeVersion)
.setNodeDownloadRoot(nodeDownloadRoot)
.setTrustInsecureDownloadRoot(trustInsecureDownloadRoot)
.setNpmVersion(npmVersion)
.setUserName(server.getUsername())
.setPassword(server.getPassword())
Expand All @@ -89,19 +96,22 @@ public void execute(FrontendPluginFactory factory) throws InstallationException
.setNodeVersion(nodeVersion)
.setNpmVersion(npmVersion)
.setNpmDownloadRoot(npmDownloadRoot)
.setTrustInsecureDownloadRoot(trustInsecureDownloadRoot)
.setUserName(server.getUsername())
.setPassword(server.getPassword())
.install();
} else {
factory.getNodeInstaller(proxyConfig)
.setNodeVersion(nodeVersion)
.setNodeDownloadRoot(nodeDownloadRoot)
.setTrustInsecureDownloadRoot(trustInsecureDownloadRoot)
.setNpmVersion(npmVersion)
.install();
factory.getNPMInstaller(proxyConfig)
.setNodeVersion(this.nodeVersion)
.setNpmVersion(this.npmVersion)
.setNpmDownloadRoot(npmDownloadRoot)
.setTrustInsecureDownloadRoot(trustInsecureDownloadRoot)
.install();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ public final class InstallNodeAndPnpmMojo extends AbstractFrontendMojo {
@Deprecated
private String downloadRoot;

/**
* Will trust insecure download targets. This flag applies to both the {@link #nodeDownloadRoot} and {@link #pnpmDownloadRoot}.
*/
@Parameter(property = "trustInsecureDownloadRoot", required = false, defaultValue = "false")
private boolean trustInsecureDownloadRoot;

/**
* The version of Node.js to install. IMPORTANT! Most Node.js version names start with 'v', for example 'v0.10.18'
*/
Expand Down Expand Up @@ -81,25 +87,29 @@ public void execute(FrontendPluginFactory factory) throws InstallationException
factory.getNodeInstaller(proxyConfig)
.setNodeVersion(nodeVersion)
.setNodeDownloadRoot(nodeDownloadRoot)
.setTrustInsecureDownloadRoot(trustInsecureDownloadRoot)
.setNpmVersion(pnpmVersion)
.setUserName(server.getUsername())
.setPassword(server.getPassword())
.install();
factory.getPNPMInstaller(proxyConfig)
.setPnpmVersion(pnpmVersion)
.setPnpmDownloadRoot(npmDownloadRoot)
.setTrustInsecureDownloadRoot(trustInsecureDownloadRoot)
.setUserName(server.getUsername())
.setPassword(server.getPassword())
.install();
} else {
factory.getNodeInstaller(proxyConfig)
.setNodeVersion(nodeVersion)
.setNodeDownloadRoot(nodeDownloadRoot)
.setTrustInsecureDownloadRoot(trustInsecureDownloadRoot)
.setNpmVersion(pnpmVersion)
.install();
factory.getPNPMInstaller(proxyConfig)
.setPnpmVersion(this.pnpmVersion)
.setPnpmDownloadRoot(npmDownloadRoot)
.setTrustInsecureDownloadRoot(trustInsecureDownloadRoot)
.install();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ public final class InstallNodeAndYarnMojo extends AbstractFrontendMojo {
defaultValue = YarnInstaller.DEFAULT_YARN_DOWNLOAD_ROOT)
private String yarnDownloadRoot;

/**
* Will trust insecure download targets. This flag applies to both the {@link #nodeDownloadRoot} and {@link #yarnDownloadRoot}.
*/
@Parameter(property = "trustInsecureDownloadRoot", required = false, defaultValue = "false")
private boolean trustInsecureDownloadRoot;

/**
* The version of Node.js to install. IMPORTANT! Most Node.js version names start with 'v', for example
* 'v0.10.18'
Expand Down Expand Up @@ -91,9 +97,11 @@ public void execute(FrontendPluginFactory factory) throws InstallationException
Server server = MojoUtils.decryptServer(this.serverId, this.session, this.decrypter);
if (null != server) {
factory.getNodeInstaller(proxyConfig).setNodeDownloadRoot(this.nodeDownloadRoot)
.setTrustInsecureDownloadRoot(trustInsecureDownloadRoot)
.setNodeVersion(this.nodeVersion).setPassword(server.getPassword())
.setUserName(server.getUsername()).install();
factory.getYarnInstaller(proxyConfig).setYarnDownloadRoot(this.yarnDownloadRoot)
.setTrustInsecureDownloadRoot(trustInsecureDownloadRoot)
.setYarnVersion(this.yarnVersion).setUserName(server.getUsername())
.setPassword(server.getPassword()).setIsYarnBerry(isYarnrcYamlFilePresent()).install();
} else {
Expand Down
6 changes: 6 additions & 0 deletions frontend-plugin-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,12 @@
<version>4.1.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.tomakehurst</groupId>
<artifactId>wiremock-jre8</artifactId>
<version>2.32.0</version>
<scope>test</scope>
</dependency>
</dependencies>

<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,23 +8,31 @@
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;

import javax.net.ssl.SSLContext;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.http.HttpHost;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.AuthCache;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.conn.ssl.TrustSelfSignedStrategy;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.BasicAuthCache;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;
import org.codehaus.plexus.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -41,7 +49,7 @@ public DownloadException(String message){
}

interface FileDownloader {
void download(String downloadUrl, String destination, String userName, String password) throws DownloadException;
void download(String downloadUrl, String destination, String userName, String password, boolean trustInsecureDownloadRoot) throws DownloadException;
}

final class DefaultFileDownloader implements FileDownloader {
Expand All @@ -54,7 +62,7 @@ public DefaultFileDownloader(ProxyConfig proxyConfig){
}

@Override
public void download(String downloadUrl, String destination, String userName, String password) throws DownloadException {
public void download(String downloadUrl, String destination, String userName, String password, boolean trustInsecureDownloadRoot) throws DownloadException {
// force tls to 1.2 since github removed weak cryptographic standards
// https://blog.github.com/2018-02-02-weak-cryptographic-standards-removal-notice/
System.setProperty("https.protocols", "TLSv1.2");
Expand All @@ -66,7 +74,7 @@ public void download(String downloadUrl, String destination, String userName, St
FileUtils.copyFile(new File(downloadURI), new File(destination));
}
else {
CloseableHttpResponse response = execute(fixedDownloadUrl, userName, password);
CloseableHttpResponse response = execute(fixedDownloadUrl, userName, password, trustInsecureDownloadRoot);
int statusCode = response.getStatusLine().getStatusCode();
if(statusCode != 200){
throw new DownloadException("Got error code "+ statusCode +" from the server.");
Expand All @@ -82,12 +90,18 @@ public void download(String downloadUrl, String destination, String userName, St
}
}

private CloseableHttpResponse execute(String requestUrl, String userName, String password) throws IOException {
private CloseableHttpResponse execute(String requestUrl, String userName, String password, boolean trustInsecureDownloadRoot)
throws IOException, DownloadException {
CloseableHttpResponse response;
SSLContext sslContext = null;
if (trustInsecureDownloadRoot) {
LOGGER.info("Trust insecure download enabled.");
sslContext = makeSSLContextForTrustedInsecureDownloads();
}
Proxy proxy = proxyConfig.getProxyForUrl(requestUrl);
if (proxy != null) {
LOGGER.info("Downloading via proxy " + proxy.toString());
return executeViaProxy(proxy, requestUrl);
return executeViaProxy(proxy, sslContext, requestUrl);
} else {
LOGGER.info("No proxy was configured, downloading directly");
if (StringUtils.isNotEmpty(userName) && StringUtils.isNotEmpty(password)) {
Expand All @@ -100,40 +114,50 @@ private CloseableHttpResponse execute(String requestUrl, String userName, String
aURL.getPort(),
userName,
password);
response = buildHttpClient(credentialsProvider).execute(new HttpGet(requestUrl),localContext);
response = buildHttpClient(sslContext, credentialsProvider).execute(new HttpGet(requestUrl),localContext);
} else {
response = buildHttpClient(null).execute(new HttpGet(requestUrl));
response = buildHttpClient(sslContext, null).execute(new HttpGet(requestUrl));
}
}
return response;
}

private CloseableHttpResponse executeViaProxy(Proxy proxy, String requestUrl) throws IOException {
final CloseableHttpClient proxyClient;
if (proxy.useAuthentication()){
proxyClient = buildHttpClient(makeCredentialsProvider(proxy.host,proxy.port,proxy.username,proxy.password));
private CloseableHttpResponse executeViaProxy(Proxy proxy, SSLContext sslContext, String requestUrl) throws IOException {
final HttpGet request = new HttpGet(requestUrl);
request.setConfig(RequestConfig.custom()
.setProxy(new HttpHost(proxy.host, proxy.port))
.build());
final CloseableHttpClient proxyClient = buildHttpClient(sslContext, null);
if (proxy.useAuthentication()) {
final AuthState authState = new AuthState();
authState.update(new BasicScheme(), new UsernamePasswordCredentials(proxy.username, proxy.password));
final HttpClientContext httpContext = HttpClientContext.create();
httpContext.setAttribute(HttpClientContext.PROXY_AUTH_STATE, authState);
return proxyClient.execute(request, httpContext);
} else {
proxyClient = buildHttpClient(null);
return proxyClient.execute(request);
}

final HttpHost proxyHttpHost = new HttpHost(proxy.host, proxy.port);

final RequestConfig requestConfig = RequestConfig.custom().setProxy(proxyHttpHost).build();

final HttpGet request = new HttpGet(requestUrl);
request.setConfig(requestConfig);

return proxyClient.execute(request);
}

private CloseableHttpClient buildHttpClient(CredentialsProvider credentialsProvider) {
private CloseableHttpClient buildHttpClient(SSLContext sslContext, CredentialsProvider credentialsProvider) {
return HttpClients.custom()
.setSSLContext(sslContext)
.disableContentCompression()
.useSystemProperties()
.setDefaultCredentialsProvider(credentialsProvider)
.build();
}

private SSLContext makeSSLContextForTrustedInsecureDownloads() throws DownloadException {
try {
return SSLContextBuilder.create()
.loadTrustMaterial(TrustSelfSignedStrategy.INSTANCE)
.build();
} catch (final NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) {
throw new DownloadException("Unable to create SSLContext to trust insecure downloads.", e);
}
}

private CredentialsProvider makeCredentialsProvider(String host, int port, String username, String password) {
CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
credentialsProvider.setCredentials(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ public class NPMInstaller {

private String nodeVersion, npmVersion, npmDownloadRoot, userName, password;

private boolean trustInsecureDownloadRoot;

private final Logger logger;

private final InstallConfig config;
Expand Down Expand Up @@ -50,6 +52,11 @@ public NPMInstaller setNpmDownloadRoot(String npmDownloadRoot) {
return this;
}

public NPMInstaller setTrustInsecureDownloadRoot(boolean trustInsecureDownloadRoot) {
this.trustInsecureDownloadRoot = trustInsecureDownloadRoot;
return this;
}

public NPMInstaller setUserName(String userName) {
this.userName = userName;
return this;
Expand Down Expand Up @@ -122,7 +129,7 @@ private void installNpm() throws InstallationException {

File archive = this.config.getCacheResolver().resolve(cacheDescriptor);

downloadFileIfMissing(downloadUrl, archive, this.userName, this.password);
downloadFileIfMissing(downloadUrl, archive, this.userName, this.password, this.trustInsecureDownloadRoot);

File installDirectory = getNodeInstallDirectory();
File nodeModulesDirectory = new File(installDirectory, "node_modules");
Expand Down Expand Up @@ -218,16 +225,16 @@ private void extractFile(File archive, File destinationDirectory) throws Archive
this.archiveExtractor.extract(archive.getPath(), destinationDirectory.getPath());
}

private void downloadFileIfMissing(String downloadUrl, File destination, String userName, String password)
private void downloadFileIfMissing(String downloadUrl, File destination, String userName, String password, boolean trustInsecureDownloadRoot)
throws DownloadException {
if (!destination.exists()) {
downloadFile(downloadUrl, destination, userName, password);
downloadFile(downloadUrl, destination, userName, password, trustInsecureDownloadRoot);
}
}

private void downloadFile(String downloadUrl, File destination, String userName, String password)
private void downloadFile(String downloadUrl, File destination, String userName, String password, boolean trustInsecureDownloadRoot)
throws DownloadException {
this.logger.info("Downloading {} to {}", downloadUrl, destination);
this.fileDownloader.download(downloadUrl, destination.getPath(), userName, password);
this.fileDownloader.download(downloadUrl, destination.getPath(), userName, password, trustInsecureDownloadRoot);
}
}
Loading

0 comments on commit cbee747

Please sign in to comment.