diff options
Diffstat (limited to 'gui-server/src/test')
17 files changed, 1055 insertions, 0 deletions
diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/SpringContextTest.java b/gui-server/src/test/java/org/onap/policy/gui/server/SpringContextTest.java new file mode 100644 index 0000000..7be7694 --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/SpringContextTest.java @@ -0,0 +1,37 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest( + properties = { + "clamp.url=https://clamp-backend:8443/", + "clamp.disable-ssl-validation=true" + }) +class SpringContextTest { + + @Test + @SuppressWarnings("java:S2699") + void whenSpringContextIsBootstrapped_thenNoExceptions() { + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig1Test.java b/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig1Test.java new file mode 100644 index 0000000..44e4c46 --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig1Test.java @@ -0,0 +1,69 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.config; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import javax.net.ssl.SSLPeerUnverifiedException; +import org.junit.jupiter.api.Test; +import org.onap.policy.gui.server.test.util.hello.HelloWorldApplication; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +/** + * In this test, SSL validation and hostname check are enabled. + * Since our keystore cert has a hostname 'helloworld' and our test request is + * to localhost, the request will fail with an SSLPeerUnverifiedException, as + * the SSL cert name does not match the server name 'localhost'. + */ +@SpringBootTest( + classes = { HelloWorldApplication.class, ClampRestTemplateConfig.class }, + properties = { + "server.ssl.key-store=file:src/test/resources/helloworld-keystore.jks", + "server.ssl.key-store-password=changeit", + "server.ssl.trust-store=file:src/test/resources/helloworld-truststore.jks", + "server.ssl.trust-store-password=changeit", + "clamp.disable-ssl-validation=false", + "clamp.disable-ssl-hostname-check=false" + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ClampRestTemplateConfig1Test { + + @LocalServerPort + private int port; + + @Autowired + @Qualifier("clampRestTemplate") + private RestTemplate restTemplate; + + @Test + void testRequestFailsWhenSslHostnameCheckIsEnabled() { + var helloUrl = "https://localhost:" + port + "/"; + Exception e = assertThrows(RestClientException.class, + () -> restTemplate.getForEntity(helloUrl, String.class)); + assertTrue(e.getCause() instanceof SSLPeerUnverifiedException); + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig2Test.java b/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig2Test.java new file mode 100644 index 0000000..b8e744c --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig2Test.java @@ -0,0 +1,61 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.onap.policy.gui.server.test.util.hello.HelloWorldRestController.HELLO_WORLD_STRING; + +import org.junit.jupiter.api.Test; +import org.onap.policy.gui.server.test.util.hello.HelloWorldApplication; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.web.client.RestTemplate; + +/** + * In this test, SSL validation is disabled. + * The test request should succeed. A trust store has not been supplied in this case. + */ +@SpringBootTest( + classes = { HelloWorldApplication.class, ClampRestTemplateConfig.class }, + properties = { + "server.ssl.key-store=file:src/test/resources/helloworld-keystore.jks", + "server.ssl.key-store-password=changeit", + "clamp.disable-ssl-validation=true" + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ClampRestTemplateConfig2Test { + + @LocalServerPort + private int port; + + @Autowired + @Qualifier("clampRestTemplate") + private RestTemplate restTemplate; + + @Test + void testRequestSucceedsWhenSslValidationIsDisabled() { + var helloUrl = "https://localhost:" + port + "/"; + String response = restTemplate.getForObject(helloUrl, String.class); + assertEquals(HELLO_WORLD_STRING, response); + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig3Test.java b/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig3Test.java new file mode 100644 index 0000000..4636982 --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig3Test.java @@ -0,0 +1,70 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.onap.policy.gui.server.test.util.hello.HelloWorldRestController.HELLO_WORLD_STRING; + +import org.junit.jupiter.api.Test; +import org.onap.policy.gui.server.test.util.hello.HelloWorldApplication; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.web.client.RestTemplate; + +/** + * In this test, SSL validation is enabled but hostname check is disabled. + * Even though our keystore cert has a hostname 'helloworld' and our test + * request is to localhost, the request will succeed as the SSL hostname check + * is disabled. + */ +@SpringBootTest( + classes = { HelloWorldApplication.class, ClampRestTemplateConfig.class }, + properties = { + "server.ssl.key-store=file:src/test/resources/helloworld-keystore.jks", + "server.ssl.key-store-password=changeit", + "server.ssl.trust-store=file:src/test/resources/helloworld-truststore.jks", + "server.ssl.trust-store-password=changeit", + "clamp.disable-ssl-validation=false", + "clamp.disable-ssl-hostname-check=true" + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ClampRestTemplateConfig3Test { + + @LocalServerPort + private int port; + + @Autowired + @Qualifier("clampRestTemplate") + private RestTemplate restTemplate; + + /* + * In this test, the request will succeed even though the SSL cert name + * does not match 'localhost', as SSL hostname verification is disabled. + */ + @Test + void testRequestSucceedsWhenSslHostnameCheckIsDisabled() { + var helloUrl = "https://localhost:" + port + "/"; + String response = restTemplate.getForObject(helloUrl, String.class); + assertEquals(HELLO_WORLD_STRING, response); + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig4Test.java b/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig4Test.java new file mode 100644 index 0000000..f0f222f --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig4Test.java @@ -0,0 +1,67 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.config; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.onap.policy.gui.server.test.util.hello.HelloWorldRestController.HELLO_WORLD_STRING; + +import org.junit.jupiter.api.Test; +import org.onap.policy.gui.server.test.util.hello.HelloWorldApplication; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.web.client.RestTemplate; + +/** + * In this test, SSL validation is disabled but hostname check is explicitly + * enabled. The expected behaviour is to disable the hostname check if SSL + * validation is disabled. We expect the request to succeed even though the + * SSL cert name does not match 'localhost', as SSL hostname verification is + * implicitly disabled. + */ +@SpringBootTest( + classes = { HelloWorldApplication.class, ClampRestTemplateConfig.class }, + properties = { + "server.ssl.key-store=file:src/test/resources/helloworld-keystore.jks", + "server.ssl.key-store-password=changeit", + "server.ssl.trust-store=file:src/test/resources/helloworld-truststore.jks", + "server.ssl.trust-store-password=changeit", + "clamp.disable-ssl-validation=true", + "clamp.disable-ssl-hostname-check=false" + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ClampRestTemplateConfig4Test { + + @LocalServerPort + private int port; + + @Autowired + @Qualifier("clampRestTemplate") + private RestTemplate restTemplate; + + @Test + void testHostnameCheckIsDisabledWhenSslValidationIsDisabled() { + var helloUrl = "https://localhost:" + port + "/"; + String response = restTemplate.getForObject(helloUrl, String.class); + assertEquals(HELLO_WORLD_STRING, response); + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig5Test.java b/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig5Test.java new file mode 100644 index 0000000..cc23de5 --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig5Test.java @@ -0,0 +1,69 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.config; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import javax.net.ssl.SSLPeerUnverifiedException; +import org.junit.jupiter.api.Test; +import org.onap.policy.gui.server.test.util.hello.HelloWorldApplication; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.web.server.LocalServerPort; +import org.springframework.web.client.RestClientException; +import org.springframework.web.client.RestTemplate; + +/** + * In this test, we verify that SSL validation and hostname check are enabled + * by default. Thus we do not explicitly set the Spring properties + * clamp.disable-ssl-validation and clamp.disable-ssl-hostname-check. + * Since our keystore cert has a hostname 'helloworld' and our test request is + * to localhost, the request will fail with an SSLPeerUnverifiedException, as + * the SSL cert name does not match the server name 'localhost'. + */ +@SpringBootTest( + classes = { HelloWorldApplication.class, ClampRestTemplateConfig.class }, + properties = { + "server.ssl.key-store=file:src/test/resources/helloworld-keystore.jks", + "server.ssl.key-store-password=changeit", + "server.ssl.trust-store=file:src/test/resources/helloworld-truststore.jks", + "server.ssl.trust-store-password=changeit", + }, + webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +class ClampRestTemplateConfig5Test { + + @LocalServerPort + private int port; + + @Autowired + @Qualifier("clampRestTemplate") + private RestTemplate restTemplate; + + @Test + void testSslValidationIsEnabledByDefault() { + var helloUrl = "https://localhost:" + port + "/"; + Exception e = assertThrows(RestClientException.class, + () -> restTemplate.getForEntity(helloUrl, String.class)); + assertTrue(e.getCause() instanceof SSLPeerUnverifiedException); + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilterTest.java b/gui-server/src/test/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilterTest.java new file mode 100644 index 0000000..5fc026d --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilterTest.java @@ -0,0 +1,211 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.filters; + +import static org.apache.commons.collections4.CollectionUtils.isEqualCollection; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.mock; +import static org.onap.policy.gui.server.filters.ClientSslHeaderFilter.SSL_CERT_HEADER_NAME; +import static org.onap.policy.gui.server.filters.ClientSslHeaderFilter.X509_ATTRIBUTE_NAME; +import static org.onap.policy.gui.server.util.X509CertificateEncoder.urlDecodeCert; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Enumeration; +import java.util.List; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.onap.policy.gui.server.test.util.KeyStoreHelper; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +class ClientSslHeaderFilterTest { + + /* + * If the client does not supply an SSL cert, the filter should not set + * the X-SSL-Cert header. + */ + @Test + void testNoClientCert_noHeader() throws ServletException, IOException { + // Create a request without client SSL cert. + HttpServletRequest inRequest = new MockHttpServletRequest(); + + // Apply the filter. + HttpServletRequest outRequest = applyRequestFilter(inRequest); + + // The modified request should not contain cert header. + assertFalse(containsCertHeader(outRequest.getHeaderNames())); + assertNull(outRequest.getHeader(SSL_CERT_HEADER_NAME)); + assertEquals(Collections.emptyEnumeration(), outRequest.getHeaders(SSL_CERT_HEADER_NAME)); + } + + /* + * If the client does supply an SSL cert, the filter should set the + * X-SSL-Cert header with the encoded SSL cert. + */ + @Test + void testValidClientCert_hasHeader() throws Exception { + // Load valid cert from key store. + X509Certificate validCert = KeyStoreHelper.loadValidCert(); + + // Create a request with a valid client SSL cert. + MockHttpServletRequest inRequest = new MockHttpServletRequest(); + inRequest.setAttribute(X509_ATTRIBUTE_NAME, new X509Certificate[] { validCert }); + + // Apply the filter. + HttpServletRequest outRequest = applyRequestFilter(inRequest); + + // The modified request should contain a cert header. + assertTrue(containsCertHeader(outRequest.getHeaderNames())); + + // Check if the cert header parses back to the original cert. + String headerValue = outRequest.getHeader(SSL_CERT_HEADER_NAME); + assertEquals(validCert, urlDecodeCert(headerValue)); + + // Verify the getHeaders method also returns cert. + assertEquals(headerValue, outRequest.getHeaders(SSL_CERT_HEADER_NAME).nextElement()); + } + + /* + * If the client supplies an expired SSL cert, the filter should not set + * the X-SSL-Cert header. + */ + @Test + void testExpiredClientCert_noHeader() throws Exception { + // Load expired cert from key store. + X509Certificate expiredCert = KeyStoreHelper.loadExpiredCert(); + + // Create a request with an expired client SSL cert. + MockHttpServletRequest inRequest = new MockHttpServletRequest(); + inRequest.setAttribute(X509_ATTRIBUTE_NAME, new X509Certificate[] { expiredCert }); + + // Apply the filter. + HttpServletRequest outRequest = applyRequestFilter(inRequest); + + // The modified request should not contain a cert header. + assertFalse(containsCertHeader(outRequest.getHeaderNames())); + assertNull(outRequest.getHeader(SSL_CERT_HEADER_NAME)); + assertEquals(Collections.emptyEnumeration(), outRequest.getHeaders(SSL_CERT_HEADER_NAME)); + } + + /* + * This test is needed to prevent a security vulnerability where a + * malicious user does not authenticate using client cert, but defines the + * X-SSL-Cert header themselves, thus gaining access without having the + * corresponding private key. + * We thus test that an incoming X-SSL-Cert header is sanitized. + */ + @Test + void existingCertHeaderIsSanitized() throws Exception { + // Create a request with X-SSL-Cert header predefined. + MockHttpServletRequest inRequest = new MockHttpServletRequest(); + inRequest.addHeader(SSL_CERT_HEADER_NAME, "somevalue"); + + // Apply the filter. + HttpServletRequest outRequest = applyRequestFilter(inRequest); + + // The modified request should not contain a cert header. + assertFalse(containsCertHeader(outRequest.getHeaderNames())); + assertNull(outRequest.getHeader(SSL_CERT_HEADER_NAME)); + assertEquals(Collections.emptyEnumeration(), outRequest.getHeaders(SSL_CERT_HEADER_NAME)); + } + + /* + * This test verifies that existing HTTP headers are preserved + * (including multi-value headers). + */ + @Test + void otherHeadersAreStillAccessible() throws Exception { + // Load valid cert from key store. + X509Certificate validCert = KeyStoreHelper.loadValidCert(); + + // Create a request with a valid client SSL cert and some existing headers. + MockHttpServletRequest inRequest = new MockHttpServletRequest(); + inRequest.setAttribute(X509_ATTRIBUTE_NAME, new X509Certificate[] { validCert }); + inRequest.addHeader("User-Agent", "Jupiter"); + inRequest.addHeader("Accept-Language", "en-US"); + inRequest.addHeader("Accept-Language", "en-IE"); + + // Apply the filter. + HttpServletRequest outRequest = applyRequestFilter(inRequest); + + // The modified request contains the new cert header and the existing headers. + assertTrue( + isEqualCollection( + List.of("Accept-Language", "User-Agent", SSL_CERT_HEADER_NAME), + Collections.list(outRequest.getHeaderNames()))); + + // Verify getHeader method returns correct value. + String userAgent = outRequest.getHeader("User-Agent"); + assertEquals("Jupiter", userAgent); + + // Verify getHeaders method returns correct values. + Enumeration<String> acceptLanguages = outRequest.getHeaders("Accept-Language"); + assertEquals("en-US", acceptLanguages.nextElement()); + assertEquals("en-IE", acceptLanguages.nextElement()); + assertFalse(acceptLanguages.hasMoreElements()); + } + + /** + * Apply the ClientSslToHeaderFilter to the input request, + * and return the modified request. + */ + private HttpServletRequest applyRequestFilter(HttpServletRequest request) throws ServletException, IOException { + HttpServletResponse response = new MockHttpServletResponse(); + + // The filter calls filterChain::doFilter after processing the request, + // so capture the HttpServletRequest argument from filterChain::doFilter. + FilterChain filterChain = mock(FilterChain.class); + ArgumentCaptor<HttpServletRequest> requestCaptor = ArgumentCaptor.forClass(HttpServletRequest.class); + doNothing().when(filterChain).doFilter(requestCaptor.capture(), eq(response)); + + // Apply the filter. + Filter filter = new ClientSslHeaderFilter(); + filter.doFilter(request, response, filterChain); + + // Return the modified HttpServletRequest. + return requestCaptor.getValue(); + } + + /** + * Check if an Enumeration of header names contains the certificate header. + * Note HTTP header names are case insensitive. + */ + private boolean containsCertHeader(Enumeration<String> headers) { + while (headers.hasMoreElements()) { + if (headers.nextElement().equalsIgnoreCase(SSL_CERT_HEADER_NAME)) { + return true; + } + } + return false; + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/rest/ApexEditorRestControllerTest.java b/gui-server/src/test/java/org/onap/policy/gui/server/rest/ApexEditorRestControllerTest.java new file mode 100644 index 0000000..4cfd994 --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/rest/ApexEditorRestControllerTest.java @@ -0,0 +1,61 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.rest; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.web.servlet.MockMvc; + +@SpringBootTest( + properties = { + "clamp.url=https://clamp-backend:8443/", + "clamp.disable-ssl-validation=true" + }) +@AutoConfigureMockMvc +class ApexEditorRestControllerTest { + + @Autowired + private MockMvc mvc; + + @Test + void testStaticContentUrls() throws Exception { + mvc.perform(get("/apex-editor/")) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/apex-editor/index.html")); + + mvc.perform(get("/apex-editor")) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/apex-editor/")); + } + + @Test + void testApexEditorRestForwarding() throws Exception { + mvc.perform(get("/apex-editor/policy/gui/v1/apex/editor/-1/Session/Create")) + .andExpect(forwardedUrl("/policy/gui/v1/apex/editor/-1/Session/Create")); + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/rest/ClampRestControllerTest.java b/gui-server/src/test/java/org/onap/policy/gui/server/rest/ClampRestControllerTest.java new file mode 100644 index 0000000..fb3e843 --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/rest/ClampRestControllerTest.java @@ -0,0 +1,161 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.rest; + +import static org.onap.policy.gui.server.filters.ClientSslHeaderFilter.SSL_CERT_HEADER_NAME; +import static org.onap.policy.gui.server.test.util.X509RequestPostProcessor.x509; +import static org.onap.policy.gui.server.util.X509CertificateEncoder.urlEncodeCert; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.header; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.method; +import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo; +import static org.springframework.test.web.client.response.MockRestResponseCreators.withStatus; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.security.cert.X509Certificate; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.onap.policy.gui.server.test.util.KeyStoreHelper; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpStatus; +import org.springframework.test.web.client.MockRestServiceServer; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.client.RestTemplate; + +@SpringBootTest( + properties = { + "clamp.url=https://clamp-backend:8443/", + "clamp.disable-ssl-validation=true" + }) +@AutoConfigureMockMvc +class ClampRestControllerTest { + + @Autowired + private MockMvc mvc; + + @Autowired + @Qualifier("clampRestTemplate") + private RestTemplate restTemplate; + + private MockRestServiceServer mockServer; + + @BeforeEach + public void init() { + mockServer = MockRestServiceServer.createServer(restTemplate); + } + + @Test + void testStaticContentUrls() throws Exception { + mvc.perform(get("/clamp/")) + .andExpect(status().isOk()) + .andExpect(forwardedUrl("/clamp/index.html")); + + mvc.perform(get("/clamp")) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/clamp/")); + } + + /* + * This is a happy path test to verify that calls to /clamp/restservices/** + * are relayed to the clamp backend, and that the backend receives the + * client certificate encoded in a header. More extensive tests of the + * certificate cert filter are in ClientSslHeaderFilterTest. + */ + @Test + void testClampProxyWithClientCert() throws Exception { + X509Certificate cert = KeyStoreHelper.loadValidCert(); + + mockServer.expect( + requestTo("https://clamp-backend:8443/restservices/junit/test")) + .andExpect(header(SSL_CERT_HEADER_NAME, urlEncodeCert(cert))) + .andRespond(withStatus(HttpStatus.OK).body("admin")); + + mvc.perform( + get("/clamp/restservices/junit/test") + .with(x509(cert))) + .andExpect(status().isOk()) + .andExpect(content().string("admin")); + + mockServer.verify(); + } + + /* + * This test verifies that HTTP headers are preserved for requests to the + * clamp backend (including multi-value headers). + */ + @Test + void verifyClampProxyPassesHeaders() throws Exception { + // Single value header + final String userAgent = "User-Agent"; + final String userAgentValue = "JUnit"; + // Multi value header + final String acceptLanguage = "Accept-Language"; + final String enUs = "en-US"; + final String enIe = "en-IE"; + + mockServer.expect( + requestTo("https://clamp-backend:8443/restservices/junit/test")) + .andExpect(method(HttpMethod.GET)) + .andExpect(header(userAgent, userAgentValue)) + .andExpect(header(acceptLanguage, enUs, enIe)) + .andRespond(withStatus(HttpStatus.OK)); + + HttpHeaders requestHeaders = new HttpHeaders(); + requestHeaders.set(userAgent, userAgentValue); + requestHeaders.add(acceptLanguage, enUs); + requestHeaders.add(acceptLanguage, enIe); + mvc.perform( + get("/clamp/restservices/junit/test") + .headers(requestHeaders)) + .andExpect(status().isOk()); + + mockServer.verify(); + } + + /* + * This test verifies that error messages from the clamp backend are + * delivered to the client (as opposed to 500 "Internal Server Error"). + */ + @Test + void verifyClampProxyReturnsBackendErrorCode() throws Exception { + final String errorMessage = "This appliance cannot brew coffee"; + + mockServer.expect( + requestTo("https://clamp-backend:8443/restservices/coffee")) + .andRespond(withStatus(HttpStatus.I_AM_A_TEAPOT).body(errorMessage)); + + mvc.perform( + post("/clamp/restservices/coffee")) + .andExpect(status().is(HttpStatus.I_AM_A_TEAPOT.value())) + .andExpect(content().string(errorMessage)); + + mockServer.verify(); + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/test/util/KeyStoreHelper.java b/gui-server/src/test/java/org/onap/policy/gui/server/test/util/KeyStoreHelper.java new file mode 100644 index 0000000..a4aabb8 --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/test/util/KeyStoreHelper.java @@ -0,0 +1,92 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.test.util; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.io.FileInputStream; +import java.security.KeyStore; +import java.security.cert.CertificateExpiredException; +import java.security.cert.X509Certificate; +import org.junit.jupiter.api.function.Executable; + +public class KeyStoreHelper { + /* + * The proxy test keystore contains certs with: + * - alias "valid": self-signed cert which expires circa year 2050 + * - alias "expired": self-signed cert which expired in year 2000 + */ + private static final String KEY_STORE_PATH = "src/test/resources/keystore-proxytest.jks"; + private static final String KEY_STORE_PASSWORD = "changeit"; + private static final String KEY_STORE_TYPE = "JKS"; + private static final String CERT_ALIAS_VALID = "valid"; + private static final String CERT_ALIAS_EXPIRED = "expired"; + + /** + * Load a valid certificate from the test keystore. + */ + public static X509Certificate loadValidCert() throws CouldNotLoadCertificateException { + X509Certificate cert = loadCertFromKeyStore(CERT_ALIAS_VALID); + assertDoesNotThrow((Executable) cert::checkValidity); + return cert; + } + + /** + * Load an expired certificate from the test keystore. + */ + public static X509Certificate loadExpiredCert() throws CouldNotLoadCertificateException { + X509Certificate cert = loadCertFromKeyStore(CERT_ALIAS_EXPIRED); + assertThrows(CertificateExpiredException.class, cert::checkValidity); + return cert; + } + + /** + * Load a certificate with given alias from the test keystore. + */ + private static X509Certificate loadCertFromKeyStore(String certAlias) throws CouldNotLoadCertificateException { + try { + KeyStore ks = KeyStore.getInstance(KEY_STORE_TYPE); + ks.load(new FileInputStream(KEY_STORE_PATH), KEY_STORE_PASSWORD.toCharArray()); + X509Certificate cert = (X509Certificate) ks.getCertificate(certAlias); + if (cert == null) { + throw new CouldNotLoadCertificateException("Alias does not exist or does not contain a certificate."); + } + return cert; + } catch (Exception e) { + throw new CouldNotLoadCertificateException( + "Could not load cert with alias '" + certAlias + "' from test keystore.", e); + } + } + + /** + * Exception class for KeyStoreHelper methods. + */ + public static class CouldNotLoadCertificateException extends java.lang.Exception { + protected CouldNotLoadCertificateException(String errorMessage) { + super(errorMessage); + } + + protected CouldNotLoadCertificateException(String errorMessage, Throwable err) { + super(errorMessage, err); + } + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/test/util/X509RequestPostProcessor.java b/gui-server/src/test/java/org/onap/policy/gui/server/test/util/X509RequestPostProcessor.java new file mode 100644 index 0000000..b1ef4a1 --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/test/util/X509RequestPostProcessor.java @@ -0,0 +1,46 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.test.util; + +import java.security.cert.X509Certificate; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.test.web.servlet.request.RequestPostProcessor; + +/** + * X509RequestPostProcessor is a test helper class for use with Spring MockMvc. + * It allows setting X509 certificates to a MockHttpServletRequest. + */ +public final class X509RequestPostProcessor implements RequestPostProcessor { + private final X509Certificate[] certificates; + + public X509RequestPostProcessor(X509Certificate... certificates) { + this.certificates = certificates; + } + + public MockHttpServletRequest postProcessRequest(MockHttpServletRequest request) { + request.setAttribute("javax.servlet.request.X509Certificate", this.certificates); + return request; + } + + public static RequestPostProcessor x509(X509Certificate... certificates) { + return new X509RequestPostProcessor(certificates); + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/test/util/hello/HelloWorldApplication.java b/gui-server/src/test/java/org/onap/policy/gui/server/test/util/hello/HelloWorldApplication.java new file mode 100644 index 0000000..e584665 --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/test/util/hello/HelloWorldApplication.java @@ -0,0 +1,29 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.test.util.hello; + +import org.springframework.boot.autoconfigure.SpringBootApplication; + +// This class is used in tests for ClampRestTemplateConfig. +@SpringBootApplication +public class HelloWorldApplication { + +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/test/util/hello/HelloWorldRestController.java b/gui-server/src/test/java/org/onap/policy/gui/server/test/util/hello/HelloWorldRestController.java new file mode 100644 index 0000000..71bd483 --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/test/util/hello/HelloWorldRestController.java @@ -0,0 +1,35 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.test.util.hello; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +// This class is used in tests for ClampRestTemplateConfig. +@RestController +public class HelloWorldRestController { + public static final String HELLO_WORLD_STRING = "Hello, World"; + + @RequestMapping("/") + public String greeting() { + return HELLO_WORLD_STRING; + } +} diff --git a/gui-server/src/test/java/org/onap/policy/gui/server/util/X509CertificateEncoderTest.java b/gui-server/src/test/java/org/onap/policy/gui/server/util/X509CertificateEncoderTest.java new file mode 100644 index 0000000..28b1217 --- /dev/null +++ b/gui-server/src/test/java/org/onap/policy/gui/server/util/X509CertificateEncoderTest.java @@ -0,0 +1,47 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 Nordix Foundation. + * ================================================================================ + * 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.util; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import org.junit.jupiter.api.Test; +import org.onap.policy.gui.server.test.util.KeyStoreHelper; + +class X509CertificateEncoderTest { + + @Test + void testPemEncoder() throws KeyStoreHelper.CouldNotLoadCertificateException, CertificateException { + X509Certificate loadedCert = KeyStoreHelper.loadValidCert(); + String pem = X509CertificateEncoder.getPemFromCert(loadedCert); + X509Certificate certFromPem = X509CertificateEncoder.getCertFromPem(pem); + assertEquals(loadedCert, certFromPem); + } + + @Test + void testUrlEncoder() throws KeyStoreHelper.CouldNotLoadCertificateException, CertificateException { + X509Certificate loadedCert = KeyStoreHelper.loadValidCert(); + String encodedCert = X509CertificateEncoder.urlEncodeCert(loadedCert); + X509Certificate decodedCert = X509CertificateEncoder.urlDecodeCert(encodedCert); + assertEquals(loadedCert, decodedCert); + } +} diff --git a/gui-server/src/test/resources/helloworld-keystore.jks b/gui-server/src/test/resources/helloworld-keystore.jks Binary files differnew file mode 100644 index 0000000..c2f1a0e --- /dev/null +++ b/gui-server/src/test/resources/helloworld-keystore.jks diff --git a/gui-server/src/test/resources/helloworld-truststore.jks b/gui-server/src/test/resources/helloworld-truststore.jks Binary files differnew file mode 100644 index 0000000..64c123e --- /dev/null +++ b/gui-server/src/test/resources/helloworld-truststore.jks diff --git a/gui-server/src/test/resources/keystore-proxytest.jks b/gui-server/src/test/resources/keystore-proxytest.jks Binary files differnew file mode 100644 index 0000000..c261084 --- /dev/null +++ b/gui-server/src/test/resources/keystore-proxytest.jks |