aboutsummaryrefslogtreecommitdiffstats
path: root/gui-server
diff options
context:
space:
mode:
Diffstat (limited to 'gui-server')
-rw-r--r--gui-server/extra/bin-for-dev/README.md10
-rw-r--r--gui-server/extra/bin-for-dev/config/dev/application.yml27
-rw-r--r--gui-server/extra/bin-for-dev/config/dev/logback.xml130
-rw-r--r--gui-server/extra/bin-for-dev/demo-clamp-keystore.p12bin0 -> 4139 bytes
-rw-r--r--gui-server/extra/bin-for-dev/demo-clamp-truststore.jksbin0 -> 1413 bytes
-rwxr-xr-xgui-server/extra/bin-for-dev/start-gui-server-docker.sh39
-rwxr-xr-xgui-server/extra/bin-for-dev/start-gui-server-jar.sh26
-rw-r--r--gui-server/pom.xml152
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/GuiServerApplication.java33
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig.java94
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/config/FilterRegistrationConfig.java42
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/config/StaticContentConfig.java38
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilter.java146
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/rest/ApexEditorRestController.java41
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/rest/ClampRestController.java78
-rw-r--r--gui-server/src/main/java/org/onap/policy/gui/server/util/X509CertificateEncoder.java70
-rw-r--r--gui-server/src/main/resources/static/index.html13
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/SpringContextTest.java37
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig1Test.java69
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig2Test.java61
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig3Test.java70
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig4Test.java67
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig5Test.java69
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilterTest.java211
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/rest/ApexEditorRestControllerTest.java61
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/rest/ClampRestControllerTest.java161
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/test/util/KeyStoreHelper.java92
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/test/util/X509RequestPostProcessor.java46
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/test/util/hello/HelloWorldApplication.java29
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/test/util/hello/HelloWorldRestController.java35
-rw-r--r--gui-server/src/test/java/org/onap/policy/gui/server/util/X509CertificateEncoderTest.java47
-rw-r--r--gui-server/src/test/resources/helloworld-keystore.jksbin0 -> 2721 bytes
-rw-r--r--gui-server/src/test/resources/helloworld-truststore.jksbin0 -> 1255 bytes
-rw-r--r--gui-server/src/test/resources/keystore-proxytest.jksbin0 -> 4953 bytes
34 files changed, 1994 insertions, 0 deletions
diff --git a/gui-server/extra/bin-for-dev/README.md b/gui-server/extra/bin-for-dev/README.md
new file mode 100644
index 0000000..b12bcff
--- /dev/null
+++ b/gui-server/extra/bin-for-dev/README.md
@@ -0,0 +1,10 @@
+# gui-server helper scripts
+This directory contains helper scripts for running gui-server.
+
+- start-gui-server-jar.sh starts the JAR.
+
+- start-gui-server-docker.sh starts the docker image.
+To ensure the latest development snapshot is run, first build the run `mvn clean install -P docker` from the gui repo.
+
+If you wish to test client cert authentication, you may import the certificate demo-clamp.keystore.p12 into your browser
+(password is 'changeit').
diff --git a/gui-server/extra/bin-for-dev/config/dev/application.yml b/gui-server/extra/bin-for-dev/config/dev/application.yml
new file mode 100644
index 0000000..efc2dcd
--- /dev/null
+++ b/gui-server/extra/bin-for-dev/config/dev/application.yml
@@ -0,0 +1,27 @@
+server:
+ port: 2443
+ ssl:
+ enabled: true
+ client-auth: want
+ key-store: file:demo-clamp-keystore.p12
+ key-store-password: changeit
+ trust-store: file:demo-clamp-truststore.jks
+ trust-store-password: changeit
+
+clamp:
+ # URL to the clamp backend
+ url: https://localhost:8443/
+ # Disabling SSL validation is useful for local testing, but should not be disabled in production.
+ disable-ssl-validation: true
+ # Disabling SSL hostname check is needed if cert name does not match hostname.
+ disable-ssl-hostname-check: true
+
+apex-editor:
+ upload-url:
+ upload-userid:
+
+management:
+ endpoints:
+ web:
+ exposure:
+ include: health, metrics, prometheus
diff --git a/gui-server/extra/bin-for-dev/config/dev/logback.xml b/gui-server/extra/bin-for-dev/config/dev/logback.xml
new file mode 100644
index 0000000..a8f8357
--- /dev/null
+++ b/gui-server/extra/bin-for-dev/config/dev/logback.xml
@@ -0,0 +1,130 @@
+<!--
+ ============LICENSE_START=======================================================
+ policy-gui
+ ================================================================================
+ Copyright (C) 2021-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.
+ ============LICENSE_END=========================================================
+ -->
+
+<configuration scan="true" scanPeriod="30 seconds" debug="false">
+
+ <property name="logDir" value="${POLICY_LOGS}" />
+
+ <property name="errorLog" value="error" />
+ <property name="debugLog" value="debug" />
+ <property name="networkLog" value="network" />
+
+ <property name="debugPattern"
+ value="[%d{yyyy-MM-dd'T'HH:mm:ss.SSS+00:00, UTC}|%level|%logger{0}|%thread] %msg%n" />
+ <property name="errorPattern" value="${debugPattern}" />
+ <property name="networkPattern" value="[%d{yyyy-MM-dd'T'HH:mm:ss.SSS+00:00, UTC}|%t]%m%n" />
+
+ <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
+ <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+ <level>INFO</level>
+ </filter>
+ <encoder>
+ <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{1024} - %msg%n
+ </pattern>
+ </encoder>
+ </appender>
+
+ <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${logDir}/${errorLog}.log</file>
+ <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+ <fileNamePattern>${logDir}/${errorLog}.%d{yyyy-MM-dd}.%i.log.zip
+ </fileNamePattern>
+ <maxFileSize>50MB</maxFileSize>
+ <maxHistory>30</maxHistory>
+ <totalSizeCap>10GB</totalSizeCap>
+ </rollingPolicy>
+ <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
+ <level>WARN</level>
+ </filter>
+ <encoder>
+ <pattern>${errorPattern}</pattern>
+ </encoder>
+ </appender>
+
+ <appender name="asyncError" class="ch.qos.logback.classic.AsyncAppender">
+ <appender-ref ref="ERROR" />
+ </appender>
+
+ <appender name="DEBUG" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${logDir}/${debugLog}.log</file>
+ <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+ <fileNamePattern>${logDir}/${debugLog}.%d{yyyy-MM-dd}.%i.log.zip
+ </fileNamePattern>
+ <maxFileSize>50MB</maxFileSize>
+ <maxHistory>30</maxHistory>
+ <totalSizeCap>10GB</totalSizeCap>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${debugPattern}</pattern>
+ </encoder>
+ </appender>
+
+ <appender name="asyncDebug" class="ch.qos.logback.classic.AsyncAppender">
+ <appender-ref ref="DEBUG" />
+ </appender>
+
+ <appender name="NETWORK" class="ch.qos.logback.core.rolling.RollingFileAppender">
+ <file>${logDir}/${networkLog}.log</file>
+ <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
+ <fileNamePattern>${logDir}/${networkLog}.%d{yyyy-MM-dd}.%i.log.zip
+ </fileNamePattern>
+ <maxFileSize>50MB</maxFileSize>
+ <maxHistory>30</maxHistory>
+ <totalSizeCap>10GB</totalSizeCap>
+ </rollingPolicy>
+ <encoder>
+ <pattern>${networkPattern}</pattern>
+ </encoder>
+ </appender>
+
+ <appender name="asyncNetwork" class="ch.qos.logback.classic.AsyncAppender">
+ <appender-ref ref="NETWORK" />
+ </appender>
+
+ <logger name="network" level="INFO" additivity="false">
+ <appender-ref ref="asyncNetwork" />
+ </logger>
+
+ <logger name="org.apache" level="INFO">
+ <appender-ref ref="DEBUG" />
+ </logger>
+
+ <!-- Spring related loggers -->
+ <logger name="org.springframework" level="INFO">
+ <appender-ref ref="DEBUG" />
+ </logger>
+
+ <!-- GUI related loggers -->
+ <logger name="org.onap.policy.gui" level="DEBUG">
+ <appender-ref ref="ERROR" />
+ <appender-ref ref="DEBUG" />
+ </logger>
+
+ <!-- logback internals logging -->
+ <logger name="ch.qos.logback.classic" level="INFO" />
+ <logger name="ch.qos.logback.core" level="INFO" />
+
+ <root level="DEBUG">
+ <appender-ref ref="asyncDebug" />
+ <appender-ref ref="asyncError" />
+ <appender-ref ref="asyncNetwork" />
+ <appender-ref ref="STDOUT" />
+ </root>
+</configuration>
diff --git a/gui-server/extra/bin-for-dev/demo-clamp-keystore.p12 b/gui-server/extra/bin-for-dev/demo-clamp-keystore.p12
new file mode 100644
index 0000000..e034eeb
--- /dev/null
+++ b/gui-server/extra/bin-for-dev/demo-clamp-keystore.p12
Binary files differ
diff --git a/gui-server/extra/bin-for-dev/demo-clamp-truststore.jks b/gui-server/extra/bin-for-dev/demo-clamp-truststore.jks
new file mode 100644
index 0000000..2af1adc
--- /dev/null
+++ b/gui-server/extra/bin-for-dev/demo-clamp-truststore.jks
Binary files differ
diff --git a/gui-server/extra/bin-for-dev/start-gui-server-docker.sh b/gui-server/extra/bin-for-dev/start-gui-server-docker.sh
new file mode 100755
index 0000000..78748cd
--- /dev/null
+++ b/gui-server/extra/bin-for-dev/start-gui-server-docker.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+#
+# ============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=========================================================
+#
+SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
+KEYSTORE_PATH=$(realpath "$SCRIPT_DIR/demo-clamp-keystore.p12")
+TRUSTSTORE_PATH=$(realpath "$SCRIPT_DIR/demo-clamp-truststore.jks")
+LOGBACK_PATH=$(realpath "$SCRIPT_DIR/config/dev/logback.xml")
+
+# Note hostname 'policy-clamp-be' is mapped to host-gateway (i.e. host's localhost)
+docker run \
+ --publish 2443:2443 \
+ --add-host policy-clamp-be:host-gateway \
+ --env "CLAMP_URL=https://policy-clamp-be:8443" \
+ --env "CLAMP_DISABLE_SSL_VALIDATION=true" \
+ --env "SERVER_SSL_CLIENT_AUTH=want" \
+ --env "KEYSTORE_PASSWD=changeit" \
+ --env "TRUSTSTORE_PASSWD=changeit" \
+ --volume "$KEYSTORE_PATH:/opt/app/policy/gui/etc/mounted/policy-keystore" \
+ --volume "$TRUSTSTORE_PATH:/opt/app/policy/gui/etc/mounted/policy-truststore" \
+ --volume "$LOGBACK_PATH:/opt/app/policy/gui/etc/mounted/logback.xml" \
+ --rm \
+ onap/policy-gui:latest
diff --git a/gui-server/extra/bin-for-dev/start-gui-server-jar.sh b/gui-server/extra/bin-for-dev/start-gui-server-jar.sh
new file mode 100755
index 0000000..5484356
--- /dev/null
+++ b/gui-server/extra/bin-for-dev/start-gui-server-jar.sh
@@ -0,0 +1,26 @@
+#!/bin/bash
+#
+# ============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=========================================================
+#
+# This script changes directory so that application.yml, keystore,
+# and truststore will be in current directory before running jar.
+SCRIPTDIR=$(dirname ${BASH_SOURCE[0]})
+pushd "$SCRIPTDIR" || exit
+java -Dspring.profiles.active=dev -jar ../../target/gui-server-*.jar
+popd || exit
diff --git a/gui-server/pom.xml b/gui-server/pom.xml
new file mode 100644
index 0000000..79c60d2
--- /dev/null
+++ b/gui-server/pom.xml
@@ -0,0 +1,152 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ============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=========================================================
+-->
+<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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <artifactId>policy-gui</artifactId>
+ <groupId>org.onap.policy.gui</groupId>
+ <version>2.2.1-SNAPSHOT</version>
+ </parent>
+ <artifactId>gui-server</artifactId>
+ <name>${project.artifactId}</name>
+ <description>Server for all the Policy GUI's.</description>
+
+ <properties>
+ <start-class>org.onap.policy.gui.server.GuiServerApplication</start-class>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.httpcomponents</groupId>
+ <artifactId>httpclient</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.commons</groupId>
+ <artifactId>commons-collections4</artifactId>
+ <version>4.4</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.gui</groupId>
+ <artifactId>gui-clamp</artifactId>
+ <version>${project.version}</version>
+ <type>pom</type>
+ </dependency>
+ <dependency>
+ <groupId>org.onap.policy.gui.editors</groupId>
+ <artifactId>gui-editor-apex</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <defaultGoal>install</defaultGoal>
+
+ <resources>
+ <resource>
+ <directory>${basedir}/src/main/resources</directory>
+ <filtering>true</filtering>
+ <includes>
+ <include>**/application*.yml</include>
+ <include>**/application*.yaml</include>
+ <include>**/application*.properties</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>${basedir}/src/main/resources</directory>
+ <filtering>false</filtering>
+ <excludes>
+ <exclude>**/application*.yml</exclude>
+ <exclude>**/application*.yaml</exclude>
+ <exclude>**/application*.properties</exclude>
+ </excludes>
+ </resource>
+ </resources>
+
+ <plugins>
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>unpack-gui-html</id>
+ <goals>
+ <goal>unpack</goal>
+ </goals>
+ <phase>generate-resources</phase>
+ </execution>
+ </executions>
+ <configuration>
+ <artifactItems>
+ <!-- copy static resources from clamp tar.gz into static/clamp/ -->
+ <artifactItem>
+ <groupId>org.onap.policy.gui</groupId>
+ <artifactId>gui-clamp</artifactId>
+ <version>${project.version}</version>
+ <classifier>clamp-build</classifier>
+ <type>tar.gz</type>
+ <overWrite>true</overWrite>
+ <outputDirectory>${project.build.directory}/classes/static</outputDirectory>
+ </artifactItem>
+ <!-- copy static resources from apex editor jar into static/apex-editor/ -->
+ <artifactItem>
+ <groupId>org.onap.policy.gui.editors</groupId>
+ <artifactId>gui-editor-apex</artifactId>
+ <version>${project.version}</version>
+ <type>jar</type>
+ <overWrite>true</overWrite>
+ <outputDirectory>${project.build.directory}/classes/static/apex-editor</outputDirectory>
+ <includes>static/**</includes>
+ <fileMappers>
+ <org.codehaus.plexus.components.io.filemappers.RegExpFileMapper>
+ <pattern>^static/</pattern>
+ <replacement>./</replacement>
+ </org.codehaus.plexus.components.io.filemappers.RegExpFileMapper>
+ </fileMappers>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-maven-plugin</artifactId>
+ <executions>
+ <execution>
+ <goals>
+ <goal>repackage</goal>
+ </goals>
+ <phase>package</phase>
+ </execution>
+ </executions>
+ </plugin>
+ </plugins>
+ </build>
+
+</project>
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/GuiServerApplication.java b/gui-server/src/main/java/org/onap/policy/gui/server/GuiServerApplication.java
new file mode 100644
index 0000000..7da2dd6
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/GuiServerApplication.java
@@ -0,0 +1,33 @@
+/*-
+ * ============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.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.annotation.ComponentScan;
+
+@SpringBootApplication
+@ComponentScan(basePackages = "org.onap.policy.gui")
+public class GuiServerApplication {
+ public static void main(String[] args) {
+ SpringApplication.run(GuiServerApplication.class, args);
+ }
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig.java b/gui-server/src/main/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig.java
new file mode 100644
index 0000000..8d501d2
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig.java
@@ -0,0 +1,94 @@
+/*-
+ * ============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 java.io.IOException;
+import java.security.GeneralSecurityException;
+import javax.annotation.PostConstruct;
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.SSLContext;
+import org.apache.http.conn.ssl.NoopHostnameVerifier;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.conn.ssl.TrustAllStrategy;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContextBuilder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.io.Resource;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+
+@Configuration
+public class ClampRestTemplateConfig {
+ private static final Logger LOG = LoggerFactory.getLogger(ClampRestTemplateConfig.class);
+
+ @Value("${clamp.disable-ssl-validation:false}")
+ private boolean disableSslValidation;
+
+ @Value("${clamp.disable-ssl-hostname-check:false}")
+ private boolean disableSslHostnameCheck;
+
+ @Value("${server.ssl.trust-store:#{null}}")
+ private Resource trustStore;
+
+ @Value("${server.ssl.trust-store-password:#{null}}")
+ private char[] trustStorePassword;
+
+ @PostConstruct
+ private void validateProperties() {
+ if (trustStore == null && !disableSslValidation) {
+ throw new IllegalArgumentException("server.ssl.trust-store must be set if SSL validation is enabled");
+ }
+ if (disableSslValidation && !disableSslHostnameCheck) {
+ LOG.info("Disabling SSL hostname check as SSL validation is disabled");
+ disableSslHostnameCheck = true;
+ }
+ }
+
+ /**
+ * Returns a RestTemplate, optionally disabling SSL hostname check or disabling SSL validation entirely.
+ */
+ @Bean
+ public RestTemplate clampRestTemplate() throws GeneralSecurityException, IOException {
+ SSLContext sslContext;
+ if (disableSslValidation) {
+ sslContext = new SSLContextBuilder().loadTrustMaterial(new TrustAllStrategy()).build();
+ } else {
+ sslContext = new SSLContextBuilder().loadTrustMaterial(trustStore.getURL(), trustStorePassword).build();
+ }
+
+ HostnameVerifier hostnameVerifier;
+ if (disableSslHostnameCheck) {
+ hostnameVerifier = new NoopHostnameVerifier();
+ } else {
+ hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier();
+ }
+
+ var csf = new SSLConnectionSocketFactory(sslContext, hostnameVerifier);
+ var httpClient = HttpClients.custom().setSSLSocketFactory(csf).build();
+ var requestFactory = new HttpComponentsClientHttpRequestFactory();
+ requestFactory.setHttpClient(httpClient);
+ return new RestTemplate(requestFactory);
+ }
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/config/FilterRegistrationConfig.java b/gui-server/src/main/java/org/onap/policy/gui/server/config/FilterRegistrationConfig.java
new file mode 100644
index 0000000..3e62237
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/config/FilterRegistrationConfig.java
@@ -0,0 +1,42 @@
+/*-
+ * ============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 org.onap.policy.gui.server.filters.ClientSslHeaderFilter;
+import org.springframework.boot.web.servlet.FilterRegistrationBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class FilterRegistrationConfig {
+
+ /**
+ * Registers ClientSslToHeaderFilter for /clamp/restservices/*.
+ */
+ @Bean
+ public FilterRegistrationBean<ClientSslHeaderFilter> clientSslHeaderFilter() {
+ FilterRegistrationBean<ClientSslHeaderFilter> registrationBean = new FilterRegistrationBean<>();
+ registrationBean.setFilter(new ClientSslHeaderFilter());
+ registrationBean.addUrlPatterns("/clamp/restservices/*");
+ return registrationBean;
+ }
+
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/config/StaticContentConfig.java b/gui-server/src/main/java/org/onap/policy/gui/server/config/StaticContentConfig.java
new file mode 100644
index 0000000..479202d
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/config/StaticContentConfig.java
@@ -0,0 +1,38 @@
+/*-
+ * ============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 org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
+import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+
+@Configuration
+public class StaticContentConfig implements WebMvcConfigurer {
+
+ @Override
+ public void addViewControllers(ViewControllerRegistry registry) {
+ registry.addViewController("/clamp").setViewName("redirect:/clamp/");
+ registry.addViewController("/clamp/").setViewName("forward:/clamp/index.html");
+ registry.addViewController("/apex-editor").setViewName("redirect:/apex-editor/");
+ registry.addViewController("/apex-editor/").setViewName("forward:/apex-editor/index.html");
+ }
+
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilter.java b/gui-server/src/main/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilter.java
new file mode 100644
index 0000000..db8f593
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilter.java
@@ -0,0 +1,146 @@
+/*-
+ * ============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.onap.policy.gui.server.util.X509CertificateEncoder.urlEncodeCert;
+
+import java.io.IOException;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateExpiredException;
+import java.security.cert.CertificateNotYetValidException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.Enumeration;
+import java.util.Set;
+import java.util.TreeSet;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpServletResponse;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.core.annotation.Order;
+import org.springframework.web.filter.OncePerRequestFilter;
+
+/**
+ * Filter which encodes a client SSL certificate into X-SSL-Cert HTTP header.
+ * CLAMP has a corresponding filter called ClampCadiFilter which decodes the
+ * header. This is needed as CLAMP runtime uses AAF for auth, and AAF uses
+ * client cert authentication. Since REST requests from CLAMP GUI to CLAMP
+ * runtime are proxied in gui-server, the proxy needs to attach a copy of the
+ * client SSL cert, as the proxy could not know the client's private key.
+ */
+@Order(1)
+public class ClientSslHeaderFilter extends OncePerRequestFilter {
+ private static final Logger LOG = LoggerFactory.getLogger(ClientSslHeaderFilter.class);
+
+ // Name of attribute containing request SSL cert.
+ public static final String X509_ATTRIBUTE_NAME = "javax.servlet.request.X509Certificate";
+
+ // Name of header containing encoded SSL cert - also used in clamp's ClampCadiFilter.
+ public static final String SSL_CERT_HEADER_NAME = "X-SSL-Cert";
+
+ @Override
+ protected void doFilterInternal(HttpServletRequest request,
+ HttpServletResponse response,
+ FilterChain filterChain) throws ServletException, IOException {
+ var wrappedRequest = new ClientSslHeaderRequestWrapper(request);
+ var certs = (X509Certificate[]) request.getAttribute(X509_ATTRIBUTE_NAME);
+ if (certs != null && certs.length > 0) {
+ try {
+ certs[0].checkValidity();
+ wrappedRequest.setSslCertHeader(urlEncodeCert(certs[0]));
+ } catch (CertificateEncodingException e) {
+ LOG.error("Error encoding client SSL cert", e);
+ } catch (CertificateExpiredException | CertificateNotYetValidException e) {
+ LOG.info("Client SSL cert expired", e);
+ }
+ }
+ filterChain.doFilter(wrappedRequest, response);
+ }
+
+ /*
+ * This class wraps a HttpServletRequest so that X-SSL-Cert header can be added.
+ */
+ private static class ClientSslHeaderRequestWrapper extends HttpServletRequestWrapper {
+ private String encodedSslCert = null;
+
+ public ClientSslHeaderRequestWrapper(HttpServletRequest request) {
+ super(request);
+ }
+
+ public void setSslCertHeader(String encodedSslCert) {
+ this.encodedSslCert = encodedSslCert;
+ }
+
+ /**
+ * Returns the value of the specified request header as a String.
+ * The header name is case insensitive.
+ */
+ @Override
+ public String getHeader(String name) {
+ if (SSL_CERT_HEADER_NAME.equalsIgnoreCase(name)) {
+ return encodedSslCert;
+ } else {
+ return super.getHeader(name);
+ }
+ }
+
+ /**
+ * Returns all the values of the specified request header as an Enumeration
+ * of String objects.
+ * Some headers, such as Accept-Language can be sent by clients as several
+ * headers each with a different value rather than sending the header as a
+ * comma separated list. The header name is case insensitive.
+ */
+ @Override
+ public Enumeration<String> getHeaders(String name) {
+ if (SSL_CERT_HEADER_NAME.equalsIgnoreCase(name)) {
+ if (encodedSslCert != null) {
+ return Collections.enumeration(Collections.singletonList(encodedSslCert));
+ } else {
+ return Collections.emptyEnumeration();
+ }
+ } else {
+ return super.getHeaders(name);
+ }
+ }
+
+ /**
+ * Returns an enumeration of all the header names this request contains.
+ * If the request has no headers, this method returns an empty enumeration.
+ */
+ @Override
+ public Enumeration<String> getHeaderNames() {
+ Set<String> names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
+ names.addAll(Collections.list(super.getHeaderNames()));
+ if (encodedSslCert != null) {
+ names.add(SSL_CERT_HEADER_NAME);
+ } else {
+ // This is needed to prevent an exploit where a user passes their own
+ // X-SSL-Cert header, possibly bypassing client cert verification.
+ names.remove(SSL_CERT_HEADER_NAME);
+ }
+ return Collections.enumeration(names);
+ }
+ }
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/rest/ApexEditorRestController.java b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ApexEditorRestController.java
new file mode 100644
index 0000000..a4b92ef
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ApexEditorRestController.java
@@ -0,0 +1,41 @@
+/*-
+ * ============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 javax.servlet.http.HttpServletRequest;
+import org.springframework.ui.ModelMap;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.servlet.ModelAndView;
+
+@RestController
+@RequestMapping("/apex-editor/policy/gui/v*/apex/editor")
+public class ApexEditorRestController {
+
+ /**
+ * Strip /apex-editor prefix from Apex Editor rest calls.
+ */
+ @RequestMapping("/**")
+ public ModelAndView forwardApexEditorRest(ModelMap model, HttpServletRequest request) {
+ String targetUrl = request.getRequestURI().replaceFirst("^/apex-editor", "");
+ return new ModelAndView("forward:" + targetUrl, model);
+ }
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/rest/ClampRestController.java b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ClampRestController.java
new file mode 100644
index 0000000..1975f37
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ClampRestController.java
@@ -0,0 +1,78 @@
+/*-
+ * ============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 java.net.URI;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestHeader;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.client.HttpStatusCodeException;
+import org.springframework.web.client.RestTemplate;
+import org.springframework.web.util.UriComponentsBuilder;
+
+@RestController
+@RequestMapping("/clamp/restservices")
+public class ClampRestController {
+
+ @Value("${clamp.url}")
+ private URI clampUrl;
+
+ @Autowired
+ @Qualifier("clampRestTemplate")
+ private RestTemplate restTemplate;
+
+ /**
+ * Proxy rest calls to clamp backend.
+ */
+ @RequestMapping("/**")
+ public ResponseEntity<String> mirrorRest(@RequestBody(required = false) String body,
+ @RequestHeader HttpHeaders headers,
+ HttpMethod method,
+ HttpServletRequest request) {
+ // Strip /clamp/ prefix from request URI.
+ String requestUri = request.getRequestURI().replaceFirst("^/clamp/", "");
+ URI uri = UriComponentsBuilder.fromUri(clampUrl)
+ .path(requestUri)
+ .query(request.getQueryString())
+ .build(true).toUri();
+
+ HttpEntity<String> httpEntity = new HttpEntity<>(body, headers);
+ try {
+ return restTemplate.exchange(uri, method, httpEntity, String.class);
+
+ } catch (HttpStatusCodeException e) {
+ // On error, return the backend error code instead of 500.
+ return ResponseEntity.status(e.getRawStatusCode())
+ .headers(e.getResponseHeaders())
+ .body(e.getResponseBodyAsString());
+ }
+ }
+
+}
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/util/X509CertificateEncoder.java b/gui-server/src/main/java/org/onap/policy/gui/server/util/X509CertificateEncoder.java
new file mode 100644
index 0000000..67da719
--- /dev/null
+++ b/gui-server/src/main/java/org/onap/policy/gui/server/util/X509CertificateEncoder.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.util;
+
+import java.io.ByteArrayInputStream;
+import java.net.URLDecoder;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Base64;
+
+/**
+ * Helper methods for encoding/decoding X509Certificates from PEM strings and URL-encoded PEM strings.
+ */
+public class X509CertificateEncoder {
+ private static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n";
+ private static final String END_CERT = "\n-----END CERTIFICATE-----";
+
+ private X509CertificateEncoder() {}
+
+ /**
+ * Returns a PEM string from an X509Certificate.
+ */
+ public static String getPemFromCert(X509Certificate cert) throws CertificateEncodingException {
+ return BEGIN_CERT + Base64.getEncoder().encodeToString(cert.getEncoded()) + END_CERT;
+ }
+
+ /**
+ * Returns an X509Certificate from a PEM string.
+ */
+ public static X509Certificate getCertFromPem(String pem) throws CertificateException {
+ return (X509Certificate) CertificateFactory.getInstance("X.509")
+ .generateCertificate(new ByteArrayInputStream(pem.getBytes()));
+ }
+
+ /**
+ * Returns URL-encoded PEM string from an X509Certificate, suitable as a HTTP header.
+ */
+ public static String urlEncodeCert(X509Certificate cert) throws CertificateEncodingException {
+ return URLEncoder.encode(getPemFromCert(cert), StandardCharsets.UTF_8);
+ }
+
+ /**
+ * Returns an X509Certificate from a URL-encoded PEM string.
+ */
+ public static X509Certificate urlDecodeCert(String encodedPem) throws CertificateException {
+ return getCertFromPem(URLDecoder.decode(encodedPem, StandardCharsets.UTF_8));
+ }
+}
diff --git a/gui-server/src/main/resources/static/index.html b/gui-server/src/main/resources/static/index.html
new file mode 100644
index 0000000..3b079a8
--- /dev/null
+++ b/gui-server/src/main/resources/static/index.html
@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+ <meta charset="utf-8">
+ <title>ONAP Policy GUI</title>
+</head>
+<body>
+<ul>
+ <li><a href="/apex-editor/">Apex Policy Editor</a></li>
+ <li><a href="/clamp/">CLAMP Designer UI</a></li>
+</ul>
+</body>
+</html>
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
new file mode 100644
index 0000000..c2f1a0e
--- /dev/null
+++ b/gui-server/src/test/resources/helloworld-keystore.jks
Binary files differ
diff --git a/gui-server/src/test/resources/helloworld-truststore.jks b/gui-server/src/test/resources/helloworld-truststore.jks
new file mode 100644
index 0000000..64c123e
--- /dev/null
+++ b/gui-server/src/test/resources/helloworld-truststore.jks
Binary files differ
diff --git a/gui-server/src/test/resources/keystore-proxytest.jks b/gui-server/src/test/resources/keystore-proxytest.jks
new file mode 100644
index 0000000..c261084
--- /dev/null
+++ b/gui-server/src/test/resources/keystore-proxytest.jks
Binary files differ