Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Introduce "minimum supported Client version" configuration parameter for Server Proxy #1741

Merged
merged 10 commits into from
Aug 28, 2023
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,6 @@ public void initialize(InitialServerConfDto configDto) {
tokenPinValidator.validateSoftwareTokenPin(configDto.getSoftwareTokenPin().toCharArray());
}


if (!isServerAddressInitialized) {
systemParameterService.updateOrCreateParameter(
SystemParameterService.CENTRAL_SERVER_ADDRESS,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,7 @@ public final class ErrorCodes {
public static final String X_INVALID_CLIENT_IDENTIFIER =
"InvalidClientIdentifier";
public static final String X_INVALID_SERVICE_TYPE = "ServiceType";
public static final String X_CLIENT_PROXY_VERSION_NOT_SUPPORTED = "ClientProxyVersionNotSupported";

// ASiC container related errors

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,10 @@ private SystemProperties() {
private static final String SERVERPROXY_CONNECTOR_SO_LINGER =
PREFIX + "proxy.server-connector-so-linger";

/** Property name of the server's minimum supported client version */
private static final String SERVERPROXY_MIN_SUPPORTED_CLIENT_VERSION =
PREFIX + "proxy.server-min-supported-client-version";

private static final String SERVERPROXY_SUPPORT_CLIENTS_POOLED_CONNECTIONS =
PREFIX + "proxy.server-support-clients-pooled-connections";

Expand Down Expand Up @@ -1376,7 +1380,7 @@ public static String[] getXroadTLSCipherSuites() {
* @return true if PIN policy should be enforced.
*/
public static boolean shouldEnforceTokenPinPolicy() {
return Boolean.valueOf(System.getProperty(SIGNER_ENFORCE_TOKEN_PIN_POLICY,
return Boolean.parseBoolean(System.getProperty(SIGNER_ENFORCE_TOKEN_PIN_POLICY,
DEFAULT_SIGNER_ENFORCE_TOKEN_PIN_POLICY));
}

Expand Down Expand Up @@ -1411,7 +1415,6 @@ public static long getServerProxyConnectorInitialIdleTime() {
public static int getServerProxyConnectorMaxIdleTime() {
return Integer.parseInt(System.getProperty(SERVERPROXY_CONNECTOR_MAX_IDLE_TIME,
DEFAULT_SERVERPROXY_CONNECTOR_MAX_IDLE_TIME));

}

/**
Expand All @@ -1426,6 +1429,10 @@ public static int getServerProxyConnectorSoLinger() {
return -1;
}

public static String getServerProxyMinSupportedClientVersion() {
return System.getProperty(SERVERPROXY_MIN_SUPPORTED_CLIENT_VERSION);
}

/**
* @return the connection maximum idle time that should be set for client proxy apache HttpClient
*/
Expand Down
2 changes: 2 additions & 0 deletions src/proxy/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ dependencies {

implementation "com.fasterxml.jackson.dataformat:jackson-dataformat-yaml"

implementation 'org.semver4j:semver4j:5.0.0'

testImplementation project(':common:common-test')
testImplementation project(path: ':common:common-util', configuration: 'testArtifacts')

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
/*
* The MIT License
*
* Copyright (c) 2019- Nordic Institute for Interoperability Solutions (NIIS)
* Copyright (c) 2018 Estonian Information System Authority (RIA),
* Nordic Institute for Interoperability Solutions (NIIS), Population Register Centre (VRK)
* Copyright (c) 2015-2017 Estonian Information System Authority (RIA), Population Register Centre (VRK)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package ee.ria.xroad.proxy.serverproxy;

import ee.ria.xroad.common.CodedException;
import ee.ria.xroad.common.SystemProperties;
import ee.ria.xroad.common.util.MimeUtils;
import ee.ria.xroad.proxy.ProxyMain;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.semver4j.Semver;

import javax.servlet.http.HttpServletRequest;

import java.util.Optional;

import static ee.ria.xroad.common.ErrorCodes.X_CLIENT_PROXY_VERSION_NOT_SUPPORTED;

@Slf4j
public final class ClientProxyVersionVerifier {
private static final String UNKNOWN_VERSION = "unknown";
private static final Semver MIN_SUPPORTED_CLIENT_VERSION;

static {
MIN_SUPPORTED_CLIENT_VERSION = Optional.ofNullable(SystemProperties.getServerProxyMinSupportedClientVersion())
.map(Semver::new)
.orElse(null);
}

private ClientProxyVersionVerifier() {
}

public static void check(HttpServletRequest request) {
String clientVersion = getVersion(request.getHeader(MimeUtils.HEADER_PROXY_VERSION));

log.info("Received request from {} (security server version: {})", request.getRemoteAddr(), clientVersion);

if (MIN_SUPPORTED_CLIENT_VERSION != null && MIN_SUPPORTED_CLIENT_VERSION.isGreaterThan(new Semver(clientVersion))) {
throw new CodedException(X_CLIENT_PROXY_VERSION_NOT_SUPPORTED,
"The minimum supported version for client security server is: %s ", MIN_SUPPORTED_CLIENT_VERSION.toString());
}

String thisVersion = getVersion(ProxyMain.readProxyVersion());
if (!clientVersion.equals(thisVersion)) {
log.warn("Peer security server version ({}) does not match host security server version ({})", clientVersion, thisVersion);
}
}

private static String getVersion(String value) {
return !StringUtils.isBlank(value) ? value : UNKNOWN_VERSION;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,11 @@
import ee.ria.xroad.common.monitoring.MonitorAgent;
import ee.ria.xroad.common.opmonitoring.OpMonitoringData;
import ee.ria.xroad.common.util.HandlerBase;
import ee.ria.xroad.common.util.MimeUtils;
import ee.ria.xroad.common.util.PerformanceLogger;
import ee.ria.xroad.proxy.ProxyMain;
import ee.ria.xroad.proxy.opmonitoring.OpMonitoring;
import ee.ria.xroad.proxy.util.MessageProcessorBase;

import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.HttpClient;
import org.eclipse.jetty.server.Request;

Expand All @@ -62,8 +59,6 @@
@Slf4j
class ServerProxyHandler extends HandlerBase {

private static final String UNKNOWN_VERSION = "unknown";

private final HttpClient client;
private final HttpClient opMonitorClient;
private final long idleTimeout = SystemProperties.getServerProxyConnectorMaxIdleTime();
Expand Down Expand Up @@ -93,7 +88,7 @@ public void handle(String target, Request baseRequest, final HttpServletRequest

GlobalConf.verifyValidity();

logProxyVersion(request);
ClientProxyVersionVerifier.check(request);
baseRequest.getHttpChannel().setIdleTimeout(idleTimeout);
final MessageProcessorBase processor = createRequestProcessor(request, response, opMonitoringData);
processor.process();
Expand Down Expand Up @@ -124,7 +119,7 @@ public void handle(String target, Request baseRequest, final HttpServletRequest
}

private MessageProcessorBase createRequestProcessor(HttpServletRequest request, HttpServletResponse response,
OpMonitoringData opMonitoringData) throws Exception {
OpMonitoringData opMonitoringData) {

if (VALUE_MESSAGE_TYPE_REST.equals(request.getHeader(HEADER_MESSAGE_TYPE))) {
return new ServerRestMessageProcessor(request, response, client, getClientSslCertChain(request),
Expand All @@ -142,23 +137,7 @@ protected void failure(HttpServletRequest request, HttpServletResponse response,
sendErrorResponse(request, response, e);
}

private static void logProxyVersion(HttpServletRequest request) {
String thatVersion = getVersion(request.getHeader(MimeUtils.HEADER_PROXY_VERSION));
String thisVersion = getVersion(ProxyMain.readProxyVersion());

log.info("Received request from {} (security server version: {})", request.getRemoteAddr(), thatVersion);

if (!thatVersion.equals(thisVersion)) {
log.warn("Peer security server version ({}) does not match host security server version ({})", thatVersion,
thisVersion);
}
}

private static String getVersion(String value) {
return !StringUtils.isBlank(value) ? value : UNKNOWN_VERSION;
}

private static X509Certificate[] getClientSslCertChain(HttpServletRequest request) throws Exception {
private static X509Certificate[] getClientSslCertChain(HttpServletRequest request) {
Object attribute = request.getAttribute("javax.servlet.request.X509Certificate");

if (attribute != null) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*
* The MIT License
* Copyright (c) 2019- Nordic Institute for Interoperability Solutions (NIIS)
* Copyright (c) 2018 Estonian Information System Authority (RIA),
* Nordic Institute for Interoperability Solutions (NIIS), Population Register Centre (VRK)
* Copyright (c) 2015-2017 Estonian Information System Authority (RIA), Population Register Centre (VRK)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package ee.ria.xroad.proxy.serverproxy;

import ee.ria.xroad.common.CodedException;
import ee.ria.xroad.common.util.MimeUtils;

import org.junit.Test;

import javax.servlet.http.HttpServletRequest;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ClientProxyVersionVerifierTest {
private static final String CLIENT_VERSION_6_26_3 = "6.26.3";
private static final String CLIENT_VERSION_7_1_3 = "7.1.3";
private static final String VERSION_7_1_3 = "7.1.3";
private static final String MIN_SUPPORTED_CLIENT_VERSION = "xroad.proxy.server-min-supported-client-version";

@Test
public void whenMinSupportedClientVersionPropertyIsEmptyThenShouldPassClientProxyVersionCheck() {
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getHeader(MimeUtils.HEADER_PROXY_VERSION)).thenReturn(CLIENT_VERSION_7_1_3);

ClientProxyVersionVerifier.check(request);
}

@Test
public void shouldPassClientProxyVersionCheck() {
System.setProperty(MIN_SUPPORTED_CLIENT_VERSION, VERSION_7_1_3);
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getHeader(MimeUtils.HEADER_PROXY_VERSION)).thenReturn(CLIENT_VERSION_7_1_3);

ClientProxyVersionVerifier.check(request);
}

@Test
public void shouldRaiseError() {
System.setProperty(MIN_SUPPORTED_CLIENT_VERSION, VERSION_7_1_3);
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getHeader(MimeUtils.HEADER_PROXY_VERSION)).thenReturn(CLIENT_VERSION_6_26_3);

CodedException exception = assertThrows(CodedException.class, () -> ClientProxyVersionVerifier.check(request));
assertEquals("ClientProxyVersionNotSupported: The minimum supported version for client security server is: 7.1.3 ",
exception.getMessage());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* The MIT License
* Copyright (c) 2019- Nordic Institute for Interoperability Solutions (NIIS)
* Copyright (c) 2018 Estonian Information System Authority (RIA),
* Nordic Institute for Interoperability Solutions (NIIS), Population Register Centre (VRK)
* Copyright (c) 2015-2017 Estonian Information System Authority (RIA), Population Register Centre (VRK)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package ee.ria.xroad.proxy.serverproxy;

import ee.ria.xroad.common.conf.globalconf.GlobalConf;

import org.apache.http.client.HttpClient;
import org.eclipse.jetty.server.Request;
import org.junit.Test;
import org.mockito.MockedStatic;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.io.IOException;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.mockStatic;
import static org.mockito.Mockito.when;

public class ServerProxyHandlerTest {

@Test
public void shouldExecuteClientProxyVersionCheck() throws Exception {
final HttpServletRequest request = getMockedRequest();
final Request baseRequest = mock(Request.class);
ServerProxyHandler serverProxyHandler = new ServerProxyHandler(mock(HttpClient.class), mock(HttpClient.class));

try (MockedStatic<GlobalConf> globalConfMock = mockStatic(GlobalConf.class);
MockedStatic<ClientProxyVersionVerifier> checkMock = mockStatic(ClientProxyVersionVerifier.class)) {
globalConfMock.when(GlobalConf::verifyValidity).then(invocationOnMock -> null);
checkMock.when(() -> ClientProxyVersionVerifier.check(any()))
.thenAnswer(invocation -> null);

serverProxyHandler.handle("target", baseRequest, request, getMockedResponse());

checkMock.verify(() -> ClientProxyVersionVerifier.check(any()));
}
}

private HttpServletRequest getMockedRequest() {
final HttpServletRequest request = mock(HttpServletRequest.class);
when(request.getRemoteAddr()).thenReturn("remoteAddr");
when(request.getMethod()).thenReturn("POST");
return request;
}

private HttpServletResponse getMockedResponse() throws IOException {
final HttpServletResponse response = mock(HttpServletResponse.class);
when(response.getOutputStream()).thenReturn(mock(ServletOutputStream.class));
return response;
}
}
Loading