From: PatrikBuhr Date: Thu, 7 May 2020 10:30:21 +0000 (+0200) Subject: Added upport for trust store when agent acts as web client X-Git-Tag: 2.0.0~43 X-Git-Url: https://gerrit.o-ran-sc.org/r/gitweb?a=commitdiff_plain;h=6d503afd38bdf9823bda3dfe3d307adaeb6f7eee;p=nonrtric.git 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 --- diff --git a/policy-agent/config/application.yaml b/policy-agent/config/application.yaml index 4010219b..55a1d64c 100644 --- a/policy-agent/config/application.yaml +++ b/policy-agent/config/application.yaml @@ -21,12 +21,17 @@ logging: 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 e2874cb7..1e01247a 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.Policies; 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 @@ import org.springframework.context.annotation.Configuration; 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 @@ class BeanFactory { @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 57ac9809..322958a6 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 @@ public class A1ClientFactory { 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 750b074d..cefc7ca8 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.ssl.util.InsecureTrustManagerFactory; 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 @@ public class AsyncRestClient { 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> postForEntity(String uri, @Nullable String body) { @@ -185,13 +204,53 @@ public class AsyncRestClient { } } - 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 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 @@ public class AsyncRestClient { 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 99e5bae4..a388267e 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 java.util.List; 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 @@ public class OscA1Client implements A1Client { 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 f5486a58..4ebc25c6 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.Arrays; 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 @@ public class StdA1ClientVersion1 implements A1Client { 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 052a96ca..6c2c91b2 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 javax.validation.constraints.NotEmpty; 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 ricConfigs = new HashMap<>(); @Getter @@ -49,21 +61,18 @@ public class ApplicationConfig { private Map 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 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 00000000..5f494983 --- /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 f13ffebd..011b9779 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 @@ public class DmaapMessageConsumer { 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 @@ public class DmaapMessageConsumer { 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 3cd6bb79..4df793d5 100644 Binary files a/policy-agent/src/main/resources/keystore.jks and b/policy-agent/src/main/resources/keystore.jks 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 0fd4a335..09662575 100644 --- a/policy-agent/src/test/java/org/oransc/policyagent/ApplicationTest.java +++ b/policy-agent/src/test/java/org/oransc/policyagent/ApplicationTest.java @@ -109,6 +109,9 @@ public class ApplicationTest { @Autowired RicSupervision supervision; + @Autowired + ApplicationConfig applicationConfig; + @Autowired Services services; @@ -723,7 +726,7 @@ public class ApplicationTest { } 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 16364181..7f57ceb2 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 @@ public class MockPolicyAgent { @Autowired PolicyTypes policyTypes; + @Autowired + ApplicationConfig applicationConfig; + static class MockApplicationConfig extends ApplicationConfig { @Override public String getLocalConfigurationFilePath() { @@ -195,7 +198,8 @@ public class MockPolicyAgent { } @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();