diff --git a/pom.xml b/pom.xml index 92ee39e4..957d57d8 100644 --- a/pom.xml +++ b/pom.xml @@ -12,7 +12,7 @@ org.springframework.boot spring-boot-starter-parent - 2.7.10 + 2.7.18 @@ -20,8 +20,7 @@ UTF-8 UTF-8 - 3.6.0 - 11 + 3.8.0 2.4 2.4 @@ -32,14 +31,14 @@ 11 - 2.7.10 + 2.7.18 italiangrid_storm-webdav italiangrid https://sonarcloud.io - 0.4.6.v20220506 + 3.3.2 2.7.1.7 2.3 @@ -50,14 +49,13 @@ 4.2.2 4.2.1 - 31.1-jre + 32.0.0-jre 1.0.5.1 2.3.3.RELEASE 6.0.2 5.5.1 - 1.72 @@ -70,8 +68,7 @@ org.apache.maven.plugins maven-compiler-plugin - 11 - 11 + 11 @@ -99,7 +96,6 @@ src/assembly/tarball.xml - storm-webdav @@ -107,6 +103,9 @@ single + + false + @@ -190,13 +189,6 @@ org.springframework.boot spring-boot-starter-actuator - - - - org.apache.logging.log4j - log4j-api - - @@ -256,6 +248,12 @@ org.springframework.boot spring-boot-starter-test test + + + com.vaadin.external.google + android-json + + @@ -326,11 +324,6 @@ metrics-core - - io.dropwizard.metrics - metrics-jvm - - io.dropwizard.metrics metrics-jetty9 @@ -348,45 +341,30 @@ - org.italiangrid - jetty-utils - ${jetty-utils.version} - - - javax.activation - activation - - - javax.mail - mail - - - org.eclipse.jetty.aggregate - jetty-all - - - ch.qos.logback - logback-core - - - ch.qos.logback - logback-classic - - + org.eclipse.jetty.http2 + http2-server - org.bouncycastle - bcpkix-jdk18on - ${bouncycastle.version} + org.eclipse.jetty + jetty-alpn-conscrypt-server - org.bouncycastle - bcprov-jdk18on - ${bouncycastle.version} + org.slf4j + slf4j-api + + org.slf4j + log4j-over-slf4j + + + + org.italiangrid + voms-api-java + ${voms-api-java.version} + ch.qos.logback diff --git a/src/main/java/org/italiangrid/storm/webdav/config/ServiceConfiguration.java b/src/main/java/org/italiangrid/storm/webdav/config/ServiceConfiguration.java index 9b3a0e03..e22bbf09 100644 --- a/src/main/java/org/italiangrid/storm/webdav/config/ServiceConfiguration.java +++ b/src/main/java/org/italiangrid/storm/webdav/config/ServiceConfiguration.java @@ -33,6 +33,8 @@ public interface ServiceConfiguration { public long getTrustAnchorsRefreshIntervalInSeconds(); + public int getMinConnections(); + public int getMaxConnections(); public int getMaxQueueSize(); diff --git a/src/main/java/org/italiangrid/storm/webdav/config/ServiceConfigurationProperties.java b/src/main/java/org/italiangrid/storm/webdav/config/ServiceConfigurationProperties.java index b6e09321..bf9d9e60 100644 --- a/src/main/java/org/italiangrid/storm/webdav/config/ServiceConfigurationProperties.java +++ b/src/main/java/org/italiangrid/storm/webdav/config/ServiceConfigurationProperties.java @@ -260,10 +260,13 @@ public static class ConnectorProperties { int securePort = 8443; @Positive - int maxConnections = 200; + int minConnections = 50; @Positive - int maxQueueSize = 512; + int maxConnections = 300; + + @Positive + int maxQueueSize = 900; @Positive int maxIdleTimeMsec = 30000; @@ -292,6 +295,14 @@ public void setSecurePort(int securePort) { this.securePort = securePort; } + public int getMinConnections() { + return minConnections; + } + + public void setMinConnections(int minConnections) { + this.minConnections = minConnections; + } + public int getMaxConnections() { return maxConnections; } @@ -732,55 +743,51 @@ public long getTrustAnchorsRefreshIntervalInSeconds() { return getTls().getTrustAnchorsRefreshIntervalSecs(); } + @Override + public int getMinConnections() { + return getConnector().getMinConnections(); + } @Override public int getMaxConnections() { return getConnector().getMaxConnections(); } - @Override public int getMaxQueueSize() { return getConnector().getMaxQueueSize(); } - @Override public int getConnectorMaxIdleTimeInMsec() { return getConnector().getMaxIdleTimeMsec(); } - @Override public String getSAConfigDir() { return getSa().getConfigDir(); } - @Override public boolean enableVOMapFiles() { return getVoMapFiles().isEnabled(); } - @Override public String getVOMapFilesConfigDir() { return getVoMapFiles().getConfigDir(); } - @Override public long getVOMapFilesRefreshIntervalInSeconds() { return getVoMapFiles().getRefreshIntervalSec(); } - @Override public boolean isAuthorizationDisabled() { return getAuthz().isDisabled(); } - @Override public boolean requireClientCertificateAuthentication() { return getTls().isRequireClientCert(); diff --git a/src/main/java/org/italiangrid/storm/webdav/server/DefaultJettyServerCustomizer.java b/src/main/java/org/italiangrid/storm/webdav/server/DefaultJettyServerCustomizer.java index 4e798c4e..82c028e0 100644 --- a/src/main/java/org/italiangrid/storm/webdav/server/DefaultJettyServerCustomizer.java +++ b/src/main/java/org/italiangrid/storm/webdav/server/DefaultJettyServerCustomizer.java @@ -32,7 +32,6 @@ import org.italiangrid.storm.webdav.config.ServiceConfiguration; import org.italiangrid.storm.webdav.config.ServiceConfigurationProperties; import org.italiangrid.storm.webdav.config.StorageAreaConfiguration; -import org.italiangrid.utils.jetty.TLSServerConnectorBuilder; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.web.ServerProperties; diff --git a/src/main/java/org/italiangrid/storm/webdav/server/DefaultWebServerFactory.java b/src/main/java/org/italiangrid/storm/webdav/server/DefaultWebServerFactory.java index b2fb8dfb..2e336aa6 100644 --- a/src/main/java/org/italiangrid/storm/webdav/server/DefaultWebServerFactory.java +++ b/src/main/java/org/italiangrid/storm/webdav/server/DefaultWebServerFactory.java @@ -15,14 +15,16 @@ */ package org.italiangrid.storm.webdav.server; +import java.util.concurrent.ArrayBlockingQueue; + import org.italiangrid.storm.webdav.config.ServiceConfiguration; -import org.italiangrid.utils.jetty.ThreadPoolBuilder; import org.springframework.boot.autoconfigure.web.ServerProperties; import org.springframework.boot.web.embedded.jetty.JettyServerCustomizer; import org.springframework.boot.web.embedded.jetty.JettyServletWebServerFactory; import org.springframework.boot.web.server.WebServerFactoryCustomizer; import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.jetty9.InstrumentedQueuedThreadPool; public class DefaultWebServerFactory implements WebServerFactoryCustomizer { @@ -42,18 +44,18 @@ public DefaultWebServerFactory(ServiceConfiguration configuration, this.metricRegistry = registry; } + private InstrumentedQueuedThreadPool getInstrumentedThreadPool() { + InstrumentedQueuedThreadPool tPool = new InstrumentedQueuedThreadPool(metricRegistry, configuration.getMaxConnections(), + configuration.getMinConnections(), configuration.getConnectorMaxIdleTimeInMsec(), + new ArrayBlockingQueue(configuration.getMaxQueueSize()), "storm.http"); + tPool.setName("thread-pool"); + return tPool; + } + @Override public void customize(JettyServletWebServerFactory factory) { - factory.setThreadPool(ThreadPoolBuilder.instance() - .withMaxRequestQueueSize(configuration.getMaxQueueSize()) - .withMaxThreads(serverProperties.getJetty().getThreads().getMax()) - .withMinThreads(serverProperties.getJetty().getThreads().getMin()) - .registry(metricRegistry) - .withPrefix("storm.http") - .withName("thread-pool") - .build()); - + factory.setThreadPool(getInstrumentedThreadPool()); factory.addServerCustomizers(serverCustomizer); } diff --git a/src/main/java/org/italiangrid/storm/webdav/server/TLSConnectorBuilderError.java b/src/main/java/org/italiangrid/storm/webdav/server/TLSConnectorBuilderError.java new file mode 100644 index 00000000..8d05acb8 --- /dev/null +++ b/src/main/java/org/italiangrid/storm/webdav/server/TLSConnectorBuilderError.java @@ -0,0 +1,34 @@ +/** + * Copyright (c) Istituto Nazionale di Fisica Nucleare, 2014-2023. + * + * 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 org.italiangrid.storm.webdav.server; + +public class TLSConnectorBuilderError extends RuntimeException { + + private static final long serialVersionUID = 1L; + + public TLSConnectorBuilderError(Throwable cause) { + super(cause); + } + + public TLSConnectorBuilderError(String message, Throwable cause) { + super(message, cause); + } + + public TLSConnectorBuilderError(String message) { + super(message); + } + +} diff --git a/src/main/java/org/italiangrid/storm/webdav/server/TLSServerConnectorBuilder.java b/src/main/java/org/italiangrid/storm/webdav/server/TLSServerConnectorBuilder.java new file mode 100644 index 00000000..67379b58 --- /dev/null +++ b/src/main/java/org/italiangrid/storm/webdav/server/TLSServerConnectorBuilder.java @@ -0,0 +1,664 @@ +/** + * Copyright (c) Istituto Nazionale di Fisica Nucleare, 2014-2023. + * + * 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 org.italiangrid.storm.webdav.server; + +import static java.util.Objects.isNull; + +import java.io.File; +import java.io.IOException; +import java.security.KeyManagementException; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.Security; +import java.security.cert.CertificateException; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.KeyManager; +import javax.net.ssl.SSLContext; +import javax.net.ssl.TrustManager; + +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.conscrypt.OpenSSLProvider; +import org.eclipse.jetty.alpn.server.ALPNServerConnectionFactory; +import org.eclipse.jetty.http.HttpVersion; +import org.eclipse.jetty.http2.HTTP2Cipher; +import org.eclipse.jetty.http2.server.HTTP2ServerConnectionFactory; +import org.eclipse.jetty.server.ConnectionFactory; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.HttpConnectionFactory; +import org.eclipse.jetty.server.SecureRequestCustomizer; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.SslConnectionFactory; +import org.eclipse.jetty.util.ssl.SslContextFactory; + +import com.codahale.metrics.MetricRegistry; +import com.codahale.metrics.jetty9.InstrumentedConnectionFactory; + +import eu.emi.security.authn.x509.X509CertChainValidatorExt; +import eu.emi.security.authn.x509.helpers.ssl.SSLTrustManager; +import eu.emi.security.authn.x509.impl.PEMCredential; + +/** + * A builder that configures a Jetty server TLS connector integrated with CANL + * {@link X509CertChainValidatorExt} certificate validation services. + * + */ +public class TLSServerConnectorBuilder { + + /** + * Conscrypt provider name. + */ + public static final String CONSCRYPT_PROVIDER = "Conscrypt"; + + /** + * Default service certificate file. + */ + public static final String DEFAULT_CERTIFICATE_FILE = "/etc/grid-security/hostcert.pem"; + + /** + * Default service certificate private key file. + */ + public static final String DEFAULT_CERTIFICATE_KEY_FILE = "/etc/grid-security/hostcert.pem"; + + /** + * The port for this connector. + */ + private int port; + + /** + * The certificate file location. + */ + private String certificateFile = DEFAULT_CERTIFICATE_FILE; + + /** + * The certificate private key file location. + */ + private String certificateKeyFile = DEFAULT_CERTIFICATE_KEY_FILE; + + /** + * The password to decrypt the certificate private key file. + */ + private char[] certicateKeyPassword = null; + + /** + * The certificate validator used by this connector builder. + */ + private final X509CertChainValidatorExt certificateValidator; + + /** + * Whether client auth will be required for this connector. + */ + private boolean tlsNeedClientAuth = false; + + /** + * Whether cluent auth is supported for this connector. + */ + private boolean tlsWantClientAuth = true; + + /** + * Supported SSL protocols. + */ + private String[] includeProtocols; + + /** + * Disabled SSL protocols. + */ + private String[] excludeProtocols; + + /** + * Supported cipher suites. + */ + private String[] includeCipherSuites; + + /** + * Disabled cipher suites. + */ + private String[] excludeCipherSuites; + + /** + * The HTTP configuration for the connector being created. + */ + private HttpConfiguration httpConfiguration; + + /** + * The key manager to use for the connector being created. + */ + private KeyManager keyManager; + + /** + * The server for which the connector is being created. + */ + private final Server server; + + /** + * The metric name to associate to the connector being built. + */ + private String metricName; + + /** + * The metric registry. + */ + private MetricRegistry registry; + + /** + * Whether the Conscrypt provider should be used instead of the default JSSE implementation + */ + private boolean useConscrypt = false; + + + /** + * Whether HTTP/2 should be configured + */ + private boolean enableHttp2 = false; + + /** + * Which TLS protocol string should be used + */ + private String tlsProtocol = "TLSv1.2"; + + /** + * Custom TLS hostname verifier + */ + private HostnameVerifier hostnameVerifier = null; + + /** + * Disable JSSE hostname verification + */ + private boolean disableJsseHostnameVerification = false; + + /** + * Number of acceptors threads for the connector + */ + private int acceptors = -1; + + /** + * Number of selector threads for the connector + */ + private int selectors = -1; + + /** + * Returns an instance of the {@link TLSServerConnectorBuilder}. + * + * @param s the {@link Server} for which the connector is being created + * @param certificateValidator a {@link X509CertChainValidatorExt} used to validate certificates + * @return an instance of the {@link TLSServerConnectorBuilder} + */ + public static TLSServerConnectorBuilder instance(Server s, + X509CertChainValidatorExt certificateValidator) { + + return new TLSServerConnectorBuilder(s, certificateValidator); + } + + /** + * Private ctor. + * + * @param s the {@link Server} for which the connector is being created + * @param certificateValidator a {@link X509CertChainValidatorExt} used to validate certificates + */ + private TLSServerConnectorBuilder(Server s, X509CertChainValidatorExt certificateValidator) { + + if (s == null) { + throw new IllegalArgumentException("Server cannot be null"); + } + + if (certificateValidator == null) { + throw new IllegalArgumentException("certificateValidator cannot be null"); + } + + this.server = s; + this.certificateValidator = certificateValidator; + } + + private void credentialsSanityChecks() { + + checkFileExistsAndIsReadable(new File(certificateFile), "Error accessing certificate file"); + + checkFileExistsAndIsReadable(new File(certificateKeyFile), + "Error accessing certificate key file"); + + } + + private void loadCredentials() { + + credentialsSanityChecks(); + + PEMCredential serviceCredentials = null; + + try { + + serviceCredentials = + new PEMCredential(certificateKeyFile, certificateFile, certicateKeyPassword); + + } catch (KeyStoreException | CertificateException | IOException e) { + + throw new TLSConnectorBuilderError("Error setting up service credentials", e); + } + + keyManager = serviceCredentials.getKeyManager(); + } + + /** + * Configures SSL session parameters for the jetty {@link SslContextFactory}. + * + * @param contextFactory the {@link SslContextFactory} being configured + */ + private void configureContextFactory(SslContextFactory.Server contextFactory) { + + if (excludeProtocols != null) { + contextFactory.setExcludeProtocols(excludeProtocols); + } + + if (includeProtocols != null) { + contextFactory.setIncludeProtocols(includeProtocols); + } + + if (excludeCipherSuites != null) { + contextFactory.setExcludeCipherSuites(excludeCipherSuites); + } + + if (includeCipherSuites != null) { + contextFactory.setIncludeCipherSuites(includeCipherSuites); + } + + contextFactory.setWantClientAuth(tlsWantClientAuth); + contextFactory.setNeedClientAuth(tlsNeedClientAuth); + + if (useConscrypt) { + contextFactory.setProvider(CONSCRYPT_PROVIDER); + } else { + contextFactory.setProvider(BouncyCastleProvider.PROVIDER_NAME); + } + + if (hostnameVerifier != null) { + contextFactory.setHostnameVerifier(hostnameVerifier); + } + + if (disableJsseHostnameVerification) { + contextFactory.setEndpointIdentificationAlgorithm(null); + } + + } + + /** + * Builds a default {@link HttpConfiguration} for the TLS-enabled connector being created + * + * @return the default {@link HttpConfiguration} + */ + private HttpConfiguration defaultHttpConfiguration() { + + HttpConfiguration httpsConfig = new HttpConfiguration(); + + httpsConfig.setSecureScheme("https"); + + httpsConfig.setSecurePort(port); + + httpsConfig.setOutputBufferSize(32768); + httpsConfig.setRequestHeaderSize(8192); + httpsConfig.setResponseHeaderSize(8192); + + httpsConfig.setSendServerVersion(true); + httpsConfig.setSendDateHeader(false); + + httpsConfig.addCustomizer(new SecureRequestCustomizer()); + + return httpsConfig; + + } + + /** + * Gives access to the {@link HttpConfiguration} used for the TLS-enabled connector being created. + * If the configuration is not set, it creates it using {@link #defaultHttpConfiguration()}. + * + * @return the {@link HttpConfiguration} being used for the TLS-enabled connector. + */ + public HttpConfiguration httpConfiguration() { + + if (httpConfiguration == null) { + httpConfiguration = defaultHttpConfiguration(); + } + + return httpConfiguration; + + } + + /** + * Sets the port for the connector being created. + * + * @param port the port for the connector + * @return this builder + */ + public TLSServerConnectorBuilder withPort(int port) { + + this.port = port; + return this; + } + + /** + * Sets the certificate file for the connector being created. + * + * @param certificateFile the certificate file + * @return this builder + */ + public TLSServerConnectorBuilder withCertificateFile(String certificateFile) { + + this.certificateFile = certificateFile; + return this; + } + + /** + * Sets the certificate key file for the connector being created. + * + * @param certificateKeyFile the certificate key file + * @return this builder + */ + public TLSServerConnectorBuilder withCertificateKeyFile(String certificateKeyFile) { + + this.certificateKeyFile = certificateKeyFile; + return this; + } + + /** + * The the certificate key password for the connector being built + * + * @param certificateKeyPassword the certificate key password + * @return this builder + */ + public TLSServerConnectorBuilder withCertificateKeyPassword(char[] certificateKeyPassword) { + + this.certicateKeyPassword = certificateKeyPassword; + return this; + } + + /** + * Sets the {@link SslContextFactory#setNeedClientAuth(boolean)} parameter for the connector being + * created. + * + * @param needClientAuth true if client authentication is required + * @return this builder + */ + public TLSServerConnectorBuilder withNeedClientAuth(boolean needClientAuth) { + + this.tlsNeedClientAuth = needClientAuth; + return this; + } + + /** + * Sets the {@link SslContextFactory#setWantClientAuth(boolean)} parameter for the connector being + * created. + * + * @param wantClientAuth true if client authentication is wanted + * @return this builder + */ + public TLSServerConnectorBuilder withWantClientAuth(boolean wantClientAuth) { + + this.tlsWantClientAuth = wantClientAuth; + return this; + } + + /** + * Sets SSL included protocols. See {@link SslContextFactory#setIncludeProtocols(String...)}. + * + * @param includeProtocols the array of included protocol names + * @return this builder + */ + public TLSServerConnectorBuilder withIncludeProtocols(String... includeProtocols) { + + this.includeProtocols = includeProtocols; + return this; + } + + /** + * Sets SSL excluded protocols. See {@link SslContextFactory#setExcludeProtocols(String...)}. + * + * @param excludeProtocols the array of excluded protocol names + * @return this builder + */ + public TLSServerConnectorBuilder withExcludeProtocols(String... excludeProtocols) { + + this.excludeProtocols = excludeProtocols; + return this; + } + + /** + * Sets the SSL included cipher suites. + * + * @param includeCipherSuites the array of included cipher suites. + * @return this builder + */ + public TLSServerConnectorBuilder withIncludeCipherSuites(String... includeCipherSuites) { + + this.includeCipherSuites = includeCipherSuites; + return this; + } + + /** + * Sets the SSL ecluded cipher suites. + * + * @param excludeCipherSuites the array of excluded cipher suites. + * @return this builder + */ + public TLSServerConnectorBuilder withExcludeCipherSuites(String... excludeCipherSuites) { + + this.excludeCipherSuites = excludeCipherSuites; + return this; + } + + /** + * Sets the {@link HttpConfiguration} for the connector being built. + * + * @param conf the {@link HttpConfiguration} to use + * @return this builder + */ + public TLSServerConnectorBuilder withHttpConfiguration(HttpConfiguration conf) { + + this.httpConfiguration = conf; + return this; + } + + /** + * Sets the {@link KeyManager} for the connector being built. + * + * @param km the {@link KeyManager} to use + * @return this builder + */ + public TLSServerConnectorBuilder withKeyManager(KeyManager km) { + + this.keyManager = km; + return this; + } + + public TLSServerConnectorBuilder withConscrypt(boolean conscryptEnabled) { + this.useConscrypt = conscryptEnabled; + return this; + } + + public TLSServerConnectorBuilder withHttp2(boolean http2Enabled) { + this.enableHttp2 = http2Enabled; + return this; + } + + public TLSServerConnectorBuilder metricRegistry(MetricRegistry registry) { + this.registry = registry; + return this; + } + + public TLSServerConnectorBuilder metricName(String metricName) { + this.metricName = metricName; + return this; + } + + public TLSServerConnectorBuilder withTlsProtocol(String tlsProtocol) { + this.tlsProtocol = tlsProtocol; + return this; + } + + public TLSServerConnectorBuilder withHostnameVerifier(HostnameVerifier verifier) { + this.hostnameVerifier = verifier; + return this; + } + + public TLSServerConnectorBuilder withDisableJsseHostnameVerification( + boolean disableJsseHostnameVerification) { + this.disableJsseHostnameVerification = disableJsseHostnameVerification; + return this; + } + + public TLSServerConnectorBuilder withAcceptors(int acceptors) { + this.acceptors = acceptors; + return this; + } + + public TLSServerConnectorBuilder withSelectors(int selectors) { + this.selectors = selectors; + return this; + } + + private SSLContext buildSSLContext() { + + SSLContext sslCtx; + + try { + + KeyManager[] kms = new KeyManager[] {keyManager}; + SSLTrustManager tm = new SSLTrustManager(certificateValidator); + + if (useConscrypt) { + + if (isNull(Security.getProvider(CONSCRYPT_PROVIDER))) { + Security.addProvider(new OpenSSLProvider()); + } + + sslCtx = SSLContext.getInstance(tlsProtocol, CONSCRYPT_PROVIDER); + } else { + sslCtx = SSLContext.getInstance(tlsProtocol); + } + + sslCtx.init(kms, new TrustManager[] {tm}, null); + + } catch (NoSuchAlgorithmException e) { + + throw new TLSConnectorBuilderError("TLS protocol not supported: " + e.getMessage(), e); + } catch (KeyManagementException e) { + throw new TLSConnectorBuilderError(e); + } catch (NoSuchProviderException e) { + throw new TLSConnectorBuilderError("TLS provider error: " + e.getMessage(), e); + } + + return sslCtx; + } + + /** + * Builds a {@link ServerConnector} based on the {@link TLSServerConnectorBuilder} parameters + * + * @return a {@link ServerConnector} + */ + public ServerConnector build() { + + if (keyManager == null) { + loadCredentials(); + } + + SSLContext sslContext = buildSSLContext(); + SslContextFactory.Server cf = new SslContextFactory.Server(); + + cf.setSslContext(sslContext); + + + configureContextFactory(cf); + + if (httpConfiguration == null) { + httpConfiguration = defaultHttpConfiguration(); + } + + + HttpConnectionFactory httpConnFactory = new HttpConnectionFactory(httpConfiguration); + ConnectionFactory connFactory = null; + + if (registry != null) { + connFactory = new InstrumentedConnectionFactory(httpConnFactory, registry.timer(metricName)); + } else { + connFactory = httpConnFactory; + } + + + ConnectionFactory h2ConnFactory = null; + ServerConnector connector = null; + + if (enableHttp2) { + + HTTP2ServerConnectionFactory h2cf = new HTTP2ServerConnectionFactory(httpConfiguration); + + if (registry != null) { + h2ConnFactory = new InstrumentedConnectionFactory(h2cf, registry.timer(metricName)); + } else { + h2ConnFactory = h2cf; + } + ALPNServerConnectionFactory alpn = createAlpnProtocolFactory(httpConnFactory); + cf.setCipherComparator(HTTP2Cipher.COMPARATOR); + cf.setUseCipherSuitesOrder(true); + + SslConnectionFactory sslCf = new SslConnectionFactory(cf, alpn.getProtocol()); + + connector = new ServerConnector(server, acceptors, selectors, sslCf, alpn, h2ConnFactory, + httpConnFactory); + + } else { + + connector = new ServerConnector(server, acceptors, selectors, + new SslConnectionFactory(cf, HttpVersion.HTTP_1_1.asString()), connFactory); + } + + connector.setPort(port); + return connector; + } + + private ALPNServerConnectionFactory createAlpnProtocolFactory( + HttpConnectionFactory httpConnectionFactory) { + ALPNServerConnectionFactory alpn = new ALPNServerConnectionFactory(); + alpn.setDefaultProtocol(httpConnectionFactory.getProtocol()); + return alpn; + } + + + /** + * Checks that file exists and is readable. + * + * @param f the {@link File} to be checked + * @param prefix A prefix string for the error message, in case the file does not exist and is not + * readable + * @throws RuntimeException if the file does not exist or is not readable + */ + private void checkFileExistsAndIsReadable(File f, String prefix) { + + String errorMessage = null; + + if (!f.exists()) { + errorMessage = "File does not exists"; + } else if (!f.canRead()) { + errorMessage = "File is not readable"; + } else if (f.isDirectory()) { + errorMessage = "File is a directory"; + } + + if (errorMessage != null) { + String msg = String.format("%s: %s [%s]", prefix, errorMessage, f.getAbsolutePath()); + throw new TLSConnectorBuilderError(msg); + } + + } +} diff --git a/src/main/java/org/italiangrid/storm/webdav/spring/AppConfig.java b/src/main/java/org/italiangrid/storm/webdav/spring/AppConfig.java index ce9a7a88..22d43a01 100644 --- a/src/main/java/org/italiangrid/storm/webdav/spring/AppConfig.java +++ b/src/main/java/org/italiangrid/storm/webdav/spring/AppConfig.java @@ -16,7 +16,7 @@ package org.italiangrid.storm.webdav.spring; import static java.util.Objects.isNull; -import static org.italiangrid.utils.jetty.TLSServerConnectorBuilder.CONSCRYPT_PROVIDER; +import static org.italiangrid.storm.webdav.server.TLSServerConnectorBuilder.CONSCRYPT_PROVIDER; import java.io.IOException; import java.net.MalformedURLException; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 6eaaa236..67245d86 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -86,13 +86,19 @@ storm: port: ${STORM_WEBDAV_HTTP_PORT:8085} # HTTPS connector port secure-port: ${STORM_WEBDAV_HTTPS_PORT:8443} + # Min concurrent connections + min-connections: ${STORM_WEBDAV_MIN_CONNECTIONS:50} # Max concurrent connections max-connections: ${STORM_WEBDAV_MAX_CONNECTIONS:300} # Connection queue size max-queue-size: ${STORM_WEBDAV_MAX_QUEUE_SIZE:900} # Connector Maximum idle time (in milliseconds) - max-idle-time-msec: ${STORM_WEBDAV_CONNECTOR_MAX_IDLE_TIME:30000} + max-idle-time-msec: ${STORM_WEBDAV_CONNECTOR_MAX_IDLE_TIME:30000} output-buffer-size-bytes: ${storm.buffer.file-buffer-size-bytes} + # Number of acceptor threads to use. When the value is -1, the default, the number of acceptors is derived from the operating environment. + jetty-acceptors: ${STORM_WEBDAV_CONNECTOR_ACCEPTORS:-1} + # Number of selector threads to use. When the value is -1, the default, the number of selectors is derived from the operating environment. + jetty-selectors: ${STORM_WEBDAV_CONNECTOR_SELECTORS:-1} tls: # Path to the service certificate. diff --git a/src/test/java/org/italiangrid/storm/webdav/server/TLSConnectorBuilderTest.java b/src/test/java/org/italiangrid/storm/webdav/server/TLSConnectorBuilderTest.java new file mode 100644 index 00000000..003dd1b1 --- /dev/null +++ b/src/test/java/org/italiangrid/storm/webdav/server/TLSConnectorBuilderTest.java @@ -0,0 +1,108 @@ +/** + * Copyright (c) Istituto Nazionale di Fisica Nucleare, 2014-2023. + * + * 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 org.italiangrid.storm.webdav.server; + +import static org.junit.Assert.assertThrows; + +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.KeyManager; + +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.eclipse.jetty.server.HttpConfiguration; +import org.eclipse.jetty.server.Server; +import org.italiangrid.storm.webdav.server.util.CANLListener; +import org.italiangrid.voms.util.CertificateValidatorBuilder; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import eu.emi.security.authn.x509.CrlCheckingMode; +import eu.emi.security.authn.x509.NamespaceCheckingMode; +import eu.emi.security.authn.x509.OCSPCheckingMode; +import eu.emi.security.authn.x509.X509CertChainValidatorExt; + +@ExtendWith(MockitoExtension.class) +public class TLSConnectorBuilderTest { + + @Test + public void tlsConnectorBuilderErrorTests() { + + new TLSConnectorBuilderError("This is an error!"); + new TLSConnectorBuilderError("This is an error!", new RuntimeException()); + new TLSConnectorBuilderError(new RuntimeException()); + } + + @Test + public void illegalArgumentExceptionThrown() { + + Server server = Mockito.mock(Server.class); + X509CertChainValidatorExt validator = Mockito.mock(X509CertChainValidatorExt.class); + + assertThrows(IllegalArgumentException.class, () -> { + TLSServerConnectorBuilder.instance(null, validator); + }); + assertThrows(IllegalArgumentException.class, () -> { + TLSServerConnectorBuilder.instance(server, null); + }); + assertThrows(IllegalArgumentException.class, () -> { + TLSServerConnectorBuilder.instance(null, null); + }); + } + + private X509CertChainValidatorExt getValidator() { + + CANLListener l = new org.italiangrid.storm.webdav.server.util.CANLListener(); + CertificateValidatorBuilder builder = new CertificateValidatorBuilder(); + + long refreshInterval = TimeUnit.SECONDS.toMillis(3600); + + return builder.namespaceChecks(NamespaceCheckingMode.EUGRIDPMA_AND_GLOBUS_REQUIRE) + .crlChecks(CrlCheckingMode.IF_VALID) + .ocspChecks(OCSPCheckingMode.IGNORE) + .lazyAnchorsLoading(false) + .storeUpdateListener(l) + .validationErrorListener(l) + .trustAnchorsDir("src/test/resources/trust-anchors") + .trustAnchorsUpdateInterval(refreshInterval) + .build(); + } + + @Test + public void tlsConnectorBuilderTests() { + + Server server = new Server(); + X509CertChainValidatorExt validator = getValidator(); + TLSServerConnectorBuilder builder = TLSServerConnectorBuilder.instance(server, validator); + HttpConfiguration httpConfiguration = builder.httpConfiguration(); + KeyManager keyManager = Mockito.mock(KeyManager.class); + builder.withPort(1234) + .withCertificateFile("fake-certificate") + .withCertificateKeyFile("fake-key") + .withCertificateKeyPassword("secret".toCharArray()) + .withHttpConfiguration(httpConfiguration) + .withKeyManager(keyManager) + .withExcludeCipherSuites("one", "two") + .withIncludeCipherSuites("three", "four") + .withIncludeProtocols("protocol", "another-protocol") + .withExcludeProtocols("another-more-protocol") + .withHostnameVerifier(new NoopHostnameVerifier()) + .withConscrypt(false); + + builder.build(); + } +} \ No newline at end of file