From 5caf0800a080d3ac4fb9573b319336160b2ebcba Mon Sep 17 00:00:00 2001 From: Zlatko Murgoski Date: Wed, 17 Apr 2019 10:10:30 +0200 Subject: VES Collector - certBasicAuth https://jira.onap.org/browse/DCAEGEN2-1440 Issue-ID: DCAEGEN2-1440 Change-Id: I7976d03c65e261930533a49a6716fd6161124ad9 Signed-off-by: Zlatko Murgoski --- .../onap/dcae/common/configuration/CertAuth.java | 29 +------- .../dcae/common/configuration/CertBasicAuth.java | 32 +-------- .../dcae/common/configuration/CustomFilter.java | 83 ---------------------- .../dcae/common/configuration/SubjectMatcher.java | 65 +++++++++++++++++ .../org/onap/dcae/restapi/ApiAuthInterceptor.java | 64 +++++++++++++---- .../onap/dcae/restapi/ApiAuthInterceptionTest.java | 2 +- 6 files changed, 118 insertions(+), 157 deletions(-) delete mode 100644 src/main/java/org/onap/dcae/common/configuration/CustomFilter.java create mode 100644 src/main/java/org/onap/dcae/common/configuration/SubjectMatcher.java diff --git a/src/main/java/org/onap/dcae/common/configuration/CertAuth.java b/src/main/java/org/onap/dcae/common/configuration/CertAuth.java index 481fb5ec..6bd924c3 100644 --- a/src/main/java/org/onap/dcae/common/configuration/CertAuth.java +++ b/src/main/java/org/onap/dcae/common/configuration/CertAuth.java @@ -21,24 +21,15 @@ 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; @Configuration -@Order(0) -@EnableWebSecurity -public class CertAuth extends WebSecurityConfigurerAdapter implements AuthMethod { +public class CertAuth implements AuthMethod { private static final Logger log = LoggerFactory.getLogger(CertAuth.class); private final ConfigurableServletWebServerFactory container; @@ -49,24 +40,6 @@ public class CertAuth extends WebSecurityConfigurerAdapter 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/CertBasicAuth.java b/src/main/java/org/onap/dcae/common/configuration/CertBasicAuth.java index c9e0af41..38d5ad5b 100644 --- a/src/main/java/org/onap/dcae/common/configuration/CertBasicAuth.java +++ b/src/main/java/org/onap/dcae/common/configuration/CertBasicAuth.java @@ -21,24 +21,15 @@ 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; @Configuration -@Order(1) -@EnableWebSecurity -public class CertBasicAuth extends WebSecurityConfigurerAdapter implements AuthMethod{ +public class CertBasicAuth implements AuthMethod{ private static final Logger log = LoggerFactory.getLogger(CertAuth.class); private final ConfigurableServletWebServerFactory container; @@ -49,24 +40,6 @@ public class CertBasicAuth extends WebSecurityConfigurerAdapter implements AuthM 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); @@ -75,5 +48,4 @@ public class CertBasicAuth extends WebSecurityConfigurerAdapter implements AuthM log.info(String.format("Application work in %s mode on %s port.", properties.authMethod(), properties.httpsPort())); } -} - +} \ No newline at end of file diff --git a/src/main/java/org/onap/dcae/common/configuration/CustomFilter.java b/src/main/java/org/onap/dcae/common/configuration/CustomFilter.java deleted file mode 100644 index ae693fa6..00000000 --- a/src/main/java/org/onap/dcae/common/configuration/CustomFilter.java +++ /dev/null @@ -1,83 +0,0 @@ -/*- - * ============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 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(",")); - } -} diff --git a/src/main/java/org/onap/dcae/common/configuration/SubjectMatcher.java b/src/main/java/org/onap/dcae/common/configuration/SubjectMatcher.java new file mode 100644 index 00000000..9ab42211 --- /dev/null +++ b/src/main/java/org/onap/dcae/common/configuration/SubjectMatcher.java @@ -0,0 +1,65 @@ +/*- + * ============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 org.onap.dcae.ApplicationException; +import org.onap.dcae.ApplicationSettings; + +public class SubjectMatcher { + + private final ApplicationSettings properties; + private final X509Certificate[] cert; + + public SubjectMatcher(ApplicationSettings properties, X509Certificate[] cert) { + this.properties = properties; + this.cert = cert; + } + + public boolean match(){ + try { + return getLines().anyMatch(element -> Pattern.compile(element).matcher(getSubjectDN(cert)).find()); + } catch (IOException ex) { + throw new ApplicationException("Cannot read file cause: ", ex); + } + } + + public boolean isCert() { + return cert !=null; + } + + private Stream 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(",")); + } +} diff --git a/src/main/java/org/onap/dcae/restapi/ApiAuthInterceptor.java b/src/main/java/org/onap/dcae/restapi/ApiAuthInterceptor.java index bb290575..7d3d2929 100644 --- a/src/main/java/org/onap/dcae/restapi/ApiAuthInterceptor.java +++ b/src/main/java/org/onap/dcae/restapi/ApiAuthInterceptor.java @@ -21,11 +21,14 @@ package org.onap.dcae.restapi; import io.vavr.control.Option; import java.io.IOException; +import java.security.cert.X509Certificate; import java.util.Base64; +import java.util.stream.Collectors; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.onap.dcae.ApplicationSettings; import org.onap.dcae.common.configuration.AuthMethodType; +import org.onap.dcae.common.configuration.SubjectMatcher; import org.onap.dcaegen2.services.sdk.security.CryptPassword; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,11 +37,12 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; final class ApiAuthInterceptor extends HandlerInterceptorAdapter { private static final Logger LOG = LoggerFactory.getLogger(ApiAuthInterceptor.class); + 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"; private final CryptPassword cryptPassword = new CryptPassword(); private final ApplicationSettings settings; private Logger errorLogger; - public ApiAuthInterceptor(ApplicationSettings applicationSettings, Logger errorLogger) { this.settings = applicationSettings; this.errorLogger = errorLogger; @@ -48,25 +52,55 @@ final class ApiAuthInterceptor extends HandlerInterceptorAdapter { public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws IOException { - if(settings.authMethod().equalsIgnoreCase(AuthMethodType.CERT_BASIC_AUTH.value())){ - if (request.getAttribute("javax.servlet.request.X509Certificate") != null){ - LOG.info("Request is authorized by certificate "); - return true; - } + SubjectMatcher subjectMatcher = new SubjectMatcher(settings,(X509Certificate[]) request.getAttribute(CERTIFICATE_X_509)); + + if(settings.authMethod().equalsIgnoreCase(AuthMethodType.CERT_ONLY.value())){ + return validateCertRequest(response, subjectMatcher); + } + + if(isCertSubject(subjectMatcher)){ + return true; + } + + if (isBasicAuth() ) { + return validateBasicHeader(request, response); + } + return true; + } + + private boolean validateBasicHeader(HttpServletRequest request, HttpServletResponse response) + throws IOException { + String authorizationHeader = request.getHeader("Authorization"); + if (authorizationHeader == null || !isAuthorized(authorizationHeader)) { + response.setStatus(401); + errorLogger.error("EVENT_RECEIPT_FAILURE: Unauthorized user"); + response.getWriter().write(ApiException.UNAUTHORIZED_USER.toJSON().toString()); + return false; } - if (isBasicAuth()) { - String authorizationHeader = request.getHeader("Authorization"); - if (authorizationHeader == null || !isAuthorized(authorizationHeader)) { - response.setStatus(401); - errorLogger.error("EVENT_RECEIPT_FAILURE: Unauthorized user"); - response.getWriter().write(ApiException.UNAUTHORIZED_USER.toJSON().toString()); - return false; - } - LOG.info("Request is authorized by basic auth"); + LOG.info("Request is authorized by basic auth"); + return true; + } + + private boolean validateCertRequest(HttpServletResponse response, SubjectMatcher subjectMatcher) + throws IOException { + if (!isCertSubject(subjectMatcher)) { + response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.getWriter().write(String.format(MESSAGE, settings.certSubjectMatcher())); + return false; } + LOG.info("Cert and subjectDN is valid"); return true; } + private boolean isCertSubject(SubjectMatcher subjectMatcher) { + if(subjectMatcher.isCert() && subjectMatcher.match()){ + LOG.info("Cert and subjectDN is valid"); + return true; + } + LOG.info(String.format(MESSAGE, settings.certSubjectMatcher())); + return false; + } + private boolean isBasicAuth() { return settings.authMethod().equalsIgnoreCase(AuthMethodType.BASIC_AUTH.value()) || settings.authMethod().equalsIgnoreCase(AuthMethodType.CERT_BASIC_AUTH.value()); diff --git a/src/test/java/org/onap/dcae/restapi/ApiAuthInterceptionTest.java b/src/test/java/org/onap/dcae/restapi/ApiAuthInterceptionTest.java index a295046b..c0a06a07 100644 --- a/src/test/java/org/onap/dcae/restapi/ApiAuthInterceptionTest.java +++ b/src/test/java/org/onap/dcae/restapi/ApiAuthInterceptionTest.java @@ -140,7 +140,7 @@ public class ApiAuthInterceptionTest { public void shouldSucceed() throws IOException { // given final HttpServletRequest request = createRequestWithAuthorizationHeader(); - when(settings.authMethod()).thenReturn(AuthMethodType.CERT_ONLY.value()); + when(settings.authMethod()).thenReturn(AuthMethodType.BASIC_AUTH.value()); when(settings.validAuthorizationCredentials()).thenReturn( HashMap.of(USERNAME, "$2a$10$BsZkEynNm/93wbAeeZuxJeu6IHRyQl4XReqDg2BtYOFDhUsz20.3G")); when(response.getWriter()).thenReturn(writer); -- cgit 1.2.3-korg