From 132dcaf425bc2ce6f13b0725f11a9dd99a08eccb Mon Sep 17 00:00:00 2001 From: ychacon Date: Wed, 13 Jan 2021 09:20:54 +0100 Subject: [PATCH] HTTPS support for rApp Catalogue Issue-ID: NONRTRIC-375 Change-Id: Iaef7c5a1b55767fe96e884f2bd1c245b93e7d85d Signed-off-by: ychacon --- docker-compose/rapp/docker-compose.yaml | 4 +- r-app-catalogue/Dockerfile | 4 +- r-app-catalogue/config/application.yaml | 28 ++++- .../config/r-app-catalogue-keystore.jks | Bin 0 -> 3641 bytes r-app-catalogue/pom.xml | 11 ++ .../rappcatalogue/configuration/TomcatConfig.java | 56 ++++++++++ .../org/oransc/rappcatalogue/HttpsRequestTest.java | 113 +++++++++++++++++++++ 7 files changed, 212 insertions(+), 4 deletions(-) create mode 100644 r-app-catalogue/config/r-app-catalogue-keystore.jks create mode 100644 r-app-catalogue/src/main/java/org/oransc/rappcatalogue/configuration/TomcatConfig.java create mode 100644 r-app-catalogue/src/test/java/org/oransc/rappcatalogue/HttpsRequestTest.java diff --git a/docker-compose/rapp/docker-compose.yaml b/docker-compose/rapp/docker-compose.yaml index a7f6f431..ade37f71 100644 --- a/docker-compose/rapp/docker-compose.yaml +++ b/docker-compose/rapp/docker-compose.yaml @@ -29,6 +29,6 @@ services: aliases: - r-app-catalogue ports: - - 8080:8080 - - 8433:8433 + - 8680:8680 + - 8633:8633 diff --git a/r-app-catalogue/Dockerfile b/r-app-catalogue/Dockerfile index a85f57d6..cd2efc9b 100644 --- a/r-app-catalogue/Dockerfile +++ b/r-app-catalogue/Dockerfile @@ -23,10 +23,12 @@ ARG JAR WORKDIR /opt/app/r-app-catalogue RUN mkdir -p /var/log/r-app-catalogue +RUN mkdir -p /opt/app/r-app-catalogue/etc/cert/ -EXPOSE 8081 8433 +EXPOSE 8680 8633 ADD /config/application.yaml /opt/app/r-app-catalogue/config/application.yaml +ADD /config/r-app-catalogue-keystore.jks /opt/app/r-app-catalogue/etc/cert/keystore.jks ADD target/${JAR} /opt/app/r-app-catalogue/r-app-catalogue.jar diff --git a/r-app-catalogue/config/application.yaml b/r-app-catalogue/config/application.yaml index fadf7d24..1ef0bdcc 100644 --- a/r-app-catalogue/config/application.yaml +++ b/r-app-catalogue/config/application.yaml @@ -1,4 +1,30 @@ + # ========================LICENSE_START================================= + # Copyright (C) 2021 Nordix Foundation. 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. + # ========================LICENSE_END=================================== + spring: profiles: active: prod - +server: + # Configuration of the HTTP/REST server. The parameters are defined and handled by the springboot framework. + # See springboot documentation. + port : 8633 + http-port: 8680 + ssl: + key-store-type: JKS + key-store: /opt/app/r-app-catalogue/etc/cert/keystore.jks + key-store-password: r-app-catalogue + key-password: r-app-catalogue + key-alias: server-cert diff --git a/r-app-catalogue/config/r-app-catalogue-keystore.jks b/r-app-catalogue/config/r-app-catalogue-keystore.jks new file mode 100644 index 0000000000000000000000000000000000000000..192fe173951fcb9e958b3996f5270c01cceab681 GIT binary patch literal 3641 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 test + + org.springframework.boot + spring-boot-starter-test + test + + + org.apache.httpcomponents + httpclient + test + + diff --git a/r-app-catalogue/src/main/java/org/oransc/rappcatalogue/configuration/TomcatConfig.java b/r-app-catalogue/src/main/java/org/oransc/rappcatalogue/configuration/TomcatConfig.java new file mode 100644 index 00000000..a04a3323 --- /dev/null +++ b/r-app-catalogue/src/main/java/org/oransc/rappcatalogue/configuration/TomcatConfig.java @@ -0,0 +1,56 @@ +/*- + * ========================LICENSE_START================================= + * Copyright (C) 2021 Nordix Foundation. 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. + * ========================LICENSE_END=================================== + */ + +package org.oransc.rappcatalogue.configuration; + +import org.apache.catalina.connector.Connector; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory; +import org.springframework.boot.web.servlet.server.ServletWebServerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +/** + * Configure embedded Tomcat + */ + +@Configuration +public class TomcatConfig { + + @Value("${server.http-port}") + private int httpPort = 0; + + // Embedded Tomcat with HTTP and HTTPS support + @Bean + public ServletWebServerFactory servletContainer() { + TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory(); + + if (httpPort > 0) { + tomcat.addAdditionalTomcatConnectors(getHttpConnector(httpPort)); + } + return tomcat; + } + + private static Connector getHttpConnector(int httpPort) { + Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL); + connector.setScheme("http"); + connector.setPort(httpPort); + connector.setSecure(false); + return connector; + } +} diff --git a/r-app-catalogue/src/test/java/org/oransc/rappcatalogue/HttpsRequestTest.java b/r-app-catalogue/src/test/java/org/oransc/rappcatalogue/HttpsRequestTest.java new file mode 100644 index 00000000..3cf2c2e7 --- /dev/null +++ b/r-app-catalogue/src/test/java/org/oransc/rappcatalogue/HttpsRequestTest.java @@ -0,0 +1,113 @@ +/*- + * ========================LICENSE_START================================= + * Copyright (C) 2021 Nordix Foundation. 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. + * ========================LICENSE_END=================================== + */ + +package org.oransc.rappcatalogue; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import javax.net.ssl.SSLContext; + +import org.apache.http.client.HttpClient; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.context.SpringBootTest.WebEnvironment; +import org.springframework.boot.test.web.client.TestRestTemplate; +import org.springframework.boot.test.web.client.TestRestTemplate.HttpClientOption; +import org.springframework.boot.web.client.RestTemplateBuilder; +import org.springframework.boot.web.server.AbstractConfigurableWebServerFactory; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.util.ResourceUtils; +import org.springframework.web.client.ResourceAccessException; + +@RunWith(SpringRunner.class) +@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT) +@TestPropertySource( + properties = { // + "server.ssl.key-store=./config/r-app-catalogue-keystore.jks", // + "server.http-port=0"}) +public class HttpsRequestTest { + + @Value("${server.ssl.key-store-password}") + private String keyStorePassword; // inject password from config + + @Value("${server.ssl.key-store}") + private String keyStore; // inject keyStore from config + + @LocalServerPort + private int port; + + @Autowired + private AbstractConfigurableWebServerFactory webServerFactory; + + @Test + public void testSsl() { + assertEquals(this.webServerFactory.getSsl().isEnabled(), true); + } + + @Test + public void rest_OverPlainHttp_GetsBadRequestRequiresTLS() throws Exception { + TestRestTemplate template = new TestRestTemplate(); + ResponseEntity responseEntity = + template.getForEntity("http://localhost:" + port + "/services", String.class); + assertEquals(HttpStatus.BAD_REQUEST, responseEntity.getStatusCode()); + assertTrue(responseEntity.getBody().contains("This combination of host and port requires TLS")); + } + + @Test + public void rest_WithoutSSLConfiguration_ThrowsSSLExceptionUnableFindValidCertPath() throws Exception { + TestRestTemplate template = new TestRestTemplate(); + + ResourceAccessException thrown = assertThrows(ResourceAccessException.class, () -> { + template.getForEntity("https://localhost:" + port + "/services", String.class); + }); + assertTrue(thrown.getMessage().contains("unable to find valid certification path to requested target")); + } + + @Test + public void rest_WithTwoWaySSL_AuthenticatesAndGetsExpectedResponse() throws Exception { + + SSLContext sslContext = new SSLContextBuilder().loadKeyMaterial(ResourceUtils.getFile(keyStore), + keyStorePassword.toCharArray(), keyStorePassword.toCharArray()).build(); + + SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(sslContext); + HttpClient httpClient = HttpClients.custom().setSSLSocketFactory(socketFactory).build(); + HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient); + RestTemplateBuilder rtb = + new RestTemplateBuilder().requestFactory(() -> factory).rootUri("https://localhost:" + port); + + TestRestTemplate template = new TestRestTemplate(rtb, null, null, HttpClientOption.SSL); + + ResponseEntity responseEntity = template.getForEntity("/services", String.class); + assertEquals(HttpStatus.OK, responseEntity.getStatusCode()); + assertEquals("[]", responseEntity.getBody()); + } + +} -- 2.16.6