aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md45
-rw-r--r--certs/password1
-rw-r--r--certs/rootCA.crt31
-rw-r--r--certs/rootCA.key54
-rw-r--r--dpo/spec/vescollector-componentspec.json24
-rwxr-xr-xetc/collector.properties5
-rw-r--r--etc/trustpasswordfile1
-rw-r--r--etc/truststorebin0 -> 1453 bytes
-rw-r--r--pom.xml41
-rw-r--r--src/main/java/org/onap/dcae/ApplicationSettings.java13
-rw-r--r--src/main/java/org/onap/dcae/commonFunction/SSLContextCreator.java82
-rw-r--r--src/main/java/org/onap/dcae/restapi/ApiConfiguration.java2
-rw-r--r--src/main/java/org/onap/dcae/restapi/ServletConfig.java56
-rw-r--r--src/test/java/org/onap/dcae/TLSTest.java138
-rw-r--r--src/test/java/org/onap/dcae/TLSTestBase.java152
-rw-r--r--src/test/java/org/onap/dcae/TestingUtilities.java57
-rw-r--r--src/test/resources/keystorebin0 -> 3717 bytes
-rw-r--r--src/test/resources/passwordfile1
-rw-r--r--src/test/resources/trustpasswordfile1
-rw-r--r--src/test/resources/truststorebin0 -> 5145 bytes
-rw-r--r--version.properties2
21 files changed, 673 insertions, 33 deletions
diff --git a/README.md b/README.md
index 9e38dd6b..09037680 100644
--- a/README.md
+++ b/README.md
@@ -99,8 +99,9 @@ STEPS FOR SETUP/TEST
Note: If DMAAPHOST provided is invalid, you will see exception around publish on the collector.logs (collector queues and attempts to resend the event hence exceptions reported will be periodic). If you don’t want to see the error, publish to dmaap can be disabled by changing either “collector.dmaap.streamid” on etc/collector.properties OR by modifying the “name” defined on etc/DmaapConfig.json.
Any changes to property within container requires collector restart
- /opt/app/VESCollector/bin/VESrestfulCollector.sh stop
- /opt/app/VESCollector/bin/VESrestfulCollector.sh start
+ cd /opt/app/VESCollector/
+ ./bin/appController.sh stop
+ ./bin/appController.sh start
5) If DMAAP instance (and DMAAPHOST passed during VESCollector startup) and VES input is valid, then events will be pushed to below topics depending on the domain
Fault :http://<dmaaphost>:3904/events/unauthenticated.SEC_FAULT_OUTPUT
@@ -119,9 +120,9 @@ Authentication is disabled on the container for R1; below are the steps for enab
Note: The actual credentials is stored part of header.authlist parameter. This is list of userid,password (base64encoded) values. Default configuration has below set
sample1,c2FtcGxlMQ==|vdnsagg,dmRuc2FnZw==, where password maps to same value as username.
3) Restart the collector
- cd /opt/app/VESCollector/bin
- ./VESrestfulCollector.sh stop
- ./VESrestfulCollector.sh start
+ cd /opt/app/VESCollector
+ ./bin/appController.sh stop
+ ./bin/appController.sh start
4) Exit from container and ensure tcp port on VM is not hanging on finwait – you can execute “netstat -an | grep 8443” . If under FIN_WAIT2, wait for server to release.
5) Simulate via curl (Note - username/pwd will be required)
Example of successfull POST:
@@ -147,3 +148,37 @@ Authentication is disabled on the container for R1; below are the steps for enab
Note: In general support for HTTPS also require certificate/keystore be installed on target VM with FS mapped into the container for VESCollector to load. For demo and testing purpose - a self signed certificate is included within docker build. When deployed via DCAEGEN2 platform - these configuration will be overridden dynamically to map to required path/certificate name. This will be exercised post R1 though.
```
+
+A client's certificate verification is disabled on the container by default; below are the steps for enabling mutual TLS authentication for VESCollector.
+```
+1) Login to the container
+2) Open /opt/app/VESCollector/etc/collector.properties and edit below properties
+ a) Comment below property (with authentication enabled, standard http should be disabled)
+ collector.service.port=8080
+ b) Enable a client's certificate verification
+ collector.service.secure.clientauth=1
+3) Restart the collector
+ cd /opt/app/VESCollector
+ ./bin/appController.sh stop
+ ./bin/appController.sh start
+4) Exit from container and ensure tcp port on VM is not hanging on finwait – you can execute “netstat -an | grep 8443” . If under FIN_WAIT2, wait for server to release.
+5) In order for VESCollector to accept a connection from a client, the client has to use TLS certificate signed by CA that is present in VESCollector truststore. If a default VESCollector truststore is used then a client's certificate may be generated using following steps:
+ a) Generate a client's private key
+ openssl genrsa -out client.key 2048
+ b) Create the signing
+ openssl req -new -key client.key -out client.csr
+ c) Create the client's certificate (CA key password should be obtained from [VESCollectorRepository]/certs/password)
+ openssl x509 -req -in client.csr -CA [VESCollectorRepository]/certs/rootCA.crt -CAkey [VESCollectorRepository]/certs/rootCA.key -CAcreateserial -out client.crt -days 500 -sha256
+6) Simulate via curl (assuming that the certificate was created via step 5)
+ Example of successfull POST:
+ curl -i -X POST -d @event.json --header "Content-Type: application/json" https://localhost:8443/eventListener/v7 -k --cert client.crt --key client.key
+ HTTP/1.1 100
+
+ HTTP/1.1 202
+ Content-Type: application/json
+ Content-Length: 8
+ Date: Wed, 21 Nov 2018 11:37:58 GMT
+
+ Example of authentication failure (without a client's certificate):
+ curl -i -X POST -d @event.json --header "Content-Type: application/json" https://localhost:8443/eventListener/v7 -k
+ curl: (35) error:14094412:SSL routines:ssl3_read_bytes:sslv3 alert bad certificate \ No newline at end of file
diff --git a/certs/password b/certs/password
new file mode 100644
index 00000000..702a4cbd
--- /dev/null
+++ b/certs/password
@@ -0,0 +1 @@
+collector
diff --git a/certs/rootCA.crt b/certs/rootCA.crt
new file mode 100644
index 00000000..6a869ffc
--- /dev/null
+++ b/certs/rootCA.crt
@@ -0,0 +1,31 @@
+-----BEGIN CERTIFICATE-----
+MIIFazCCA1OgAwIBAgIUHt/v/SBbEGqP7K3veoJTDUgmiG4wDQYJKoZIhvcNAQEL
+BQAwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgMClNvbWUtU3RhdGUxITAfBgNVBAoM
+GEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDAeFw0xODExMDcwODU5MDRaFw0yMTA4
+MjcwODU5MDRaMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIDApTb21lLVN0YXRlMSEw
+HwYDVQQKDBhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwggIiMA0GCSqGSIb3DQEB
+AQUAA4ICDwAwggIKAoICAQC+yIztigSbID4dT33lEc+UF69gKpgdD0o7/9nn3Qey
+ZrHExdNdF9RFnEReswLe64ZEL8BwBaPzveAPYzk7jN6J1LS3vfTDkGnzNXVyokcT
+rGL4xIGAFe13ZBj1xTlxBojRuJsAJ2n2ihnk2pcQa+QCOy7HA7iORVrrf4hzUthH
+ccvVFY5Z17sNLtTCrso9P9xIkMq4E+8GS7ufTZIUVSi6oruSOlhD0K0w/aeKwPjB
+HTk5o8ymhc/HSOcxVbUe+fkcsK8izHLToPT4lCAPCrsveHb6eIQzd+yzdgCSdOfL
+sCoSZP7s56DkmaPNxk8Ua5VnunTKqGOSmTIET+hwRTKW5lCyJGWNIML99rWkp4E+
+TXaSMyQ86S1ffm2qsBwHuNYbTc6KzjeN0Gr54+ApageVIpOM43bPEYd5BLM3StgV
+5GdLMH5ndX30zMoIvlgF7olcmdORdA7WgFRAmivN4fn66f6Kie6lvX2FTgjFlArb
+abHFLjiqzW4XXcS7SV+opnd67stZAbdkLx+nOZjjJZDmpI5rvh1eZOz0hL9gkbi0
+yYTHXmpkfJoikn2FeCZHrqHXbC5FkDeR0zDEUHC6o7YZvQ9h1yogWRGa6/OZU8Ni
+h3hc6ZbVWGWGvG7tX6I5ft5y5GJgwkqOqcG43UCw/Gz2u7mDi0VoNw+PhFIQG94J
+VQIDAQABo1MwUTAdBgNVHQ4EFgQUUTgeHVFkmRuXF3IL3eR1nFM1TQowHwYDVR0j
+BBgwFoAUUTgeHVFkmRuXF3IL3eR1nFM1TQowDwYDVR0TAQH/BAUwAwEB/zANBgkq
+hkiG9w0BAQsFAAOCAgEAqnpeSRF8bp4GKwnqU5HSg3uB5BbmfB3YF2D2NIVxYGfK
+Z4KOY6Iea5sxMUEACfWQrI50GBy3J/Wgw2wBMBcKSLXa0VF7WAY61B6VPKVrKBPo
+hVdSiWQ+UuwMbV00DPub3myB0rLYLagHrdZ0y9HHpDLQrqW+LR+lEbxckhrVmUsy
+wO2d4AmdLCnjqUUVG5xktmpq2Q5aEPkNhQPJEggn9hFlIybklSyRMoWyK36KUo5F
+szEt0teI+T4Q6lqJTiVrV3N5w9fNlcJ4ye5uTFrMwBjMS4I0kId2PHfv6KZ+LUQc
+4fu22RtNtFmcIfNAxaYyNb53EmcsDLf/v5WM/ZzietU1p0UHzxECdSTFt7cV1Ldt
+yFjbWkyZ4Jdn+aMadxpUdjT/Egm31hovE+KPbZUdd9xNEbQ+JZc/kx2Cuyxw1c8a
+UcFAxwPzwqU8+LpHLiXx6rZrGuZ7Nc8RpgR4ylHbyRr2ynJgi66QPyp6HhwvBEgK
+ig+8qUaczUGlSbssMN11tJ235/j6FFn+V8yp3UJEU7ax7epQ9YMyiw3maY+x1L0Z
+ZOXrZCkXCf1LQ3x52e1gvfGqYCBSxY217+bNNieQe3RsxPwaGd/KVv5C62GzIVG+
+r1k7gCv3gIHk8kTVeHpv22qvqrIT6lnGpUgJTAPzvtb+gZn/ByrvGHs5AnZkOkk=
+-----END CERTIFICATE-----
diff --git a/certs/rootCA.key b/certs/rootCA.key
new file mode 100644
index 00000000..08347661
--- /dev/null
+++ b/certs/rootCA.key
@@ -0,0 +1,54 @@
+-----BEGIN RSA PRIVATE KEY-----
+Proc-Type: 4,ENCRYPTED
+DEK-Info: DES-EDE3-CBC,00244E4EB5B0693C
+
+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
+-----END RSA PRIVATE KEY-----
diff --git a/dpo/spec/vescollector-componentspec.json b/dpo/spec/vescollector-componentspec.json
index 3c588fb1..e683345b 100644
--- a/dpo/spec/vescollector-componentspec.json
+++ b/dpo/spec/vescollector-componentspec.json
@@ -248,6 +248,14 @@
"policy_editable": false,
"designer_editable": true
},
+ {
+ "name": "collector.service.secure.clientauth",
+ "value": 0,
+ "description": "Mutual TLS authentication flag; enables an authentication of the client to the server",
+ "sourced_at_deployment": false,
+ "policy_editable": false,
+ "designer_editable": false
+ },
{
"name": "collector.keystore.file.location",
"value": "/opt/app/dcae-certificate/keystore.jks",
@@ -272,6 +280,22 @@
"policy_editable": false,
"designer_editable": false
},
+ {
+ "name": "collector.truststore.file.location",
+ "value": "/opt/app/dcae-certificate/truststore.jks",
+ "description": "fs location of truststore file in vm",
+ "sourced_at_deployment": false,
+ "policy_editable": false,
+ "designer_editable": false
+ },
+ {
+ "name": "collector.truststore.passwordfile",
+ "value": "/opt/app/dcae-certificate/.trustpassword",
+ "description": "location of truststore password file in vm",
+ "sourced_at_deployment": false,
+ "policy_editable": false,
+ "designer_editable": false
+ },
{
"name": "collector.inputQueue.maxPending",
"value": 8096,
diff --git a/etc/collector.properties b/etc/collector.properties
index c9ccc133..475c49b0 100755
--- a/etc/collector.properties
+++ b/etc/collector.properties
@@ -23,11 +23,16 @@ collector.service.port=8080
## When enabled - require valid keystore defined
collector.service.secure.port=8443
+collector.service.secure.clientauth=0
+
## The keystore must be setup per installation when secure port is configured
collector.keystore.file.location=etc/keystore
collector.keystore.passwordfile=etc/passwordfile
collector.keystore.alias=tomcat
+## The truststore must be setup per installation when mutual tls support is configured
+collector.truststore.file.location=etc/truststore
+collector.truststore.passwordfile=etc/trustpasswordfile
###############################################################################
## Processing
diff --git a/etc/trustpasswordfile b/etc/trustpasswordfile
new file mode 100644
index 00000000..25acfbf5
--- /dev/null
+++ b/etc/trustpasswordfile
@@ -0,0 +1 @@
+collector \ No newline at end of file
diff --git a/etc/truststore b/etc/truststore
new file mode 100644
index 00000000..1fbf7ef9
--- /dev/null
+++ b/etc/truststore
Binary files differ
diff --git a/pom.xml b/pom.xml
index c4347888..eeaa6b9d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -27,7 +27,7 @@ limitations under the License.
</parent>
<groupId>org.onap.dcaegen2.collectors.ves</groupId>
<artifactId>VESCollector</artifactId>
- <version>1.4.0-SNAPSHOT</version>
+ <version>1.4.1-SNAPSHOT</version>
<name>dcaegen2-collectors-ves</name>
<description>VESCollector</description>
<properties>
@@ -87,6 +87,11 @@ limitations under the License.
<version>2.9</version>
</plugin>
<plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <version>3.0.0-M1</version>
+ </plugin>
+ <plugin>
<groupId>com.spotify</groupId>
<artifactId>docker-maven-plugin</artifactId>
<version>1.1.1</version>
@@ -334,7 +339,7 @@ limitations under the License.
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
- <version>2.3.1</version>
+ <version>2.8.5</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
@@ -404,15 +409,27 @@ limitations under the License.
</dependency>
<!-- TESTING -->
<dependency>
- <groupId>org.mockito</groupId>
- <artifactId>mockito-core</artifactId>
- <version>2.18.0</version>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-api</artifactId>
+ <version>5.3.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-engine</artifactId>
+ <version>5.3.1</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.junit.vintage</groupId>
+ <artifactId>junit-vintage-engine</artifactId>
+ <version>5.3.1</version>
<scope>test</scope>
</dependency>
<dependency>
- <groupId>junit</groupId>
- <artifactId>junit</artifactId>
- <version>4.12</version>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-junit-jupiter</artifactId>
+ <version>2.23.0</version>
<scope>test</scope>
</dependency>
<dependency>
@@ -436,7 +453,13 @@ limitations under the License.
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
- <version>5.1.0.RELEASE</version>
+ <version>5.1.1.RELEASE</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework.boot</groupId>
+ <artifactId>spring-boot-starter-test</artifactId>
+ <version>2.1.0.RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
diff --git a/src/main/java/org/onap/dcae/ApplicationSettings.java b/src/main/java/org/onap/dcae/ApplicationSettings.java
index 189d4a9a..ead148c4 100644
--- a/src/main/java/org/onap/dcae/ApplicationSettings.java
+++ b/src/main/java/org/onap/dcae/ApplicationSettings.java
@@ -37,7 +37,6 @@ import org.apache.commons.configuration.PropertiesConfiguration;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import org.springframework.stereotype.Component;
import javax.annotation.Nullable;
import java.io.IOException;
@@ -167,10 +166,22 @@ public class ApplicationSettings {
return prependWithUserDirOnRelative(properties.getString("collector.keystore.file.location", "etc/keystore"));
}
+ public boolean clientTlsAuthenticationEnabled() {
+ return httpsEnabled() && properties.getInt("collector.service.secure.clientauth", 0) > 0;
+ }
+
public String keystoreAlias() {
return properties.getString("collector.keystore.alias", "tomcat");
}
+ public String truststorePasswordFileLocation() {
+ return prependWithUserDirOnRelative(properties.getString("collector.truststore.passwordfile", "etc/trustpasswordfile"));
+ }
+
+ public String truststoreFileLocation() {
+ return prependWithUserDirOnRelative(properties.getString("collector.truststore.file.location", "etc/truststore"));
+ }
+
public String exceptionConfigFileLocation() {
return properties.getString("exceptionConfig", null);
}
diff --git a/src/main/java/org/onap/dcae/commonFunction/SSLContextCreator.java b/src/main/java/org/onap/dcae/commonFunction/SSLContextCreator.java
new file mode 100644
index 00000000..29e974ef
--- /dev/null
+++ b/src/main/java/org/onap/dcae/commonFunction/SSLContextCreator.java
@@ -0,0 +1,82 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * PROJECT
+ * ================================================================================
+ * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.dcae.commonFunction;
+
+import org.springframework.boot.web.server.Ssl;
+
+import java.nio.file.Path;
+
+public class SSLContextCreator {
+ private final String keyStorePassword;
+ private final String certAlias;
+ private final Path keyStoreFile;
+
+ private Path trustStoreFile;
+ private String trustStorePassword;
+ private boolean hasTlsClientAuthentication = false;
+
+ public static SSLContextCreator create(final Path keyStoreFile, final String certAlias, final String password) {
+ return new SSLContextCreator(keyStoreFile, certAlias, password);
+ }
+
+ private SSLContextCreator(final Path keyStoreFile, final String certAlias, final String password) {
+ this.certAlias = certAlias;
+ this.keyStoreFile = keyStoreFile;
+ this.keyStorePassword = password;
+ }
+
+ public SSLContextCreator withTlsClientAuthentication(final Path trustStoreFile, final String password) {
+ hasTlsClientAuthentication = true;
+ this.trustStoreFile = trustStoreFile;
+ this.trustStorePassword = password;
+
+ return this;
+ }
+
+ private void configureKeyStore(final Ssl ssl) {
+ final String keyStore = keyStoreFile.toAbsolutePath().toString();
+
+ ssl.setKeyStore(keyStore);
+ ssl.setKeyPassword(keyStorePassword);
+ ssl.setKeyAlias(certAlias);
+ }
+
+ private void configureTrustStore(final Ssl ssl) {
+ final String trustStore = trustStoreFile.toAbsolutePath().toString();
+
+ ssl.setTrustStore(trustStore);
+ ssl.setTrustStorePassword(trustStorePassword);
+ ssl.setClientAuth(Ssl.ClientAuth.NEED);
+ }
+
+ public Ssl build() {
+ final Ssl ssl = new Ssl();
+ ssl.setEnabled(true);
+
+ configureKeyStore(ssl);
+
+ if (hasTlsClientAuthentication) {
+ configureTrustStore(ssl);
+ }
+
+ return ssl;
+ }
+} \ No newline at end of file
diff --git a/src/main/java/org/onap/dcae/restapi/ApiConfiguration.java b/src/main/java/org/onap/dcae/restapi/ApiConfiguration.java
index 85db81df..9ebb5394 100644
--- a/src/main/java/org/onap/dcae/restapi/ApiConfiguration.java
+++ b/src/main/java/org/onap/dcae/restapi/ApiConfiguration.java
@@ -25,9 +25,11 @@ import org.onap.dcae.ApplicationSettings;
import org.slf4j.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
+import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
+@EnableWebMvc
@Configuration
public class ApiConfiguration implements WebMvcConfigurer {
private final ApplicationSettings applicationSettings;
diff --git a/src/main/java/org/onap/dcae/restapi/ServletConfig.java b/src/main/java/org/onap/dcae/restapi/ServletConfig.java
index 871904c6..49455e54 100644
--- a/src/main/java/org/onap/dcae/restapi/ServletConfig.java
+++ b/src/main/java/org/onap/dcae/restapi/ServletConfig.java
@@ -22,6 +22,7 @@
package org.onap.dcae.restapi;
import org.onap.dcae.ApplicationSettings;
+import org.onap.dcae.commonFunction.SSLContextCreator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@@ -31,6 +32,7 @@ import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerF
import org.springframework.stereotype.Component;
import java.io.IOException;
+import java.nio.file.Path;
import java.nio.file.Paths;
import static java.nio.file.Files.readAllBytes;
@@ -45,31 +47,57 @@ public class ServletConfig implements WebServerFactoryCustomizer<ConfigurableSer
@Override
public void customize(ConfigurableServletWebServerFactory container) {
- if (properties.authorizationEnabled()) {
- container.setSsl(createSSL());
+ final boolean hasClientTlsAuthentication = properties.clientTlsAuthenticationEnabled();
+
+ if (hasClientTlsAuthentication || properties.authorizationEnabled()) {
+ container.setSsl(hasClientTlsAuthentication ? httpsContextWithTlsAuthentication() : simpleHttpsContext());
container.setPort(properties.httpsPort());
} else {
container.setPort(properties.httpPort());
}
}
- private Ssl createSSL() {
+ private SSLContextCreator simpleHttpsContextBuilder() {
log.info("Enabling SSL");
- Ssl ssl = new Ssl();
- ssl.setEnabled(true);
- String keyStore = Paths.get(properties.keystoreFileLocation()).toAbsolutePath().toString();
+
+ final Path keyStore = toAbsolutePath(properties.keystoreFileLocation());
log.info("Using keyStore path: " + keyStore);
- ssl.setKeyStore(keyStore);
- String keyPasswordFileLocation = Paths.get(properties.keystorePasswordFileLocation()).toAbsolutePath().toString();
- log.info("Using keyStore password from: " + keyPasswordFileLocation);
- ssl.setKeyPassword(getKeyStorePassword(keyPasswordFileLocation));
- ssl.setKeyAlias(properties.keystoreAlias());
- return ssl;
+
+ final Path keyStorePasswordLocation = toAbsolutePath(properties.keystorePasswordFileLocation());
+ final String keyStorePassword = getKeyStorePassword(keyStorePasswordLocation);
+ log.info("Using keyStore password from: " + keyStorePasswordLocation);
+
+ final String alias = properties.keystoreAlias();
+
+ return SSLContextCreator.create(keyStore, alias, keyStorePassword);
+ }
+
+ private Ssl simpleHttpsContext() {
+ return simpleHttpsContextBuilder().build();
+ }
+
+ private Ssl httpsContextWithTlsAuthentication() {
+ final SSLContextCreator sslContextCreator = simpleHttpsContextBuilder();
+
+ log.info("Enabling TLS client authorization");
+
+ final Path trustStore = toAbsolutePath(properties.truststoreFileLocation());
+ log.info("Using trustStore path: " + trustStore);
+
+ final Path trustPasswordFileLocation = toAbsolutePath(properties.truststorePasswordFileLocation());
+ final String trustStorePassword = getKeyStorePassword(trustPasswordFileLocation);
+ log.info("Using trustStore password from: " + trustPasswordFileLocation);
+
+ return sslContextCreator.withTlsClientAuthentication(trustStore, trustStorePassword).build();
+ }
+
+ private Path toAbsolutePath(final String path) {
+ return Paths.get(path).toAbsolutePath();
}
- private String getKeyStorePassword(String location) {
+ private String getKeyStorePassword(final Path location) {
try {
- return new String(readAllBytes(Paths.get(location)));
+ return new String(readAllBytes(location));
} catch (IOException e) {
log.error("Could not read keystore password from: '" + location + "'.", e);
throw new RuntimeException(e);
diff --git a/src/test/java/org/onap/dcae/TLSTest.java b/src/test/java/org/onap/dcae/TLSTest.java
new file mode 100644
index 00000000..63099b7d
--- /dev/null
+++ b/src/test/java/org/onap/dcae/TLSTest.java
@@ -0,0 +1,138 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * org.onap.dcaegen2.collectors.ves
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.dcae;
+
+import io.vavr.collection.HashMap;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.springframework.context.annotation.Import;
+import org.springframework.http.HttpStatus;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.when;
+import static org.onap.dcae.TLSTest.HttpsConfiguration.USERNAME;
+import static org.onap.dcae.TLSTest.HttpsConfiguration.PASSWORD;
+
+public class TLSTest extends TLSTestBase {
+
+ @Nested
+ @Import(HttpConfiguration.class)
+ class HttpTest extends TestClassBase {
+
+ @Test
+ public void shouldHttpRequestSucceed() {
+ assertEquals(HttpStatus.OK, makeHttpRequest().getStatusCode());
+ }
+
+ @Test
+ public void shouldHttpsRequestFail() {
+ assertThrows(Exception.class, this::makeHttpsRequest);
+ }
+ }
+
+ @Nested
+ @Import(HttpsConfiguration.class)
+ class HttpsTest extends TestClassBase {
+
+
+ @Test
+ public void shouldHttpsRequestWithoutBasicAuthFail() {
+ assertThrows(Exception.class, this::makeHttpsRequest);
+ }
+
+ @Test
+ public void shouldHttpsRequestWithBasicAuthSucceed() {
+ assertEquals(HttpStatus.OK, makeHttpsRequestWithBasicAuth(USERNAME, PASSWORD).getStatusCode());
+ }
+ }
+
+ @Nested
+ @Import(HttpsConfigurationWithTLSAuthentication.class)
+ class HttpsWithTLSAuthenticationTest extends TestClassBase {
+
+ @Test
+ public void shouldHttpsRequestWithoutCertificateFail() {
+ assertThrows(Exception.class, this::makeHttpsRequest);
+ }
+
+ @Test
+ public void shouldHttpsRequestWithCertificateSucceed() {
+ assertEquals(HttpStatus.OK, makeHttpsRequestWithClientCert().getStatusCode());
+ }
+ }
+
+ @Nested
+ @Import(HttpsConfigurationWithTLSAuthenticationAndBasicAuth.class)
+ class HttpsWithTLSAuthenticationAndBasicAuthTest extends TestClassBase {
+
+ @Test
+ public void shouldHttpsRequestWithoutBasicAuthFail() {
+ assertThrows(Exception.class, this::makeHttpsRequestWithClientCert);
+ }
+
+ @Test
+ public void shouldHttpsRequestWithBasicAuthSucceed() {
+ assertEquals(HttpStatus.OK, makeHttpsRequestWithClientCertAndBasicAuth(USERNAME, PASSWORD).getStatusCode());
+ }
+ }
+
+ // ApplicationSettings configurations
+ static class HttpConfiguration extends TLSTestBase.ConfigurationBase {
+ @Override
+ protected void configureSettings(ApplicationSettings settings) {
+ }
+ }
+
+ static class HttpsConfiguration extends TLSTestBase.ConfigurationBase {
+ public static final String USERNAME = "TestUser";
+ public static final String PASSWORD = "TestPassword";
+
+ @Override
+ protected void configureSettings(ApplicationSettings settings) {
+ when(settings.keystoreAlias()).thenReturn(KEYSTORE_ALIAS);
+ when(settings.keystoreFileLocation()).thenReturn(KEYSTORE.toString());
+ when(settings.keystorePasswordFileLocation()).thenReturn(KEYSTORE_PASSWORD_FILE.toString());
+ when(settings.authorizationEnabled()).thenReturn(true);
+ when(settings.validAuthorizationCredentials()).thenReturn(HashMap.of(USERNAME, PASSWORD));
+ }
+ }
+
+ static class HttpsConfigurationWithTLSAuthentication extends HttpsConfiguration {
+ @Override
+ protected void configureSettings(ApplicationSettings settings) {
+ super.configureSettings(settings);
+ when(settings.authorizationEnabled()).thenReturn(false);
+ when(settings.clientTlsAuthenticationEnabled()).thenReturn(true);
+ when(settings.truststoreFileLocation()).thenReturn(TRUSTSTORE.toString());
+ when(settings.truststorePasswordFileLocation()).thenReturn(TRUSTSTORE_PASSWORD_FILE.toString());
+ }
+ }
+
+ static class HttpsConfigurationWithTLSAuthenticationAndBasicAuth extends HttpsConfigurationWithTLSAuthentication {
+ @Override
+ protected void configureSettings(ApplicationSettings settings) {
+ super.configureSettings(settings);
+ when(settings.authorizationEnabled()).thenReturn(true);
+ }
+ }
+}
diff --git a/src/test/java/org/onap/dcae/TLSTestBase.java b/src/test/java/org/onap/dcae/TLSTestBase.java
new file mode 100644
index 00000000..8b486ec7
--- /dev/null
+++ b/src/test/java/org/onap/dcae/TLSTestBase.java
@@ -0,0 +1,152 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * org.onap.dcaegen2.collectors.ves
+ * ================================================================================
+ * Copyright (C) 2018 Nokia. All rights reserved.
+ * Copyright (C) 2018 AT&T Intellectual Property. All rights reserved.
+ * ================================================================================
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.dcae;
+
+import org.json.JSONObject;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mockito;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.boot.test.mock.mockito.MockBean;
+import org.springframework.boot.web.server.LocalServerPort;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Primary;
+import org.springframework.http.ResponseEntity;
+import org.springframework.http.client.support.BasicAuthenticationInterceptor;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
+import org.springframework.web.client.RestTemplate;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.concurrent.LinkedBlockingQueue;
+
+import static org.onap.dcae.TestingUtilities.*;
+
+@Configuration
+@ExtendWith(SpringExtension.class)
+public class TLSTestBase {
+ protected static final String KEYSTORE_ALIAS = "localhost";
+ protected static final Path RESOURCES = Paths.get("src", "test", "resources");
+ protected static final Path KEYSTORE = Paths.get(RESOURCES.toString(), "keystore");
+ protected static final Path KEYSTORE_PASSWORD_FILE = Paths.get(RESOURCES.toString(), "passwordfile");
+ protected static final Path TRUSTSTORE = Paths.get(RESOURCES.toString(), "truststore");
+ protected static final Path TRUSTSTORE_PASSWORD_FILE = Paths.get(RESOURCES.toString(), "trustpasswordfile");
+
+ protected static abstract class ConfigurationBase {
+ protected final ApplicationSettings settings = Mockito.mock(ApplicationSettings.class);
+
+ @Bean
+ @Primary
+ public ApplicationSettings settings() {
+ configureSettings(settings);
+ return settings;
+ }
+
+ protected abstract void configureSettings(final ApplicationSettings settings);
+ }
+
+ @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
+ protected abstract class TestClassBase {
+ @MockBean
+ @Qualifier("inputQueue")
+ protected LinkedBlockingQueue<JSONObject> queue;
+
+ @LocalServerPort
+ private int port;
+
+ private final String keyStorePassword;
+ private final String trustStorePassword;
+
+ public TestClassBase() {
+ keyStorePassword = readFile(KEYSTORE_PASSWORD_FILE);
+ trustStorePassword = readFile(TRUSTSTORE_PASSWORD_FILE);
+ }
+
+ private String getURL(final String protocol, final String uri) {
+ return protocol + "://localhost:" + port + uri;
+ }
+
+ private RestTemplate addBasicAuth(final RestTemplate template, final String username, final String password) {
+ template.getInterceptors()
+ .add(new BasicAuthenticationInterceptor(username, password));
+
+ return template;
+ }
+
+ public String createHttpURL(String uri) {
+ return getURL("http", uri);
+ }
+
+ public String createHttpsURL(String uri) {
+ return getURL("https", uri);
+ }
+
+ public RestTemplate createHttpRestTemplate() {
+ return new RestTemplate();
+ }
+
+ public RestTemplate createHttpsRestTemplate() {
+ return rethrow(() ->
+ createRestTemplateWithSsl(
+ sslBuilderWithTrustStore(KEYSTORE, keyStorePassword).build()
+ ));
+ }
+
+ public RestTemplate createHttpsRestTemplateWithKeyStore() {
+ return rethrow(() ->
+ createRestTemplateWithSsl(
+ configureKeyStore(
+ sslBuilderWithTrustStore(KEYSTORE, keyStorePassword),
+ TRUSTSTORE,
+ trustStorePassword
+ ).build())
+ );
+ }
+
+ public ResponseEntity<String> makeHttpRequest() {
+ return createHttpRestTemplate().getForEntity(createHttpURL("/"), String.class);
+ }
+
+ public ResponseEntity<String> makeHttpsRequest() {
+ return createHttpsRestTemplate().getForEntity(createHttpsURL("/"), String.class);
+ }
+
+
+ public ResponseEntity<String> makeHttpsRequestWithBasicAuth(final String username, final String password) {
+ return addBasicAuth(createHttpsRestTemplate(), username, password)
+ .getForEntity(createHttpsURL("/"), String.class);
+
+ }
+
+ public ResponseEntity<String> makeHttpsRequestWithClientCert() {
+ return createHttpsRestTemplateWithKeyStore().getForEntity(createHttpsURL("/"), String.class);
+ }
+
+ public ResponseEntity<String> makeHttpsRequestWithClientCertAndBasicAuth(
+ final String username,
+ final String password) {
+ return addBasicAuth(createHttpsRestTemplateWithKeyStore(), username, password)
+ .getForEntity(createHttpsURL("/"), String.class);
+ }
+ }
+}
diff --git a/src/test/java/org/onap/dcae/TestingUtilities.java b/src/test/java/org/onap/dcae/TestingUtilities.java
index bd05c4ea..4c0d5382 100644
--- a/src/test/java/org/onap/dcae/TestingUtilities.java
+++ b/src/test/java/org/onap/dcae/TestingUtilities.java
@@ -24,13 +24,30 @@ import static java.nio.file.Files.readAllBytes;
import static org.assertj.core.api.Assertions.assertThat;
import io.vavr.control.Try;
+
import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.CertificateException;
+
+import org.apache.http.client.HttpClient;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.ssl.SSLContextBuilder;
import org.assertj.core.api.AbstractThrowableAssert;
import org.assertj.core.api.Java6Assertions;
import org.json.JSONObject;
+import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
+import org.springframework.web.client.RestTemplate;
+
+import javax.net.ssl.SSLContext;
/**
* @author Pawel Szalapski (pawel.szalapski@nokia.com)
@@ -67,11 +84,11 @@ public final class TestingUtilities {
* Exception in test case usually means there is something wrong, it should never be catched, but rather thrown to
* be handled by JUnit framework.
*/
- private static <T> T rethrow(CheckedSupplier<T> supplier) {
+ public static <T> T rethrow(CheckedSupplier<T> supplier) {
try {
return supplier.get();
} catch (Exception e) {
- throw new RuntimeException();
+ throw new RuntimeException(e);
}
}
@@ -84,9 +101,43 @@ public final class TestingUtilities {
public static void assertFailureHasInfo(Try any, String... msgPart) {
Java6Assertions.assertThat(any.isFailure()).isTrue();
AbstractThrowableAssert<?, ? extends Throwable> o = Java6Assertions.assertThat(any.getCause())
- .hasCauseInstanceOf(Exception.class);
+ .hasCauseInstanceOf(Exception.class);
for (String s : msgPart) {
o.hasStackTraceContaining(s);
}
}
+
+ public static SSLContextBuilder sslBuilderWithTrustStore(final Path trustStore, final String pass) {
+ return rethrow(() ->
+ new SSLContextBuilder()
+ .loadTrustMaterial(trustStore.toFile(), pass.toCharArray())
+ );
+ }
+
+ public static SSLContextBuilder configureKeyStore(
+ final SSLContextBuilder builder,
+ final Path keyStore,
+ final String pass) {
+ return rethrow(() -> {
+ KeyStore cks = KeyStore.getInstance(KeyStore.getDefaultType());
+ cks.load(new FileInputStream(keyStore.toFile()), pass.toCharArray());
+
+ builder.loadKeyMaterial(cks, pass.toCharArray());
+
+ return builder;
+ });
+ }
+
+ public static RestTemplate createRestTemplateWithSsl(final SSLContext context) {
+ final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(context);
+ final HttpClient httpClient = HttpClients
+ .custom()
+ .setSSLSocketFactory(socketFactory)
+ .build();
+
+ final HttpComponentsClientHttpRequestFactory factory =
+ new HttpComponentsClientHttpRequestFactory(httpClient);
+
+ return new RestTemplate(factory);
+ }
}
diff --git a/src/test/resources/keystore b/src/test/resources/keystore
new file mode 100644
index 00000000..f0822cb8
--- /dev/null
+++ b/src/test/resources/keystore
Binary files differ
diff --git a/src/test/resources/passwordfile b/src/test/resources/passwordfile
new file mode 100644
index 00000000..11e1e240
--- /dev/null
+++ b/src/test/resources/passwordfile
@@ -0,0 +1 @@
+vestest \ No newline at end of file
diff --git a/src/test/resources/trustpasswordfile b/src/test/resources/trustpasswordfile
new file mode 100644
index 00000000..11e1e240
--- /dev/null
+++ b/src/test/resources/trustpasswordfile
@@ -0,0 +1 @@
+vestest \ No newline at end of file
diff --git a/src/test/resources/truststore b/src/test/resources/truststore
new file mode 100644
index 00000000..b0802a72
--- /dev/null
+++ b/src/test/resources/truststore
Binary files differ
diff --git a/version.properties b/version.properties
index 9e0d73d4..f3529928 100644
--- a/version.properties
+++ b/version.properties
@@ -1,6 +1,6 @@
major=1
minor=4
-patch=0
+patch=1
base_version=${major}.${minor}.${patch}
release_version=${base_version}
snapshot_version=${base_version}-SNAPSHOT