Collector authentication enhancement 88/84688/10
authorZlatko Murgoski <zlatko.murgoski@nokia.com>
Tue, 9 Apr 2019 13:32:52 +0000 (15:32 +0200)
committerZlatko Murgoski <zlatko.murgoski@nokia.com>
Mon, 15 Apr 2019 08:04:19 +0000 (10:04 +0200)
Add cert subject verifier

Change-Id: If2c3c0984e9eec63e2884ca17db953fff2719888
Issue-ID: DCAEGEN2-1101
Signed-off-by: Zlatko Murgoski <zlatko.murgoski@nokia.com>
12 files changed:
etc/certSubjectMatcher.properties [new file with mode: 0644]
etc/collector.properties
pom.xml
src/main/java/org/onap/dcae/ApplicationSettings.java
src/main/java/org/onap/dcae/common/configuration/CertAuth.java
src/main/java/org/onap/dcae/common/configuration/CertBasicAuth.java
src/main/java/org/onap/dcae/common/configuration/CustomFilter.java [new file with mode: 0644]
src/main/java/org/onap/dcae/restapi/ApiAuthInterceptor.java
src/test/java/org/onap/dcae/TLSTest.java
src/test/java/org/onap/dcae/TLSTestBase.java
src/test/resources/certSubjectMatcher [new file with mode: 0644]
version.properties

diff --git a/etc/certSubjectMatcher.properties b/etc/certSubjectMatcher.properties
new file mode 100644 (file)
index 0000000..9abb766
--- /dev/null
@@ -0,0 +1 @@
+.*
\ No newline at end of file
index 36c79b5..82ba595 100755 (executable)
@@ -40,6 +40,8 @@ header.authlist=sample1,$2a$10$0buh.2WeYwN868YMwnNNEuNEAMNYVU9.FSMJGyIKV3dGET/7o
 collector.keystore.file.location=etc/keystore\r
 collector.keystore.passwordfile=etc/passwordfile\r
 \r
+collector.cert.subject.matcher=etc/certSubjectMatcher.properties\r
+\r
 ## The truststore must be setup per installation when mutual tls support is configured\r
 collector.truststore.file.location=etc/truststore\r
 collector.truststore.passwordfile=etc/trustpasswordfile\r
diff --git a/pom.xml b/pom.xml
index 383595d..304221a 100644 (file)
--- a/pom.xml
+++ b/pom.xml
@@ -24,7 +24,7 @@
        </parent>\r
        <groupId>org.onap.dcaegen2.collectors.ves</groupId>\r
        <artifactId>VESCollector</artifactId>\r
-       <version>1.4.3-SNAPSHOT</version>\r
+       <version>1.4.4-SNAPSHOT</version>\r
        <name>dcaegen2-collectors-ves</name>\r
        <description>VESCollector</description>\r
        <properties>\r
index c4f2c06..61bcf4b 100644 (file)
@@ -183,11 +183,14 @@ public class ApplicationSettings {
     public String exceptionConfigFileLocation() {
         return properties.getString("exceptionConfig", null);
     }
-
     public String dMaaPConfigurationFileLocation() {
         return prependWithUserDirOnRelative(properties.getString("collector.dmaapfile", "etc/DmaapConfig.json"));
     }
 
+    public String certSubjectMatcher(){
+        return prependWithUserDirOnRelative(properties.getString("collector.cert.subject.matcher", "etc/certSubjectMatcher.properties"));
+    }
+
     public String authMethod(){
         return properties.getString("auth.method", AuthMethodType.NO_AUTH.value());
     }
index 3c4fb62..481fb5e 100644 (file)
 
 package org.onap.dcae.common.configuration;
 
+import org.onap.dcae.ApplicationException;
 import org.onap.dcae.ApplicationSettings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.boot.web.server.Ssl.ClientAuth;
 import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
 
-public class CertAuth implements AuthMethod {
+@Configuration
+@Order(0)
+@EnableWebSecurity
+public class CertAuth extends WebSecurityConfigurerAdapter implements AuthMethod {
 
   private static final Logger log = LoggerFactory.getLogger(CertAuth.class);
   private final ConfigurableServletWebServerFactory container;
@@ -38,6 +49,24 @@ public class CertAuth implements AuthMethod {
     this.properties = properties;
   }
 
+  @Override
+  public void configure(WebSecurity web) {
+    web.ignoring().anyRequest();
+  }
+
+  @Override
+  protected void configure(HttpSecurity http) {
+    try {
+      http.authorizeRequests()
+          .anyRequest().authenticated().and()
+          .addFilterBefore(new CustomFilter(properties), FilterSecurityInterceptor.class);
+
+    } catch (Exception ex) {
+      log.error("Cannot authorize request cause: ",ex);
+      throw new ApplicationException(ex);
+    }
+  }
+
   @Override
   public void configure() {
     SslContextCreator sslContextCreator = new SslContextCreator(properties);
index f756b47..c9e0af4 100644 (file)
 
 package org.onap.dcae.common.configuration;
 
+import org.onap.dcae.ApplicationException;
 import org.onap.dcae.ApplicationSettings;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.boot.web.server.Ssl.ClientAuth;
 import org.springframework.boot.web.servlet.server.ConfigurableServletWebServerFactory;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.annotation.Order;
+import org.springframework.security.config.annotation.web.builders.HttpSecurity;
+import org.springframework.security.config.annotation.web.builders.WebSecurity;
+import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
+import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
+import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
 
-public class CertBasicAuth implements AuthMethod{
+@Configuration
+@Order(1)
+@EnableWebSecurity
+public class CertBasicAuth extends WebSecurityConfigurerAdapter implements AuthMethod{
 
   private static final Logger log = LoggerFactory.getLogger(CertAuth.class);
   private final ConfigurableServletWebServerFactory container;
@@ -38,6 +49,24 @@ public class CertBasicAuth implements AuthMethod{
     this.properties = properties;
   }
 
+  @Override
+  public void configure(WebSecurity web) {
+    web.ignoring().anyRequest();
+  }
+
+  @Override
+  protected void configure(HttpSecurity http) {
+    try {
+      http.authorizeRequests()
+          .anyRequest().authenticated().and()
+          .addFilterBefore(new CustomFilter(properties), FilterSecurityInterceptor.class);
+
+    } catch (Exception ex) {
+      log.error("Cannot authorize request cause: ",ex);
+      throw new ApplicationException(ex);
+    }
+  }
+
   @Override
   public void configure() {
     SslContextCreator sslContextCreator = new SslContextCreator(properties);
diff --git a/src/main/java/org/onap/dcae/common/configuration/CustomFilter.java b/src/main/java/org/onap/dcae/common/configuration/CustomFilter.java
new file mode 100644 (file)
index 0000000..ae693fa
--- /dev/null
@@ -0,0 +1,83 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * org.onap.dcaegen2.collectors.ves
+ * ================================================================================
+ * Copyright (C) 2019 Nokia. 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.common.configuration;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Paths;
+import java.security.cert.X509Certificate;
+import java.util.Arrays;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import javax.servlet.FilterChain;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+import org.onap.dcae.ApplicationSettings;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.web.filter.GenericFilterBean;
+
+@Configuration
+public class CustomFilter extends GenericFilterBean {
+
+  private static final String CERTIFICATE_X_509 = "javax.servlet.request.X509Certificate";
+  private static final String MESSAGE = "SubjectDN didn't match with any regexp from %s file like %s";
+  private ApplicationSettings properties;
+
+  public CustomFilter(ApplicationSettings properties) {
+    this.properties = properties;
+  }
+
+  @Override
+  public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
+      FilterChain filterChain) throws IOException, ServletException {
+
+    X509Certificate[] cert = (X509Certificate[]) servletRequest.getAttribute(CERTIFICATE_X_509);
+
+    if (cert != null) {
+      if (getLines().anyMatch(element -> Pattern.compile(element).matcher(getSubjectDN(cert)).find())) {
+        filterChain.doFilter(servletRequest, servletResponse);
+      } else {
+        setResponse((HttpServletResponse) servletResponse);
+      }
+    } else {
+      filterChain.doFilter(servletRequest, servletResponse);
+    }
+  }
+
+  private void setResponse(HttpServletResponse servletResponse) throws IOException {
+    HttpServletResponse response = servletResponse;
+    response.sendError(HttpServletResponse.SC_FORBIDDEN,
+        String.format(MESSAGE, properties.certSubjectMatcher(), getLines().collect(Collectors.joining(" "))));
+  }
+
+  private Stream<String> getLines() throws IOException {
+    return Files.lines(Paths.get(properties.certSubjectMatcher()));
+  }
+
+  private String getSubjectDN(X509Certificate[] certs) {
+    return Arrays.stream(certs).map(e -> e.getSubjectDN().getName())
+        .map(x -> x.split(",")).flatMap(Arrays::stream)
+        .collect(Collectors.joining(","));
+  }
+}
index e2ac74c..bb29057 100644 (file)
@@ -54,7 +54,6 @@ final class ApiAuthInterceptor extends HandlerInterceptorAdapter {
                 return true;
             }
         }
-
         if (isBasicAuth()) {
             String authorizationHeader = request.getHeader("Authorization");
             if (authorizationHeader == null || !isAuthorized(authorizationHeader)) {
index b1f9037..3cf0a16 100644 (file)
@@ -125,6 +125,7 @@ public class TLSTest extends TLSTestBase {
             when(settings.authMethod()).thenReturn(AuthMethodType.CERT_ONLY.value());
             when(settings.truststoreFileLocation()).thenReturn(TRUSTSTORE.toString());
             when(settings.truststorePasswordFileLocation()).thenReturn(TRUSTSTORE_PASSWORD_FILE.toString());
+            when(settings.certSubjectMatcher()).thenReturn(CERT_SUBJECT_MATCHER.toString());
         }
     }
 
index 8b486ec..4dada12 100644 (file)
@@ -45,12 +45,12 @@ 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 final Path CERT_SUBJECT_MATCHER = Paths.get(RESOURCES.toString(), "certSubjectMatcher");
 
     protected static abstract class ConfigurationBase {
         protected final ApplicationSettings settings = Mockito.mock(ApplicationSettings.class);
@@ -67,6 +67,7 @@ public class TLSTestBase {
 
     @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
     protected abstract class TestClassBase {
+
         @MockBean
         @Qualifier("inputQueue")
         protected LinkedBlockingQueue<JSONObject> queue;
diff --git a/src/test/resources/certSubjectMatcher b/src/test/resources/certSubjectMatcher
new file mode 100644 (file)
index 0000000..9abb766
--- /dev/null
@@ -0,0 +1 @@
+.*
\ No newline at end of file
index 44b6acf..9e50923 100644 (file)
@@ -1,6 +1,6 @@
 major=1
 minor=4
-patch=3
+patch=4
 base_version=${major}.${minor}.${patch}
 release_version=${base_version}
 snapshot_version=${base_version}-SNAPSHOT