Updated SSL Configuration for WebServer (#1852)

* Improvements for configuration of TLS.
* TLS Example for webserver.

Signed-off-by: Tomas Langer <tomas.langer@oracle.com>
This commit is contained in:
Tomas Langer
2020-05-26 01:00:33 +02:00
committed by GitHub
parent 9d9db4e5f0
commit 1fc12f4c11
11 changed files with 448 additions and 1 deletions

View File

@@ -398,6 +398,16 @@ public final class KeyConfig {
return this;
}
/**
* Pass-phrase of the keystore (supported with JKS and PKCS12 keystores).
*
* @param keystorePassword keystore password to use, calls {@link #keystorePassphrase(char[])}
* @return updated builder instance
*/
public KeystoreBuilder keystorePassphrase(String keystorePassword) {
return keystorePassphrase(keystorePassword.toCharArray());
}
/**
* Alias of the private key in the keystore.
*
@@ -446,6 +456,18 @@ public final class KeyConfig {
return this;
}
/**
* Pass-phrase of the key in the keystore (used for private keys).
* This is (by default) the same as keystore passphrase - only configure
* if it differs from keystore passphrase.
*
* @param privateKeyPassphrase pass-phrase of the key
* @return updated builder instance
*/
public KeystoreBuilder keyPassphrase(String privateKeyPassphrase) {
return keyPassphrase(privateKeyPassphrase.toCharArray());
}
/**
* Create an instance of {@link KeyConfig} based on this builder.
*
@@ -654,6 +676,17 @@ public final class KeyConfig {
return this;
}
/**
* Passphrase for private key. If the key is encrypted (and in PEM PKCS#8 format), this passphrase will be used to
* decrypt it.
*
* @param passphrase passphrase used to encrypt the private key
* @return updated builder instance
*/
public PemBuilder keyPassphrase(String passphrase) {
return keyPassphrase(passphrase.toCharArray());
}
/**
* Load certificate chain from PEM resource.
*

View File

@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2017, 2020 Oracle and/or its affiliates. All rights reserved.
Copyright (c) 2017, 2020 Oracle and/or its affiliates.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -40,5 +40,6 @@
<module>opentracing</module>
<module>streaming</module>
<module>websocket</module>
<module>tls</module>
</modules>
</project>

View File

@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright (c) 2020 Oracle and/or its affiliates.
~
~ 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.
-->
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>io.helidon.applications</groupId>
<artifactId>helidon-se</artifactId>
<version>2.0.0-SNAPSHOT</version>
<relativePath>../../../applications/se/pom.xml</relativePath>
</parent>
<groupId>io.helidon.examples.webserver</groupId>
<artifactId>helidon-examples-webserver-tls</artifactId>
<name>Helidon WebServer Examples TLS</name>
<description>
Application demonstrates TLS configuration using a builder
and config.
</description>
<properties>
<mainClass>io.helidon.webserver.examples.tls.Main</mainClass>
</properties>
<dependencies>
<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.config</groupId>
<artifactId>helidon-config-yaml</artifactId>
</dependency>
<dependency>
<groupId>io.helidon.webclient</groupId>
<artifactId>helidon-webclient</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.webserver</groupId>
<artifactId>helidon-webserver-test-support</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.helidon.webclient</groupId>
<artifactId>helidon-webclient</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-libs</id>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,94 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
*
* 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 io.helidon.webserver.examples.tls;
import java.io.IOException;
import java.io.InputStream;
import java.util.concurrent.CompletionStage;
import java.util.logging.LogManager;
import io.helidon.common.configurable.Resource;
import io.helidon.common.pki.KeyConfig;
import io.helidon.config.Config;
import io.helidon.webserver.Routing;
import io.helidon.webserver.TlsConfig;
import io.helidon.webserver.WebServer;
/**
* Main class of TLS example.
*/
public final class Main {
// utility class
private Main() {
}
/**
* Start the example.
* This will start two Helidon WebServers, both protected by TLS - one configured from config, one using a builder.
* Port of the servers will be configured from config, to be able to switch to an ephemeral port for tests.
*
* @param args start arguments are ignored
*/
public static void main(String[] args) throws IOException {
setupLogging();
Config config = Config.create();
startConfigBasedServer(config.get("config-based"))
.thenAccept(ws -> {
System.out.println("Started config based WebServer on http://localhost:" + ws.port());
});
startBuilderBasedServer(config.get("builder-based"))
.thenAccept(ws -> {
System.out.println("Started builder based WebServer on http://localhost:" + ws.port());
});
}
static CompletionStage<WebServer> startBuilderBasedServer(Config config) {
return WebServer.builder()
.config(config)
.routing(routing())
// now let's configure TLS
.tls(TlsConfig.builder()
.privateKey(KeyConfig.keystoreBuilder()
.keystore(Resource.create("certificate.p12"))
.keystorePassphrase("helidon")))
.build()
.start();
}
static CompletionStage<WebServer> startConfigBasedServer(Config config) {
return WebServer.builder()
.config(config)
.routing(routing())
.build()
.start();
}
private static Routing routing() {
return Routing.builder()
.get("/", (req, res) -> res.send("Hello!"))
.build();
}
/**
* Configure logging from logging.properties file.
*/
private static void setupLogging() throws IOException {
try (InputStream is = Main.class.getResourceAsStream("/logging.properties")) {
LogManager.getLogManager().readConfiguration(is);
}
}
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
*
* 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.
*/
/**
* Example of TLS configuration for webserver, using both {@link io.helidon.config.Config} and builder based approach.
*/
package io.helidon.webserver.examples.tls;

View File

@@ -0,0 +1,25 @@
#
# Copyright (c) 2020 Oracle and/or its affiliates.
#
# 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.
#
config-based:
port: 8080
ssl:
private-key.keystore:
resource.resource-path: "certificate.p12"
passphrase: "helidon"
builder-based:
port: 8081

View File

@@ -0,0 +1,20 @@
#
# Copyright (c) 2020 Oracle and/or its affiliates. All rights reserved.
#
# 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.
#
handlers=io.helidon.common.HelidonConsoleHandler
java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n
.level=INFO
io.helidon.microprofile.server.level=INFO

View File

@@ -0,0 +1,115 @@
/*
* Copyright (c) 2020 Oracle and/or its affiliates.
*
* 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 io.helidon.webserver.examples.tls;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.stream.Stream;
import io.helidon.config.Config;
import io.helidon.config.ConfigSources;
import io.helidon.webclient.Ssl;
import io.helidon.webclient.WebClient;
import io.helidon.webserver.WebServer;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
class MainTest {
private static WebServer configBasedServer;
private static WebServer builderBasedServer;
private static WebClient configBasedClient;
private static WebClient builderBasedClient;
@BeforeAll
static void initClass() throws ExecutionException, InterruptedException {
Config config = Config.create(ConfigSources.classpath("test-application.yaml"),
ConfigSources.classpath("application.yaml"));
configBasedServer = Main.startConfigBasedServer(config.get("config-based"))
.toCompletableFuture()
.get();
builderBasedServer = Main.startBuilderBasedServer(config.get("builder-based"))
.toCompletableFuture()
.get();
configBasedClient = WebClient.builder()
.baseUri("https://localhost:" + configBasedServer.port())
// trust all, as we use a self-signed certificate
.ssl(Ssl.builder().trustAll(true).build())
.build();
builderBasedClient = WebClient.builder()
.baseUri("https://localhost:" + builderBasedServer.port())
// trust all, as we use a self-signed certificate
.ssl(Ssl.builder().trustAll(true).build())
.build();
}
@AfterAll
static void destroyClass() {
CompletionStage<WebServer> configBased;
CompletionStage<WebServer> builderBased;
if (null == configBasedServer) {
configBased = CompletableFuture.completedFuture(null);
} else {
configBased = configBasedServer.shutdown();
}
if (null == builderBasedServer) {
builderBased = CompletableFuture.completedFuture(null);
} else {
builderBased = builderBasedServer.shutdown();
}
configBased.toCompletableFuture().join();
builderBased.toCompletableFuture().join();
}
static Stream<TestData> testDataSource() {
return Stream.of(new TestData("Builder based", builderBasedClient),
new TestData("Config based", configBasedClient));
}
@ParameterizedTest
@MethodSource("testDataSource")
void testSsl(TestData testData) {
String response = testData.client
.get()
.request(String.class)
.await();
assertThat(testData.type + " SSL server response.", response, is("Hello!"));
}
private static class TestData {
private final String type;
private final WebClient client;
private TestData(String type, WebClient client) {
this.type = type;
this.client = client;
}
}
}

View File

@@ -0,0 +1,22 @@
#
# Copyright (c) 2020 Oracle and/or its affiliates.
#
# 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.
#
config-based:
# switch to available ephemeral port for tests
port: 0
builder-based:
port: 0

View File

@@ -32,6 +32,7 @@ import java.util.Objects;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
@@ -219,6 +220,16 @@ public final class TlsConfig {
return this;
}
/**
* Configure private key to use for SSL context.
*
* @param privateKeyConfigBuilder the required private key configuration parameter
* @return this builder
*/
public Builder privateKey(Supplier<KeyConfig> privateKeyConfigBuilder) {
return privateKey(privateKeyConfigBuilder.get());
}
/**
* Set the trust key configuration to be used to validate certificates.
*
@@ -233,6 +244,16 @@ public final class TlsConfig {
return this;
}
/**
* Set the trust key configuration to be used to validate certificates.
*
* @param trustConfigBuilder the trust configuration builder
* @return this builder
*/
public Builder trust(Supplier<KeyConfig> trustConfigBuilder) {
return trust(trustConfigBuilder.get());
}
/**
* Set the size of the cache used for storing SSL session objects. {@code 0} to use the
* default value.