-
Notifications
You must be signed in to change notification settings - Fork 399
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[#41] feat(server): Add https support for Jetty server #860
Changes from all commits
d2455cd
3244424
8db1af3
0627f52
a9bc4ff
a5b36c4
25e78fb
f2b46f2
bbf3760
3f083d4
3666dbc
9dc2780
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -44,3 +44,44 @@ The signature algorithm which Gravitino supports is as below: | |
| PS256 | RSASSA-PSS using SHA-256 and MGF1 with SHA-256 | | ||
| PS384 | RSASSA-PSS using SHA-384 and MGF1 with SHA-384 | | ||
| PS512 | RSASSA-PSS using SHA-512 and MGF1 with SHA-512 | | ||
|
||
## HTTPS configuration | ||
Users would better use HTTPS instead of HTTP if users choose OAuth 2.0 as the authenticator. | ||
Because HTTPS will protect the header of request from smuggling and HTTPS will be safer. | ||
If users choose to enable HTTPS, Gravitino won't provide the ability of HTTP service. | ||
Both Gravitino server and Iceberg REST service can configure HTTPS. | ||
|
||
### Gravitino server's configuration | ||
| Configuration item | Description | Default value | Since version | | ||
|-----------------------------------------------------|------------------------------------------------------------|---------------|---------------| | ||
| `gravitino.server.webserver.enableHttps` | Enables https | `false` | 0.3.0 | | ||
| `gravitino.server.webserver.httpsPort` | The https port number of the Jetty web server | `8433` | 0.3.0 | | ||
| `gravitino.server.webserver.keyStorePath` | Path to the key store file | `` | 0.3.0 | | ||
| `gravitino.server.webserver.keyStorePassword` | Password to the key store | `` | 0.3.0 | | ||
| `gravitino.server.webserver.keyStoreType` | The type to the key store | `JKS` | 0.3.0 | | ||
| `gravitino.server.webserver.managerPassword` | Manager password to the key store | `` | 0.3.0 | | ||
| `gravitino.server.webserver.tlsProtocol` | TLS protocol to use. The protocol must be supported by JVM | none | 0.3.0 | | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How do you configure this? and algorithm, it would be better to add more docs about it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK, I will add more description referring the doc of Spark. |
||
| `gravitino.server.webserver.enableCipherAlgorithms` | The collection of the cipher algorithms which are enabled. | `` | 0.3.0 | | ||
| `gravitino.server.webserver.enableClientAuth` | Enables the authentication of the client | `false` | 0.3.0 | | ||
| `gravitino.server.webserver.trustStorePath` | Path to the trust store file | `` | 0.3.0 | | ||
| `gravitino.server.webserver.trustStorePassword` | Password to the trust store | `` | 0.3.0 | | ||
| `gravitino.server.webserver.trustStoreType` | The type to the trust store | `JKS` | 0.3.0 | | ||
|
||
### Iceberg REST service's configuration | ||
| Configuration item | Description | Default value | Since version | | ||
|------------------------------------------------------------|------------------------------------------------------------|---------------|---------------| | ||
| `gravitino.auxService.iceberg-rest.enableHttps` | Enables https | `false` | 0.3.0 | | ||
| `gravitino.auxService.iceberg-rest.httpsPort` | The https port number of the Jetty web server | `8433` | 0.3.0 | | ||
| `gravitino.auxService.iceberg-rest.keyStorePath` | Path to the key store file | `` | 0.3.0 | | ||
| `gravitino.auxService.iceberg-rest.keyStorePassword` | Password to the key store | `` | 0.3.0 | | ||
| `gravitino.uxService.iceberg-rest.keyStoreType` | The type to the key store | `JKS` | 0.3.0 | | ||
| `gravitino.auxService.iceberg-rest.managerPassword` | Manager password to the key store | `` | 0.3.0 | | ||
| `gravitino.auxService.iceberg-rest.tlsProtocol` | TLS protocol to use. The protocol must be supported by JVM | none | 0.3.0 | | ||
| `gravitino.auxService.iceberg-rest.enableCipherAlgorithms` | The collection of the cipher algorithms which are enabled | `` | 0.3.0 | | ||
| `gravitino.auxService.iceberg-rest.enableClientAuth` | Enables the authentication of the client | `false` | 0.3.0 | | ||
| `gravitino.auxService.iceberg-rest.trustStorePath` | Path to the trust store file | `` | 0.3.0 | | ||
| `gravitino.auxService.iceberg-rest.trustStorePassword` | Password to the trust store | `` | 0.3.0 | | ||
| `gravitino.auxService.iceberg-rest.trustStoreType` | The type to the trust store | `JKS` | 0.3.0 | | ||
|
||
About `tlsProtocol`, the reference list of protocols can be found in the "Additional JSSE Standard Names" section of the Java security guide. The list for Java 8 can be found at [this](https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#jssenames). | ||
About `enableCipherAlgorithms`, the reference list of protocols can be found in the "JSSE Cipher Suite Names" section of the Java security guide. The list for Java 8 can be found at [this](https://docs.oracle.com/javase/8/docs/technotes/guides/security/StandardNames.html#ciphersuites) |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -8,6 +8,7 @@ | |
import com.codahale.metrics.servlets.MetricsServlet; | ||
import com.datastrato.gravitino.GravitinoEnv; | ||
import com.datastrato.gravitino.metrics.MetricsSystem; | ||
import com.google.common.base.Preconditions; | ||
import java.io.File; | ||
import java.io.IOException; | ||
import java.net.BindException; | ||
|
@@ -16,22 +17,28 @@ | |
import java.nio.file.Path; | ||
import java.nio.file.Paths; | ||
import java.util.EnumSet; | ||
import java.util.Optional; | ||
import java.util.Set; | ||
import java.util.concurrent.LinkedBlockingQueue; | ||
import javax.servlet.DispatcherType; | ||
import javax.servlet.Filter; | ||
import javax.servlet.Servlet; | ||
import org.apache.commons.lang3.StringUtils; | ||
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.server.handler.ErrorHandler; | ||
import org.eclipse.jetty.server.handler.HandlerCollection; | ||
import org.eclipse.jetty.servlet.DefaultServlet; | ||
import org.eclipse.jetty.servlet.FilterHolder; | ||
import org.eclipse.jetty.servlet.ServletContextHandler; | ||
import org.eclipse.jetty.servlet.ServletHolder; | ||
import org.eclipse.jetty.util.component.LifeCycle; | ||
import org.eclipse.jetty.util.ssl.SslContextFactory; | ||
import org.eclipse.jetty.util.thread.QueuedThreadPool; | ||
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler; | ||
import org.eclipse.jetty.util.thread.Scheduler; | ||
|
@@ -44,6 +51,9 @@ public final class JettyServer { | |
|
||
private static final Logger LOG = LoggerFactory.getLogger(JettyServer.class); | ||
|
||
private static final String HTTPS = "https"; | ||
private static final String HTTP_PROTOCOL = "http/1.1"; | ||
|
||
private Server server; | ||
|
||
private ServletContextHandler servletContextHandler; | ||
|
@@ -76,18 +86,56 @@ public synchronized void initialize( | |
errorHandler.setServer(server); | ||
server.addBean(errorHandler); | ||
|
||
// Create and set Http ServerConnector | ||
ServerConnector httpConnector = | ||
createHttpServerConnector( | ||
server, | ||
serverConfig.getRequestHeaderSize(), | ||
serverConfig.getResponseHeaderSize(), | ||
serverConfig.getHost(), | ||
serverConfig.getHttpPort(), | ||
serverConfig.getIdleTimeout()); | ||
server.addConnector(httpConnector); | ||
|
||
// TODO. Create and set https connector @jerry | ||
if (serverConfig.isEnableHttps()) { | ||
// Create and set Https ServerConnector | ||
Preconditions.checkArgument( | ||
StringUtils.isNotBlank(serverConfig.getKeyStorePath()), | ||
"If enables https, must set keyStorePath"); | ||
Preconditions.checkArgument( | ||
StringUtils.isNotBlank(serverConfig.getKeyStorePassword()), | ||
"If enables https, must set keyStorePassword"); | ||
Preconditions.checkArgument( | ||
StringUtils.isNotBlank(serverConfig.getManagerPassword()), | ||
"If enables https, must set managerPassword"); | ||
if (serverConfig.isEnableClientAuth()) { | ||
Preconditions.checkArgument( | ||
StringUtils.isNotBlank(serverConfig.getTrustStorePath()), | ||
"If enables the authentication of the client, must set trustStorePath"); | ||
Preconditions.checkArgument( | ||
StringUtils.isNotBlank(serverConfig.getTrustStorePasword()), | ||
"If enables the authentication of the client, must set trustStorePassword"); | ||
} | ||
ServerConnector httpsConnector = | ||
createHttpsServerConnector( | ||
server, | ||
serverConfig.getRequestHeaderSize(), | ||
serverConfig.getResponseHeaderSize(), | ||
serverConfig.getHost(), | ||
serverConfig.getHttpsPort(), | ||
serverConfig.getIdleTimeout(), | ||
serverConfig.getKeyStorePath(), | ||
serverConfig.getKeyStorePassword(), | ||
serverConfig.getManagerPassword(), | ||
serverConfig.getKeyStoreType(), | ||
serverConfig.getTlsProtocol(), | ||
serverConfig.getSupportedAlgorithms(), | ||
serverConfig.isEnableClientAuth(), | ||
serverConfig.getTrustStorePath(), | ||
serverConfig.getTrustStorePasword(), | ||
serverConfig.getTrustStoreType()); | ||
server.addConnector(httpsConnector); | ||
} else { | ||
// Create and set Http ServerConnector | ||
ServerConnector httpConnector = | ||
createHttpServerConnector( | ||
server, | ||
serverConfig.getRequestHeaderSize(), | ||
serverConfig.getResponseHeaderSize(), | ||
serverConfig.getHost(), | ||
serverConfig.getHttpPort(), | ||
serverConfig.getIdleTimeout()); | ||
server.addConnector(httpConnector); | ||
} | ||
|
||
// Initialize ServletContextHandler or WebAppContext | ||
if (shouldEnableUI) { | ||
|
@@ -127,11 +175,12 @@ public synchronized void start() throws RuntimeException { | |
throw new RuntimeException("Failed to start " + serverName + " web server.", e); | ||
} | ||
|
||
if (!serverConfig.isEnableHttps()) { | ||
LOG.warn("Users would better use HTTPS to void token data leak."); | ||
} | ||
|
||
LOG.info( | ||
"{} web server started on host {} port {}.", | ||
serverName, | ||
serverConfig.getHost(), | ||
serverConfig.getHttpPort()); | ||
"{} web server started on host {} port {}.", serverName, serverConfig.getHost(), getPort()); | ||
} | ||
|
||
public synchronized void join() { | ||
|
@@ -162,7 +211,7 @@ public synchronized void stop() { | |
"{} web server stopped on host {} port {}.", | ||
serverName, | ||
serverConfig.getHost(), | ||
serverConfig.getHttpPort()); | ||
getPort()); | ||
} catch (Exception e) { | ||
// Swallow the exception. | ||
LOG.warn("Failed to stop {} web server.", serverName, e); | ||
|
@@ -260,15 +309,77 @@ private ServerConnector createHttpServerConnector( | |
|
||
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig); | ||
ServerConnector connector = | ||
creatorServerConnector(server, new ConnectionFactory[] {httpConnectionFactory}); | ||
createServerConnector(server, new ConnectionFactory[] {httpConnectionFactory}); | ||
connector.setHost(host); | ||
connector.setPort(port); | ||
connector.setReuseAddress(true); | ||
|
||
return connector; | ||
} | ||
|
||
private ServerConnector creatorServerConnector( | ||
private int getPort() { | ||
if (serverConfig.isEnableHttps()) { | ||
return serverConfig.getHttpsPort(); | ||
} else { | ||
return serverConfig.getHttpPort(); | ||
} | ||
} | ||
|
||
private ServerConnector createHttpsServerConnector( | ||
Server server, | ||
int reqHeaderSize, | ||
int respHeaderSize, | ||
String host, | ||
int port, | ||
int idleTimeout, | ||
String keyStorePath, | ||
String keyStorePassword, | ||
String keyManagerPassword, | ||
String keyStoreType, | ||
Optional<String> tlsProtocol, | ||
Set<String> supportedAlgorithms, | ||
boolean isEnableClientAuth, | ||
String trustStorePath, | ||
String trustStorePassword, | ||
String trustStoreType) { | ||
HttpConfiguration httpConfig = new HttpConfiguration(); | ||
httpConfig.setSecureScheme(HTTPS); | ||
httpConfig.setRequestHeaderSize(reqHeaderSize); | ||
httpConfig.setResponseHeaderSize(respHeaderSize); | ||
httpConfig.setSendServerVersion(true); | ||
httpConfig.setIdleTimeout(idleTimeout); | ||
httpConfig.setSecurePort(port); | ||
|
||
SslContextFactory sslContextFactory = new SslContextFactory.Server(); | ||
sslContextFactory.setKeyStorePath(keyStorePath); | ||
sslContextFactory.setKeyStorePassword(keyStorePassword); | ||
sslContextFactory.setKeyManagerPassword(keyManagerPassword); | ||
sslContextFactory.setKeyStoreType(keyStoreType); | ||
tlsProtocol.ifPresent(sslContextFactory::setProtocol); | ||
if (!supportedAlgorithms.isEmpty()) { | ||
sslContextFactory.setIncludeCipherSuites(supportedAlgorithms.toArray(new String[0])); | ||
} | ||
if (isEnableClientAuth) { | ||
sslContextFactory.setNeedClientAuth(true); | ||
jerryshao marked this conversation as resolved.
Show resolved
Hide resolved
|
||
sslContextFactory.setTrustStorePath(trustStorePath); | ||
sslContextFactory.setTrustStorePassword(trustStorePassword); | ||
sslContextFactory.setTrustStoreType(trustStoreType); | ||
} | ||
SecureRequestCustomizer src = new SecureRequestCustomizer(); | ||
httpConfig.addCustomizer(src); | ||
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpConfig); | ||
SslConnectionFactory sslConnectionFactory = | ||
new SslConnectionFactory(sslContextFactory, HTTP_PROTOCOL); | ||
ServerConnector connector = | ||
createServerConnector( | ||
server, new ConnectionFactory[] {sslConnectionFactory, httpConnectionFactory}); | ||
connector.setHost(host); | ||
connector.setPort(port); | ||
connector.setReuseAddress(true); | ||
return connector; | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How to handle exceptions on https initialize. Would it be easy for the user to understand if the original exception is thrown? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What exception should we handle? |
||
private ServerConnector createServerConnector( | ||
Server server, ConnectionFactory[] connectionFactories) { | ||
Scheduler serverExecutor = | ||
new ScheduledExecutorScheduler(serverName + "-webserver-JettyScheduler", true); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Gravitino server and Iceberg REST server have it's configuration doc, I wonder whether it's proper to put security configuration here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's ok for us not to add these configuration options in those documents. Spark also follows this document style.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should add more doc example about how to configure https.