Added upport for trust store when agent acts as web client
Updated Dmaap so it uses http towards the agent NBI instead of https
Change-Id: Ia30ac783683efa478bc8ab56dc4ac8311b03f8f0
Issue-ID: NONRTRIC-195
Signed-off-by: PatrikBuhr <patrik.buhr@est.tech>
diff --git a/policy-agent/config/application.yaml b/policy-agent/config/application.yaml
index 4010219..55a1d64 100644
--- a/policy-agent/config/application.yaml
+++ b/policy-agent/config/application.yaml
@@ -21,12 +21,17 @@
file: /var/log/policy-agent/application.log
server:
port : 8433
+ http-port: 8081
ssl:
- key-store-type: PKCS12
+ key-store-type: JKS
key-store-password: policy_agent
key-store: classpath:keystore.jks
key-password: policy_agent
+ key-alias: policy_agent
app:
filepath: /opt/app/policy-agent/config/application_configuration.json
-
+ webclient:
+ trust-store-used: false
+ trust-store-password: policy_agent
+ trust-store: classpath:keystore.jks
diff --git a/policy-agent/src/main/java/org/oransc/policyagent/BeanFactory.java b/policy-agent/src/main/java/org/oransc/policyagent/BeanFactory.java
index e2874cb..1e01247 100644
--- a/policy-agent/src/main/java/org/oransc/policyagent/BeanFactory.java
+++ b/policy-agent/src/main/java/org/oransc/policyagent/BeanFactory.java
@@ -29,6 +29,7 @@
import org.oransc.policyagent.repository.PolicyTypes;
import org.oransc.policyagent.repository.Rics;
import org.oransc.policyagent.repository.Services;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.servlet.server.ServletWebServerFactory;
import org.springframework.context.annotation.Bean;
@@ -38,6 +39,9 @@
class BeanFactory {
private final ApplicationConfig applicationConfig = new ApplicationConfig();
+ @Value("${server.http-port}")
+ private int httpPort = 0;
+
@Bean
public Policies getPolicies() {
return new Policies();
@@ -76,14 +80,16 @@
@Bean
public ServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
- tomcat.addAdditionalTomcatConnectors(getHttpConnector());
+ if (httpPort > 0) {
+ tomcat.addAdditionalTomcatConnectors(getHttpConnector(httpPort));
+ }
return tomcat;
}
- private static Connector getHttpConnector() {
+ private static Connector getHttpConnector(int httpPort) {
Connector connector = new Connector(TomcatServletWebServerFactory.DEFAULT_PROTOCOL);
connector.setScheme("http");
- connector.setPort(8081);
+ connector.setPort(httpPort);
connector.setSecure(false);
return connector;
}
diff --git a/policy-agent/src/main/java/org/oransc/policyagent/clients/A1ClientFactory.java b/policy-agent/src/main/java/org/oransc/policyagent/clients/A1ClientFactory.java
index 57ac980..322958a 100644
--- a/policy-agent/src/main/java/org/oransc/policyagent/clients/A1ClientFactory.java
+++ b/policy-agent/src/main/java/org/oransc/policyagent/clients/A1ClientFactory.java
@@ -69,10 +69,10 @@
A1Client createClient(Ric ric, A1ProtocolType version) throws ServiceException {
if (version == A1ProtocolType.STD_V1_1) {
assertNoControllerConfig(ric, version);
- return new StdA1ClientVersion1(ric.getConfig());
+ return new StdA1ClientVersion1(ric.getConfig(), this.appConfig.getWebClientConfig());
} else if (version == A1ProtocolType.OSC_V1) {
assertNoControllerConfig(ric, version);
- return new OscA1Client(ric.getConfig());
+ return new OscA1Client(ric.getConfig(), this.appConfig.getWebClientConfig());
} else if (version == A1ProtocolType.SDNC_OSC_STD_V1_1 || version == A1ProtocolType.SDNC_OSC_OSC_V1) {
return new SdncOscA1Client(version, ric.getConfig(), getControllerConfig(ric));
} else if (version == A1ProtocolType.SDNC_ONAP) {
diff --git a/policy-agent/src/main/java/org/oransc/policyagent/clients/AsyncRestClient.java b/policy-agent/src/main/java/org/oransc/policyagent/clients/AsyncRestClient.java
index 750b074..cefc7ca 100644
--- a/policy-agent/src/main/java/org/oransc/policyagent/clients/AsyncRestClient.java
+++ b/policy-agent/src/main/java/org/oransc/policyagent/clients/AsyncRestClient.java
@@ -27,17 +27,29 @@
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.handler.timeout.WriteTimeoutHandler;
+import java.io.FileInputStream;
+import java.io.IOException;
import java.lang.invoke.MethodHandles;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.X509Certificate;
+import java.util.Collections;
+import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
+import java.util.stream.Collectors;
-import javax.net.ssl.SSLException;
-
+import org.oransc.policyagent.configuration.ImmutableWebClientConfig;
+import org.oransc.policyagent.configuration.WebClientConfig;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.reactive.ReactorClientHttpConnector;
import org.springframework.lang.Nullable;
+import org.springframework.util.ResourceUtils;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
import org.springframework.web.reactive.function.client.WebClientResponseException;
@@ -54,9 +66,16 @@
private WebClient webClient = null;
private final String baseUrl;
private static final AtomicInteger sequenceNumber = new AtomicInteger();
+ private final WebClientConfig clientConfig;
public AsyncRestClient(String baseUrl) {
+ this(baseUrl,
+ ImmutableWebClientConfig.builder().isTrustStoreUsed(false).trustStore("").trustStorePassword("").build());
+ }
+
+ public AsyncRestClient(String baseUrl, WebClientConfig config) {
this.baseUrl = baseUrl;
+ this.clientConfig = config;
}
public Mono<ResponseEntity<String>> postForEntity(String uri, @Nullable String body) {
@@ -185,13 +204,53 @@
}
}
- private static SslContext createSslContext() throws SSLException {
+ private boolean isCertificateEntry(KeyStore trustStore, String alias) {
+ try {
+ return trustStore.isCertificateEntry(alias);
+ } catch (KeyStoreException e) {
+ logger.error("Error reading truststore {}", e.getMessage());
+ return false;
+ }
+ }
+
+ private Certificate getCertificate(KeyStore trustStore, String alias) {
+ try {
+ return trustStore.getCertificate(alias);
+ } catch (KeyStoreException e) {
+ logger.error("Error reading truststore {}", e.getMessage());
+ return null;
+ }
+ }
+
+ SslContext createSslContextSecure(String trustStorePath, String trustStorePass)
+ throws NoSuchAlgorithmException, CertificateException, IOException, KeyStoreException {
+
+ final KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType());
+ trustStore.load(new FileInputStream(ResourceUtils.getFile(trustStorePath)), trustStorePass.toCharArray());
+
+ List<Certificate> certificateList = Collections.list(trustStore.aliases()).stream() //
+ .filter(alias -> isCertificateEntry(trustStore, alias)) //
+ .map(alias -> getCertificate(trustStore, alias)) //
+ .collect(Collectors.toList());
+ final X509Certificate[] certificates = certificateList.toArray(new X509Certificate[certificateList.size()]);
+
return SslContextBuilder.forClient() //
- .trustManager(InsecureTrustManagerFactory.INSTANCE) //
+ .trustManager(certificates) //
.build();
}
- private static WebClient createWebClient(String baseUrl, SslContext sslContext) {
+ private SslContext createSslContext()
+ throws NoSuchAlgorithmException, CertificateException, KeyStoreException, IOException {
+ if (this.clientConfig.isTrustStoreUsed()) {
+ return createSslContextSecure(this.clientConfig.trustStore(), this.clientConfig.trustStorePassword());
+ } else {
+ return SslContextBuilder.forClient() //
+ .trustManager(InsecureTrustManagerFactory.INSTANCE) //
+ .build();
+ }
+ }
+
+ private WebClient createWebClient(String baseUrl, SslContext sslContext) {
TcpClient tcpClient = TcpClient.create() //
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 10_000) //
.secure(c -> c.sslContext(sslContext)) //
@@ -213,7 +272,7 @@
try {
SslContext sslContext = createSslContext();
this.webClient = createWebClient(this.baseUrl, sslContext);
- } catch (SSLException e) {
+ } catch (Exception e) {
logger.error("Could not create WebClient {}", e.getMessage());
return Mono.error(e);
}
diff --git a/policy-agent/src/main/java/org/oransc/policyagent/clients/OscA1Client.java b/policy-agent/src/main/java/org/oransc/policyagent/clients/OscA1Client.java
index 99e5bae..a388267 100644
--- a/policy-agent/src/main/java/org/oransc/policyagent/clients/OscA1Client.java
+++ b/policy-agent/src/main/java/org/oransc/policyagent/clients/OscA1Client.java
@@ -25,6 +25,7 @@
import org.json.JSONObject;
import org.oransc.policyagent.configuration.RicConfig;
+import org.oransc.policyagent.configuration.WebClientConfig;
import org.oransc.policyagent.repository.Policy;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -116,8 +117,8 @@
private final AsyncRestClient restClient;
private final UriBuilder uri;
- public OscA1Client(RicConfig ricConfig) {
- this(ricConfig, new AsyncRestClient(""));
+ public OscA1Client(RicConfig ricConfig, WebClientConfig clientConfig) {
+ this(ricConfig, new AsyncRestClient("", clientConfig));
}
public OscA1Client(RicConfig ricConfig, AsyncRestClient restClient) {
diff --git a/policy-agent/src/main/java/org/oransc/policyagent/clients/StdA1ClientVersion1.java b/policy-agent/src/main/java/org/oransc/policyagent/clients/StdA1ClientVersion1.java
index f5486a5..4ebc25c 100644
--- a/policy-agent/src/main/java/org/oransc/policyagent/clients/StdA1ClientVersion1.java
+++ b/policy-agent/src/main/java/org/oransc/policyagent/clients/StdA1ClientVersion1.java
@@ -24,6 +24,7 @@
import java.util.List;
import org.oransc.policyagent.configuration.RicConfig;
+import org.oransc.policyagent.configuration.WebClientConfig;
import org.oransc.policyagent.repository.Policy;
import reactor.core.publisher.Flux;
@@ -84,8 +85,8 @@
private final AsyncRestClient restClient;
private final UriBuilder uri;
- public StdA1ClientVersion1(RicConfig ricConfig) {
- this(new AsyncRestClient(""), ricConfig);
+ public StdA1ClientVersion1(RicConfig ricConfig, WebClientConfig webClientConfig) {
+ this(new AsyncRestClient("", webClientConfig), ricConfig);
}
public StdA1ClientVersion1(AsyncRestClient restClient, RicConfig ricConfig) {
diff --git a/policy-agent/src/main/java/org/oransc/policyagent/configuration/ApplicationConfig.java b/policy-agent/src/main/java/org/oransc/policyagent/configuration/ApplicationConfig.java
index 052a96c..6c2c91b 100644
--- a/policy-agent/src/main/java/org/oransc/policyagent/configuration/ApplicationConfig.java
+++ b/policy-agent/src/main/java/org/oransc/policyagent/configuration/ApplicationConfig.java
@@ -31,15 +31,27 @@
import lombok.Getter;
import org.oransc.policyagent.exceptions.ServiceException;
+import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import reactor.core.publisher.Flux;
@EnableConfigurationProperties
-@ConfigurationProperties("app")
+@ConfigurationProperties()
public class ApplicationConfig {
@NotEmpty
- private String filepath;
+ @Getter
+ @Value("${app.filepath}")
+ private String localConfigurationFilePath;
+
+ @Value("${app.webclient.trust-store-used}")
+ private boolean sslTrustStoreUsed = false;
+
+ @Value("${app.webclient.trust-store-password}")
+ private String sslTrustStorePassword = "";
+
+ @Value("${app.webclient.trust-store}")
+ private String sslTrustStore = "";
private Map<String, RicConfig> ricConfigs = new HashMap<>();
@Getter
@@ -49,21 +61,18 @@
private Map<String, ControllerConfig> controllerConfigs = new HashMap<>();
- public String getLocalConfigurationFilePath() {
- return this.filepath;
- }
-
- /*
- * Do not remove, used by framework!
- */
- public synchronized void setFilepath(String filepath) {
- this.filepath = filepath;
- }
-
public synchronized Collection<RicConfig> getRicConfigs() {
return this.ricConfigs.values();
}
+ public WebClientConfig getWebClientConfig() {
+ return ImmutableWebClientConfig.builder() //
+ .isTrustStoreUsed(this.sslTrustStoreUsed) //
+ .trustStore(this.sslTrustStore) //
+ .trustStorePassword(this.sslTrustStorePassword) //
+ .build();
+ }
+
public synchronized ControllerConfig getControllerConfig(String name) throws ServiceException {
ControllerConfig controllerConfig = this.controllerConfigs.get(name);
if (controllerConfig == null) {
diff --git a/policy-agent/src/main/java/org/oransc/policyagent/configuration/WebClientConfig.java b/policy-agent/src/main/java/org/oransc/policyagent/configuration/WebClientConfig.java
new file mode 100644
index 0000000..5f49498
--- /dev/null
+++ b/policy-agent/src/main/java/org/oransc/policyagent/configuration/WebClientConfig.java
@@ -0,0 +1,35 @@
+/*-
+ * ========================LICENSE_START=================================
+ * O-RAN-SC
+ * %%
+ * Copyright (C) 2020 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===================================
+ */
+
+package org.oransc.policyagent.configuration;
+
+import org.immutables.value.Value;
+
+@Value.Immutable
+@Value.Style(redactedMask = "####")
+public interface WebClientConfig {
+ public boolean isTrustStoreUsed();
+
+ @Value.Redacted
+ public String trustStorePassword();
+
+ public String trustStore();
+
+}
diff --git a/policy-agent/src/main/java/org/oransc/policyagent/dmaap/DmaapMessageConsumer.java b/policy-agent/src/main/java/org/oransc/policyagent/dmaap/DmaapMessageConsumer.java
index f13ffeb..011b977 100644
--- a/policy-agent/src/main/java/org/oransc/policyagent/dmaap/DmaapMessageConsumer.java
+++ b/policy-agent/src/main/java/org/oransc/policyagent/dmaap/DmaapMessageConsumer.java
@@ -64,8 +64,8 @@
private DmaapMessageHandler dmaapMessageHandler = null;
private MRConsumer messageRouterConsumer = null;
- @Value("${server.port}")
- private int localServerPort;
+ @Value("${server.http-port}")
+ private int localServerHttpPort;
@Autowired
public DmaapMessageConsumer(ApplicationConfig applicationConfig) {
@@ -139,7 +139,7 @@
protected DmaapMessageHandler getDmaapMessageHandler() throws IOException {
if (this.dmaapMessageHandler == null) {
- String agentBaseUrl = "https://localhost:" + this.localServerPort;
+ String agentBaseUrl = "http://localhost:" + this.localServerHttpPort;
AsyncRestClient agentClient = new AsyncRestClient(agentBaseUrl);
Properties dmaapPublisherProperties = applicationConfig.getDmaapPublisherConfig();
MRBatchingPublisher producer = MRClientFactory.createBatchingPublisher(dmaapPublisherProperties);
diff --git a/policy-agent/src/main/resources/keystore.jks b/policy-agent/src/main/resources/keystore.jks
index 3cd6bb7..4df793d 100644
--- a/policy-agent/src/main/resources/keystore.jks
+++ b/policy-agent/src/main/resources/keystore.jks
Binary files differ
diff --git a/policy-agent/src/test/java/org/oransc/policyagent/ApplicationTest.java b/policy-agent/src/test/java/org/oransc/policyagent/ApplicationTest.java
index 0fd4a33..0966257 100644
--- a/policy-agent/src/test/java/org/oransc/policyagent/ApplicationTest.java
+++ b/policy-agent/src/test/java/org/oransc/policyagent/ApplicationTest.java
@@ -110,6 +110,9 @@
RicSupervision supervision;
@Autowired
+ ApplicationConfig applicationConfig;
+
+ @Autowired
Services services;
private static Gson gson = new GsonBuilder() //
@@ -723,7 +726,7 @@
}
private AsyncRestClient restClient() {
- return new AsyncRestClient(baseUrl());
+ return new AsyncRestClient(baseUrl(), this.applicationConfig.getWebClientConfig());
}
private void testErrorCode(Mono<?> request, HttpStatus expStatus) {
diff --git a/policy-agent/src/test/java/org/oransc/policyagent/MockPolicyAgent.java b/policy-agent/src/test/java/org/oransc/policyagent/MockPolicyAgent.java
index 1636418..7f57ceb 100644
--- a/policy-agent/src/test/java/org/oransc/policyagent/MockPolicyAgent.java
+++ b/policy-agent/src/test/java/org/oransc/policyagent/MockPolicyAgent.java
@@ -67,6 +67,9 @@
@Autowired
PolicyTypes policyTypes;
+ @Autowired
+ ApplicationConfig applicationConfig;
+
static class MockApplicationConfig extends ApplicationConfig {
@Override
public String getLocalConfigurationFilePath() {
@@ -195,7 +198,8 @@
}
@Test
- @SuppressWarnings("squid:S2699") // Tests should include assertions. This test is only for keeping the server alive,
+ @SuppressWarnings("squid:S2699") // Tests should include assertions. This test is only for keeping the server
+ // alive,
// so it will only be confusing to add an assertion.
public void runMock() throws Exception {
keepServerAlive();