From 4212017188b2bf7ec741647cf23c536b0c97f15b Mon Sep 17 00:00:00 2001 From: Jim Hahn Date: Mon, 23 Aug 2021 15:53:55 -0400 Subject: [PATCH] Add filter to control xacml-pdp rest api Added a filter class for the REST server that only allows "API" services (i.e., decision API services) through when the API is enabled, disallowing them otherwise. The filter always allows PDP-wide services (e.g., "healthcheck"). Per review comments: - modified the new class to "implement Filter" rather than "extends AafFilter" Issue-ID: POLICY-3531 Change-Id: I7055e21045eea270e454a47a443b29476d9a85ee Signed-off-by: Jim Hahn --- .../java/org/onap/policy/pdpx/main/XacmlState.java | 4 +- .../pdpx/main/rest/XacmlPdpServiceFilter.java | 91 ++++++++++++ .../pdpx/main/startstop/XacmlPdpActivator.java | 30 ++-- .../pdpx/main/startstop/XacmlPdpRestServer.java | 9 +- .../java/org/onap/policy/pdpx/main/CommonRest.java | 2 +- .../org/onap/policy/pdpx/main/XacmlStateTest.java | 4 +- .../main/rest/TestAbbreviateDecisionResults.java | 2 +- .../onap/policy/pdpx/main/rest/TestDecision.java | 4 +- .../pdpx/main/rest/TestXacmlPdpServiceFilter.java | 161 +++++++++++++++++++++ .../pdpx/main/startstop/TestXacmlPdpActivator.java | 14 +- 10 files changed, 287 insertions(+), 34 deletions(-) create mode 100644 main/src/main/java/org/onap/policy/pdpx/main/rest/XacmlPdpServiceFilter.java create mode 100644 main/src/test/java/org/onap/policy/pdpx/main/rest/TestXacmlPdpServiceFilter.java diff --git a/main/src/main/java/org/onap/policy/pdpx/main/XacmlState.java b/main/src/main/java/org/onap/policy/pdpx/main/XacmlState.java index d1e326f1..a2c8ca94 100644 --- a/main/src/main/java/org/onap/policy/pdpx/main/XacmlState.java +++ b/main/src/main/java/org/onap/policy/pdpx/main/XacmlState.java @@ -179,10 +179,10 @@ public class XacmlState { private void handleXacmlRestController() { if (status.getState() == PdpState.ACTIVE) { LOGGER.info("State change: {} - Starting rest controller", status.getState()); - XacmlPdpActivator.getCurrent().startXacmlRestController(); + XacmlPdpActivator.getCurrent().enableApi(); } else if (status.getState() == PdpState.PASSIVE) { LOGGER.info("State change: {} - Stopping rest controller", status.getState()); - XacmlPdpActivator.getCurrent().stopXacmlRestController(); + XacmlPdpActivator.getCurrent().disableApi(); } else { // unsupported state LOGGER.warn("Unsupported state: {}", status.getState()); diff --git a/main/src/main/java/org/onap/policy/pdpx/main/rest/XacmlPdpServiceFilter.java b/main/src/main/java/org/onap/policy/pdpx/main/rest/XacmlPdpServiceFilter.java new file mode 100644 index 00000000..50dafd52 --- /dev/null +++ b/main/src/main/java/org/onap/policy/pdpx/main/rest/XacmlPdpServiceFilter.java @@ -0,0 +1,91 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2021 AT&T Intellectual Property. 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.policy.pdpx.main.rest; + +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** + * Filter that verifies that the API services (i.e., decision services) are enabled + * before allowing the request through. + */ +public class XacmlPdpServiceFilter implements Filter { + + /** + * Services the are always available, even when the API is disabled. + */ + public static final Set PERMANENT_SERVICES = Set.of("healthcheck", "statistics"); + + + private static final AtomicBoolean apiDisabled = new AtomicBoolean(true); + + + @Override + public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) + throws IOException, ServletException { + + HttpServletRequest request = (HttpServletRequest) servletRequest; + HttpServletResponse response = (HttpServletResponse) servletResponse; + + if (apiDisabled.get() && !PERMANENT_SERVICES.contains(getUriSuffix(request))) { + response.setStatus(HttpServletResponse.SC_CONFLICT); + } else { + filterChain.doFilter(servletRequest, servletResponse); + } + } + + private String getUriSuffix(HttpServletRequest request) { + String uri = request.getRequestURI(); + int index = uri.lastIndexOf('/'); + return (index < 0 ? uri : uri.substring(index + 1)); + } + + /** + * Determines if API services are enabled. + * + * @return {@code true}, if API services are enabled + */ + public static boolean isApiEnabled() { + return !apiDisabled.get(); + } + + /** + * Enables the API services. + */ + public static void enableApi() { + apiDisabled.set(false); + } + + /** + * Disables the API services. + */ + public static void disableApi() { + apiDisabled.set(true); + } +} diff --git a/main/src/main/java/org/onap/policy/pdpx/main/startstop/XacmlPdpActivator.java b/main/src/main/java/org/onap/policy/pdpx/main/startstop/XacmlPdpActivator.java index 892b3835..531374d0 100644 --- a/main/src/main/java/org/onap/policy/pdpx/main/startstop/XacmlPdpActivator.java +++ b/main/src/main/java/org/onap/policy/pdpx/main/startstop/XacmlPdpActivator.java @@ -20,6 +20,7 @@ package org.onap.policy.pdpx.main.startstop; +import java.util.List; import lombok.Getter; import lombok.Setter; import org.onap.policy.common.endpoints.event.comm.TopicEndpointManager; @@ -44,6 +45,7 @@ import org.onap.policy.pdpx.main.parameters.XacmlPdpParameterGroup; import org.onap.policy.pdpx.main.rest.XacmlPdpAafFilter; import org.onap.policy.pdpx.main.rest.XacmlPdpApplicationManager; import org.onap.policy.pdpx.main.rest.XacmlPdpRestController; +import org.onap.policy.pdpx.main.rest.XacmlPdpServiceFilter; import org.onap.policy.pdpx.main.rest.XacmlPdpStatisticsManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -127,8 +129,11 @@ public class XacmlPdpActivator extends ServiceManagerContainer { msgDispatcher.register(PdpMessageType.PDP_UPDATE.name(), new XacmlPdpUpdateListener(sinkClient, state, heartbeat, appmgr)); + XacmlPdpServiceFilter.disableApi(); + restServer = new XacmlPdpRestServer(xacmlPdpParameterGroup.getRestServerParameters(), - XacmlPdpAafFilter.class, XacmlPdpRestController.class); + List.of(XacmlPdpServiceFilter.class, XacmlPdpAafFilter.class), + List.of(XacmlPdpRestController.class)); } catch (RuntimeException | HttpClientConfigException | BidirectionalTopicClientException e) { throw new PolicyXacmlPdpRuntimeException(e.getMessage(), e); @@ -158,6 +163,9 @@ public class XacmlPdpActivator extends ServiceManagerContainer { heartbeat::terminate); // @formatter:on + addAction("REST Server", + restServer::start, + restServer::stop); } /* @@ -212,26 +220,18 @@ public class XacmlPdpActivator extends ServiceManagerContainer { /** * Start the xacmlpdp rest controller. */ - public void startXacmlRestController() { - if (isXacmlRestControllerAlive()) { - LOGGER.info("Xacml rest controller already running"); - } else { - restServer.start(); - } + public void enableApi() { + XacmlPdpServiceFilter.enableApi(); } /** * Stop the xacmlpdp rest controller. */ - public void stopXacmlRestController() { - if (isXacmlRestControllerAlive()) { - restServer.stop(); - } else { - LOGGER.info("Xacml rest controller already stopped"); - } + public void disableApi() { + XacmlPdpServiceFilter.disableApi(); } - public boolean isXacmlRestControllerAlive() { - return restServer.isAlive(); + public boolean isApiEnabled() { + return XacmlPdpServiceFilter.isApiEnabled(); } } diff --git a/main/src/main/java/org/onap/policy/pdpx/main/startstop/XacmlPdpRestServer.java b/main/src/main/java/org/onap/policy/pdpx/main/startstop/XacmlPdpRestServer.java index 487253b2..683d013e 100644 --- a/main/src/main/java/org/onap/policy/pdpx/main/startstop/XacmlPdpRestServer.java +++ b/main/src/main/java/org/onap/policy/pdpx/main/startstop/XacmlPdpRestServer.java @@ -20,12 +20,13 @@ package org.onap.policy.pdpx.main.startstop; +import java.util.List; import java.util.Properties; +import javax.servlet.Filter; import org.onap.policy.common.endpoints.http.server.JsonExceptionMapper; import org.onap.policy.common.endpoints.http.server.RestServer; import org.onap.policy.common.endpoints.http.server.YamlExceptionMapper; import org.onap.policy.common.endpoints.http.server.YamlMessageBodyHandler; -import org.onap.policy.common.endpoints.http.server.aaf.AafAuthFilter; import org.onap.policy.common.endpoints.parameters.RestServerParameters; import org.onap.policy.common.endpoints.properties.PolicyEndPointProperties; import org.onap.policy.common.gson.GsonMessageBodyHandler; @@ -45,13 +46,13 @@ public class XacmlPdpRestServer extends RestServer { * Constructs the object. * * @param restServerParameters the rest server parameters - * @param aafFilter class of object to use to filter AAF requests, or {@code null} + * @param filters class of object to use to filter requests, or {@code null} * @param jaxrsProviders classes providing the services */ public XacmlPdpRestServer(final RestServerParameters restServerParameters, - Class aafFilter, Class... jaxrsProviders) { + List> filters, List> jaxrsProviders) { - super(restServerParameters, aafFilter, jaxrsProviders); + super(restServerParameters, filters, jaxrsProviders); } @Override diff --git a/main/src/test/java/org/onap/policy/pdpx/main/CommonRest.java b/main/src/test/java/org/onap/policy/pdpx/main/CommonRest.java index 938fe581..422d4336 100644 --- a/main/src/test/java/org/onap/policy/pdpx/main/CommonRest.java +++ b/main/src/test/java/org/onap/policy/pdpx/main/CommonRest.java @@ -122,7 +122,7 @@ public class CommonRest { main = new Main(xacmlPdpConfigParameters); // start xacml rest controller - XacmlPdpActivator.getCurrent().startXacmlRestController(); + XacmlPdpActivator.getCurrent().enableApi(); if (!NetworkUtil.isTcpPortOpen("localhost", port, 20, 1000L)) { throw new IllegalStateException("server is not listening on port " + port); diff --git a/main/src/test/java/org/onap/policy/pdpx/main/XacmlStateTest.java b/main/src/test/java/org/onap/policy/pdpx/main/XacmlStateTest.java index 5ff3d5c7..0b8d1404 100644 --- a/main/src/test/java/org/onap/policy/pdpx/main/XacmlStateTest.java +++ b/main/src/test/java/org/onap/policy/pdpx/main/XacmlStateTest.java @@ -130,12 +130,12 @@ public class XacmlStateTest { req.setState(PdpState.ACTIVE); status = state.updateInternalState(req); assertEquals(PdpState.ACTIVE, status.getState()); - verify(act).startXacmlRestController(); + verify(act).enableApi(); req.setState(PdpState.PASSIVE); status = state.updateInternalState(req); assertEquals(PdpState.PASSIVE, status.getState()); - verify(act).stopXacmlRestController(); + verify(act).disableApi(); } @Test diff --git a/main/src/test/java/org/onap/policy/pdpx/main/rest/TestAbbreviateDecisionResults.java b/main/src/test/java/org/onap/policy/pdpx/main/rest/TestAbbreviateDecisionResults.java index 8d80b832..3e525e91 100644 --- a/main/src/test/java/org/onap/policy/pdpx/main/rest/TestAbbreviateDecisionResults.java +++ b/main/src/test/java/org/onap/policy/pdpx/main/rest/TestAbbreviateDecisionResults.java @@ -135,7 +135,7 @@ public class TestAbbreviateDecisionResults { // Start the service // main = startXacmlPdpService(fileParams); - XacmlPdpActivator.getCurrent().startXacmlRestController(); + XacmlPdpActivator.getCurrent().enableApi(); // // Make sure it is running // diff --git a/main/src/test/java/org/onap/policy/pdpx/main/rest/TestDecision.java b/main/src/test/java/org/onap/policy/pdpx/main/rest/TestDecision.java index 77e8873f..fb7d7179 100644 --- a/main/src/test/java/org/onap/policy/pdpx/main/rest/TestDecision.java +++ b/main/src/test/java/org/onap/policy/pdpx/main/rest/TestDecision.java @@ -124,7 +124,7 @@ public class TestDecision { // Start the service // main = startXacmlPdpService(fileParams); - XacmlPdpActivator.getCurrent().startXacmlRestController(); + XacmlPdpActivator.getCurrent().enableApi(); // // Make sure it is running // @@ -260,4 +260,4 @@ public class TestDecision { LOGGER.error("Failed to copy {} to {}", source, dest); } } -} \ No newline at end of file +} diff --git a/main/src/test/java/org/onap/policy/pdpx/main/rest/TestXacmlPdpServiceFilter.java b/main/src/test/java/org/onap/policy/pdpx/main/rest/TestXacmlPdpServiceFilter.java new file mode 100644 index 00000000..9f098f78 --- /dev/null +++ b/main/src/test/java/org/onap/policy/pdpx/main/rest/TestXacmlPdpServiceFilter.java @@ -0,0 +1,161 @@ +/*- + * ============LICENSE_START======================================================= + * ONAP + * ================================================================================ + * Copyright (C) 2021 AT&T Intellectual Property. 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.policy.pdpx.main.rest; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import javax.servlet.FilterChain; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class TestXacmlPdpServiceFilter { + + // pick an arbitrary service + private static final String PERM_SVC = XacmlPdpServiceFilter.PERMANENT_SERVICES.iterator().next(); + + @Mock + private HttpServletRequest request; + + @Mock + private HttpServletResponse response; + + private FilterChain filterChain; + + private XacmlPdpServiceFilter filter; + + + /** + * Initializes the fields. + */ + @Before + public void setUp() { + XacmlPdpServiceFilter.disableApi(); + + filterChain = (req, resp) -> { + HttpServletResponse resp2 = (HttpServletResponse) resp; + resp2.setStatus(HttpServletResponse.SC_OK); + }; + + filter = new XacmlPdpServiceFilter(); + } + + @Test + public void testDoFilter() throws Exception { + XacmlPdpServiceFilter.enableApi(); + lenient().when(request.getRequestURI()).thenReturn("/other"); + assertThat(getFilterResponse()).isEqualTo(HttpServletResponse.SC_OK); + } + + /** + * Tests doFilter() when the API is disabled, but a permanent service is requested. + */ + @Test + public void testDoFilter_DisabledPermanentServiceReq() throws Exception { + XacmlPdpServiceFilter.disableApi(); + when(request.getRequestURI()).thenReturn(PERM_SVC); + assertThat(getFilterResponse()).isEqualTo(HttpServletResponse.SC_OK); + } + + /** + * Tests doFilter() when the API is disabled, but a permanent service is requested, with a leading slash. + */ + @Test + public void testDoFilter_DisabledPermanentServiceReqLeadingSlash() throws Exception { + XacmlPdpServiceFilter.disableApi(); + when(request.getRequestURI()).thenReturn("/" + PERM_SVC); + assertThat(getFilterResponse()).isEqualTo(HttpServletResponse.SC_OK); + } + + /** + * Tests doFilter() when the API is disabled, but a permanent service is requested, with extra URI prefix. + */ + @Test + public void testDoFilter_DisabledPermanentServiceReqExtraUri() throws Exception { + XacmlPdpServiceFilter.disableApi(); + when(request.getRequestURI()).thenReturn("/some/stuff/" + PERM_SVC); + assertThat(getFilterResponse()).isEqualTo(HttpServletResponse.SC_OK); + } + + /** + * Tests doFilter() when the API is disabled, but a permanent service is requested, with extra characters before + * the service name. + */ + @Test + public void testDoFilter_DisabledPermanentServiceReqExtraChars() throws Exception { + XacmlPdpServiceFilter.disableApi(); + when(request.getRequestURI()).thenReturn("/ExtraStuff" + PERM_SVC); + assertThat(getFilterResponse()).isEqualTo(HttpServletResponse.SC_CONFLICT); + } + + /** + * Tests doFilter() when the API is disabled and an API service is requested. + */ + @Test + public void testDoFilter_DisabledApiReq() throws Exception { + XacmlPdpServiceFilter.disableApi(); + when(request.getRequestURI()).thenReturn("/other"); + assertThat(getFilterResponse()).isEqualTo(HttpServletResponse.SC_CONFLICT); + } + + /** + * Tests doFilter() when the API is disabled and an API service is requested. + */ + @Test + public void testDoFilter_EnabledApiReq() throws Exception { + XacmlPdpServiceFilter.enableApi(); + lenient().when(request.getRequestURI()).thenReturn("/other"); + assertThat(getFilterResponse()).isEqualTo(HttpServletResponse.SC_OK); + } + + @Test + public void testEnableApi_testDisableApi_testIsApiEnabled() { + + XacmlPdpServiceFilter.enableApi(); + assertThat(XacmlPdpServiceFilter.isApiEnabled()).isTrue(); + + XacmlPdpServiceFilter.disableApi(); + assertThat(XacmlPdpServiceFilter.isApiEnabled()).isFalse(); + } + + /** + * Invokes doFilter(). + * @return the response code set by the filter + */ + private int getFilterResponse() throws Exception { + filter.doFilter(request, response, filterChain); + + // should only be called once + var responseCode = ArgumentCaptor.forClass(Integer.class); + verify(response).setStatus(responseCode.capture()); + + return responseCode.getValue(); + } +} diff --git a/main/src/test/java/org/onap/policy/pdpx/main/startstop/TestXacmlPdpActivator.java b/main/src/test/java/org/onap/policy/pdpx/main/startstop/TestXacmlPdpActivator.java index c874761d..ff084047 100644 --- a/main/src/test/java/org/onap/policy/pdpx/main/startstop/TestXacmlPdpActivator.java +++ b/main/src/test/java/org/onap/policy/pdpx/main/startstop/TestXacmlPdpActivator.java @@ -1,6 +1,6 @@ /*- * ============LICENSE_START======================================================= - * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. + * Copyright (C) 2019, 2021 AT&T Intellectual Property. All rights reserved. * Modifications Copyright (C) 2019 Nordix Foundation. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); @@ -77,21 +77,21 @@ public class TestXacmlPdpActivator extends CommonRest { @Test public void testXacmlPdpActivator() throws Exception { assertFalse(activator.isAlive()); - assertFalse(activator.isXacmlRestControllerAlive()); + assertFalse(activator.isApiEnabled()); activator.start(); assertTrue(activator.isAlive()); // XacmlPdp starts in PASSIVE state so the rest controller should not be alive - assertFalse(activator.isXacmlRestControllerAlive()); + assertFalse(activator.isApiEnabled()); assertTrue(activator.getParameterGroup().isValid()); assertEquals(CommonTestData.PDPX_PARAMETER_GROUP_NAME, activator.getParameterGroup().getName()); assertEquals(CommonTestData.PDPX_GROUP, activator.getParameterGroup().getPdpGroup()); - activator.startXacmlRestController(); - assertTrue(activator.isXacmlRestControllerAlive()); + activator.enableApi(); + assertTrue(activator.isApiEnabled()); - activator.stopXacmlRestController(); - assertFalse(activator.isXacmlRestControllerAlive()); + activator.disableApi(); + assertFalse(activator.isApiEnabled()); } @Test -- 2.16.6