diff options
Diffstat (limited to 'gui-server/src/main/java/org')
8 files changed, 542 insertions, 0 deletions
diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/GuiServerApplication.java b/gui-server/src/main/java/org/onap/policy/gui/server/GuiServerApplication.java new file mode 100644 index 0000000..7da2dd6 --- /dev/null +++ b/gui-server/src/main/java/org/onap/policy/gui/server/GuiServerApplication.java @@ -0,0 +1,33 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.ComponentScan; + +@SpringBootApplication +@ComponentScan(basePackages = "org.onap.policy.gui") +public class GuiServerApplication { + public static void main(String[] args) { + SpringApplication.run(GuiServerApplication.class, args); + } +} diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig.java b/gui-server/src/main/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig.java new file mode 100644 index 0000000..8d501d2 --- /dev/null +++ b/gui-server/src/main/java/org/onap/policy/gui/server/config/ClampRestTemplateConfig.java @@ -0,0 +1,94 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.config; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import javax.annotation.PostConstruct; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import org.apache.http.conn.ssl.NoopHostnameVerifier; +import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.TrustAllStrategy; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContextBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.Resource; +import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; +import org.springframework.web.client.RestTemplate; + +@Configuration +public class ClampRestTemplateConfig { + private static final Logger LOG = LoggerFactory.getLogger(ClampRestTemplateConfig.class); + + @Value("${clamp.disable-ssl-validation:false}") + private boolean disableSslValidation; + + @Value("${clamp.disable-ssl-hostname-check:false}") + private boolean disableSslHostnameCheck; + + @Value("${server.ssl.trust-store:#{null}}") + private Resource trustStore; + + @Value("${server.ssl.trust-store-password:#{null}}") + private char[] trustStorePassword; + + @PostConstruct + private void validateProperties() { + if (trustStore == null && !disableSslValidation) { + throw new IllegalArgumentException("server.ssl.trust-store must be set if SSL validation is enabled"); + } + if (disableSslValidation && !disableSslHostnameCheck) { + LOG.info("Disabling SSL hostname check as SSL validation is disabled"); + disableSslHostnameCheck = true; + } + } + + /** + * Returns a RestTemplate, optionally disabling SSL hostname check or disabling SSL validation entirely. + */ + @Bean + public RestTemplate clampRestTemplate() throws GeneralSecurityException, IOException { + SSLContext sslContext; + if (disableSslValidation) { + sslContext = new SSLContextBuilder().loadTrustMaterial(new TrustAllStrategy()).build(); + } else { + sslContext = new SSLContextBuilder().loadTrustMaterial(trustStore.getURL(), trustStorePassword).build(); + } + + HostnameVerifier hostnameVerifier; + if (disableSslHostnameCheck) { + hostnameVerifier = new NoopHostnameVerifier(); + } else { + hostnameVerifier = SSLConnectionSocketFactory.getDefaultHostnameVerifier(); + } + + var csf = new SSLConnectionSocketFactory(sslContext, hostnameVerifier); + var httpClient = HttpClients.custom().setSSLSocketFactory(csf).build(); + var requestFactory = new HttpComponentsClientHttpRequestFactory(); + requestFactory.setHttpClient(httpClient); + return new RestTemplate(requestFactory); + } +} diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/config/FilterRegistrationConfig.java b/gui-server/src/main/java/org/onap/policy/gui/server/config/FilterRegistrationConfig.java new file mode 100644 index 0000000..3e62237 --- /dev/null +++ b/gui-server/src/main/java/org/onap/policy/gui/server/config/FilterRegistrationConfig.java @@ -0,0 +1,42 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.config; + +import org.onap.policy.gui.server.filters.ClientSslHeaderFilter; +import org.springframework.boot.web.servlet.FilterRegistrationBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class FilterRegistrationConfig { + + /** + * Registers ClientSslToHeaderFilter for /clamp/restservices/*. + */ + @Bean + public FilterRegistrationBean<ClientSslHeaderFilter> clientSslHeaderFilter() { + FilterRegistrationBean<ClientSslHeaderFilter> registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new ClientSslHeaderFilter()); + registrationBean.addUrlPatterns("/clamp/restservices/*"); + return registrationBean; + } + +} diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/config/StaticContentConfig.java b/gui-server/src/main/java/org/onap/policy/gui/server/config/StaticContentConfig.java new file mode 100644 index 0000000..479202d --- /dev/null +++ b/gui-server/src/main/java/org/onap/policy/gui/server/config/StaticContentConfig.java @@ -0,0 +1,38 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class StaticContentConfig implements WebMvcConfigurer { + + @Override + public void addViewControllers(ViewControllerRegistry registry) { + registry.addViewController("/clamp").setViewName("redirect:/clamp/"); + registry.addViewController("/clamp/").setViewName("forward:/clamp/index.html"); + registry.addViewController("/apex-editor").setViewName("redirect:/apex-editor/"); + registry.addViewController("/apex-editor/").setViewName("forward:/apex-editor/index.html"); + } + +} diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilter.java b/gui-server/src/main/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilter.java new file mode 100644 index 0000000..db8f593 --- /dev/null +++ b/gui-server/src/main/java/org/onap/policy/gui/server/filters/ClientSslHeaderFilter.java @@ -0,0 +1,146 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.filters; + +import static org.onap.policy.gui.server.util.X509CertificateEncoder.urlEncodeCert; + +import java.io.IOException; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateExpiredException; +import java.security.cert.CertificateNotYetValidException; +import java.security.cert.X509Certificate; +import java.util.Collections; +import java.util.Enumeration; +import java.util.Set; +import java.util.TreeSet; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletRequestWrapper; +import javax.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.annotation.Order; +import org.springframework.web.filter.OncePerRequestFilter; + +/** + * Filter which encodes a client SSL certificate into X-SSL-Cert HTTP header. + * CLAMP has a corresponding filter called ClampCadiFilter which decodes the + * header. This is needed as CLAMP runtime uses AAF for auth, and AAF uses + * client cert authentication. Since REST requests from CLAMP GUI to CLAMP + * runtime are proxied in gui-server, the proxy needs to attach a copy of the + * client SSL cert, as the proxy could not know the client's private key. + */ +@Order(1) +public class ClientSslHeaderFilter extends OncePerRequestFilter { + private static final Logger LOG = LoggerFactory.getLogger(ClientSslHeaderFilter.class); + + // Name of attribute containing request SSL cert. + public static final String X509_ATTRIBUTE_NAME = "javax.servlet.request.X509Certificate"; + + // Name of header containing encoded SSL cert - also used in clamp's ClampCadiFilter. + public static final String SSL_CERT_HEADER_NAME = "X-SSL-Cert"; + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + var wrappedRequest = new ClientSslHeaderRequestWrapper(request); + var certs = (X509Certificate[]) request.getAttribute(X509_ATTRIBUTE_NAME); + if (certs != null && certs.length > 0) { + try { + certs[0].checkValidity(); + wrappedRequest.setSslCertHeader(urlEncodeCert(certs[0])); + } catch (CertificateEncodingException e) { + LOG.error("Error encoding client SSL cert", e); + } catch (CertificateExpiredException | CertificateNotYetValidException e) { + LOG.info("Client SSL cert expired", e); + } + } + filterChain.doFilter(wrappedRequest, response); + } + + /* + * This class wraps a HttpServletRequest so that X-SSL-Cert header can be added. + */ + private static class ClientSslHeaderRequestWrapper extends HttpServletRequestWrapper { + private String encodedSslCert = null; + + public ClientSslHeaderRequestWrapper(HttpServletRequest request) { + super(request); + } + + public void setSslCertHeader(String encodedSslCert) { + this.encodedSslCert = encodedSslCert; + } + + /** + * Returns the value of the specified request header as a String. + * The header name is case insensitive. + */ + @Override + public String getHeader(String name) { + if (SSL_CERT_HEADER_NAME.equalsIgnoreCase(name)) { + return encodedSslCert; + } else { + return super.getHeader(name); + } + } + + /** + * Returns all the values of the specified request header as an Enumeration + * of String objects. + * Some headers, such as Accept-Language can be sent by clients as several + * headers each with a different value rather than sending the header as a + * comma separated list. The header name is case insensitive. + */ + @Override + public Enumeration<String> getHeaders(String name) { + if (SSL_CERT_HEADER_NAME.equalsIgnoreCase(name)) { + if (encodedSslCert != null) { + return Collections.enumeration(Collections.singletonList(encodedSslCert)); + } else { + return Collections.emptyEnumeration(); + } + } else { + return super.getHeaders(name); + } + } + + /** + * Returns an enumeration of all the header names this request contains. + * If the request has no headers, this method returns an empty enumeration. + */ + @Override + public Enumeration<String> getHeaderNames() { + Set<String> names = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + names.addAll(Collections.list(super.getHeaderNames())); + if (encodedSslCert != null) { + names.add(SSL_CERT_HEADER_NAME); + } else { + // This is needed to prevent an exploit where a user passes their own + // X-SSL-Cert header, possibly bypassing client cert verification. + names.remove(SSL_CERT_HEADER_NAME); + } + return Collections.enumeration(names); + } + } +} diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/rest/ApexEditorRestController.java b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ApexEditorRestController.java new file mode 100644 index 0000000..a4b92ef --- /dev/null +++ b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ApexEditorRestController.java @@ -0,0 +1,41 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.rest; + +import javax.servlet.http.HttpServletRequest; +import org.springframework.ui.ModelMap; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.servlet.ModelAndView; + +@RestController +@RequestMapping("/apex-editor/policy/gui/v*/apex/editor") +public class ApexEditorRestController { + + /** + * Strip /apex-editor prefix from Apex Editor rest calls. + */ + @RequestMapping("/**") + public ModelAndView forwardApexEditorRest(ModelMap model, HttpServletRequest request) { + String targetUrl = request.getRequestURI().replaceFirst("^/apex-editor", ""); + return new ModelAndView("forward:" + targetUrl, model); + } +} diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/rest/ClampRestController.java b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ClampRestController.java new file mode 100644 index 0000000..1975f37 --- /dev/null +++ b/gui-server/src/main/java/org/onap/policy/gui/server/rest/ClampRestController.java @@ -0,0 +1,78 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.rest; + +import java.net.URI; +import javax.servlet.http.HttpServletRequest; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.client.HttpStatusCodeException; +import org.springframework.web.client.RestTemplate; +import org.springframework.web.util.UriComponentsBuilder; + +@RestController +@RequestMapping("/clamp/restservices") +public class ClampRestController { + + @Value("${clamp.url}") + private URI clampUrl; + + @Autowired + @Qualifier("clampRestTemplate") + private RestTemplate restTemplate; + + /** + * Proxy rest calls to clamp backend. + */ + @RequestMapping("/**") + public ResponseEntity<String> mirrorRest(@RequestBody(required = false) String body, + @RequestHeader HttpHeaders headers, + HttpMethod method, + HttpServletRequest request) { + // Strip /clamp/ prefix from request URI. + String requestUri = request.getRequestURI().replaceFirst("^/clamp/", ""); + URI uri = UriComponentsBuilder.fromUri(clampUrl) + .path(requestUri) + .query(request.getQueryString()) + .build(true).toUri(); + + HttpEntity<String> httpEntity = new HttpEntity<>(body, headers); + try { + return restTemplate.exchange(uri, method, httpEntity, String.class); + + } catch (HttpStatusCodeException e) { + // On error, return the backend error code instead of 500. + return ResponseEntity.status(e.getRawStatusCode()) + .headers(e.getResponseHeaders()) + .body(e.getResponseBodyAsString()); + } + } + +} diff --git a/gui-server/src/main/java/org/onap/policy/gui/server/util/X509CertificateEncoder.java b/gui-server/src/main/java/org/onap/policy/gui/server/util/X509CertificateEncoder.java new file mode 100644 index 0000000..67da719 --- /dev/null +++ b/gui-server/src/main/java/org/onap/policy/gui/server/util/X509CertificateEncoder.java @@ -0,0 +1,70 @@ +/*- + * ============LICENSE_START======================================================= + * Copyright (C) 2022 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. + * + * SPDX-License-Identifier: Apache-2.0 + * ============LICENSE_END========================================================= + */ + +package org.onap.policy.gui.server.util; + +import java.io.ByteArrayInputStream; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Base64; + +/** + * Helper methods for encoding/decoding X509Certificates from PEM strings and URL-encoded PEM strings. + */ +public class X509CertificateEncoder { + private static final String BEGIN_CERT = "-----BEGIN CERTIFICATE-----\n"; + private static final String END_CERT = "\n-----END CERTIFICATE-----"; + + private X509CertificateEncoder() {} + + /** + * Returns a PEM string from an X509Certificate. + */ + public static String getPemFromCert(X509Certificate cert) throws CertificateEncodingException { + return BEGIN_CERT + Base64.getEncoder().encodeToString(cert.getEncoded()) + END_CERT; + } + + /** + * Returns an X509Certificate from a PEM string. + */ + public static X509Certificate getCertFromPem(String pem) throws CertificateException { + return (X509Certificate) CertificateFactory.getInstance("X.509") + .generateCertificate(new ByteArrayInputStream(pem.getBytes())); + } + + /** + * Returns URL-encoded PEM string from an X509Certificate, suitable as a HTTP header. + */ + public static String urlEncodeCert(X509Certificate cert) throws CertificateEncodingException { + return URLEncoder.encode(getPemFromCert(cert), StandardCharsets.UTF_8); + } + + /** + * Returns an X509Certificate from a URL-encoded PEM string. + */ + public static X509Certificate urlDecodeCert(String encodedPem) throws CertificateException { + return getCertFromPem(URLDecoder.decode(encodedPem, StandardCharsets.UTF_8)); + } +} |