This is an automated email from the ASF dual-hosted git repository. ilyak pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/ignite.git
The following commit(s) were added to refs/heads/master by this push: new 19ffc88 IGNITE-12752 Pass client certificates to security - Fixes #7508. 19ffc88 is described below commit 19ffc883d7cb25c0b998f9437cd6a927ff477b1f Author: Ilya Kasnacheev <ilya.kasnach...@gmail.com> AuthorDate: Mon Mar 16 13:06:48 2020 +0300 IGNITE-12752 Pass client certificates to security - Fixes #7508. Signed-off-by: Ilya Kasnacheev <ilya.kasnach...@gmail.com> --- .../rest/JettyRestProcessorCommonSelfTest.java | 25 ++- .../rest/protocols/tcp/TcpRestParserSelfTest.java | 1 + modules/clients/src/test/keystore/node0102.jks | Bin 0 -> 4618 bytes .../resources/jetty/rest-jetty-ssl-client-auth.xml | 92 ++++++++ .../ClientListenerAbstractConnectionContext.java | 8 +- .../odbc/ClientListenerConnectionContext.java | 5 +- .../processors/odbc/ClientListenerNioListener.java | 10 +- .../odbc/jdbc/JdbcConnectionContext.java | 14 +- .../odbc/odbc/OdbcConnectionContext.java | 12 +- .../platform/client/ClientConnectionContext.java | 6 +- .../processors/rest/GridRestProcessor.java | 1 + .../rest/protocols/tcp/GridTcpRestNioListener.java | 1 + .../processors/rest/request/GridRestRequest.java | 18 ++ .../ignite/internal/util/nio/GridNioSession.java | 6 + .../internal/util/nio/GridNioSessionImpl.java | 20 ++ .../internal/util/nio/ssl/GridNioSslFilter.java | 2 + .../plugin/security/AuthenticationContext.java | 21 ++ .../ignite/plugin/security/SecuritySubject.java | 10 + modules/core/src/test/config/tests.properties | 1 + .../client/ThinClientSslPermissionCheckTest.java | 237 +++++++++++++++++++++ .../TestCertificateSecurityPluginProvider.java | 40 ++++ ....java => TestCertificateSecurityProcessor.java} | 91 +++----- .../security/impl/TestSecurityProcessor.java | 1 + .../security/impl/TestSecuritySubject.java | 19 ++ .../util/nio/impl/GridNioFilterChainSelfTest.java | 139 ------------ .../internal/util/nio/impl}/MockNioSession.java | 8 +- .../tcp/TcpDiscoverySslTrustedUntrustedTest.java | 16 ++ .../ignite/testsuites/SecurityTestSuite.java | 2 + .../util/GridCommandHandlerClusterByClassTest.java | 4 +- ...andHandlerClusterByClassTest_cache_help.output} | 0 ...idCommandHandlerClusterByClassTest_help.output} | 0 ...lerClusterByClassWithSSLTest_cache_help.output} | 0 ...ndHandlerClusterByClassWithSSLTest_help.output} | 4 +- .../protocols/http/jetty/GridJettyRestHandler.java | 6 + parent/pom.xml | 3 +- 35 files changed, 586 insertions(+), 237 deletions(-) diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorCommonSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorCommonSelfTest.java index 1b93284..5f14e2c 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorCommonSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/JettyRestProcessorCommonSelfTest.java @@ -108,12 +108,7 @@ public abstract class JettyRestProcessorCommonSelfTest extends AbstractRestProce URL url = new URL(sb.toString()); - URLConnection conn = url.openConnection(); - - String signature = signature(); - - if (signature != null) - conn.setRequestProperty("X-Signature", signature); + URLConnection conn = openConnection(url); InputStream in = conn.getInputStream(); @@ -128,6 +123,24 @@ public abstract class JettyRestProcessorCommonSelfTest extends AbstractRestProce } /** + * Open REST connection, set signature header if needed. + * + * @param url URL to open. + * @return URL connection. + * @throws Exception If failed. + */ + protected URLConnection openConnection(URL url) throws Exception { + URLConnection conn = url.openConnection(); + + String signature = signature(); + + if (signature != null) + conn.setRequestProperty("X-Signature", signature); + + return conn; + } + + /** * @param cacheName Optional cache name. * @param cmd REST command. * @param params Command parameters. diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/TcpRestParserSelfTest.java b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/TcpRestParserSelfTest.java index 182149e..6780fa1 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/TcpRestParserSelfTest.java +++ b/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/TcpRestParserSelfTest.java @@ -29,6 +29,7 @@ import org.apache.ignite.internal.processors.rest.client.message.GridClientCache import org.apache.ignite.internal.processors.rest.client.message.GridClientHandshakeRequest; import org.apache.ignite.internal.processors.rest.client.message.GridClientMessage; import org.apache.ignite.internal.util.nio.GridNioSession; +import org.apache.ignite.internal.util.nio.impl.MockNioSession; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.testframework.GridTestUtils; diff --git a/modules/clients/src/test/keystore/node0102.jks b/modules/clients/src/test/keystore/node0102.jks new file mode 100644 index 0000000..3e7cfd7 Binary files /dev/null and b/modules/clients/src/test/keystore/node0102.jks differ diff --git a/modules/clients/src/test/resources/jetty/rest-jetty-ssl-client-auth.xml b/modules/clients/src/test/resources/jetty/rest-jetty-ssl-client-auth.xml new file mode 100644 index 0000000..a1bd0dd --- /dev/null +++ b/modules/clients/src/test/resources/jetty/rest-jetty-ssl-client-auth.xml @@ -0,0 +1,92 @@ +<?xml version="1.0"?> + +<!-- + Licensed to the Apache Software Foundation (ASF) under one or more + contributor license agreements. See the NOTICE file distributed with + this work for additional information regarding copyright ownership. + The ASF licenses this file to You 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. + --> + +<!DOCTYPE Configure PUBLIC "-//Jetty//Configure//EN" "http://www.eclipse.org/jetty/configure.dtd"> +<Configure id="Server" class="org.eclipse.jetty.server.Server"> + <Arg name="threadPool"> + <New class="org.eclipse.jetty.util.thread.QueuedThreadPool"> + <Set name="minThreads">5</Set> + <Set name="maxThreads">10</Set> + </New> + </Arg> + + <New id="httpsCfg" class="org.eclipse.jetty.server.HttpConfiguration"> + <Set name="secureScheme">https</Set> + <Set name="securePort"><SystemProperty name="IGNITE_JETTY_PORT" default="8091"/></Set> + <Set name="sendServerVersion">true</Set> + <Set name="sendDateHeader">true</Set> + <Call name="addCustomizer"> + <Arg><New class="org.eclipse.jetty.server.SecureRequestCustomizer"/></Arg> + </Call> + </New> + + <New id="sslContextFactory" class="org.eclipse.jetty.util.ssl.SslContextFactory$Server"> + <Set name="keyStorePath"><SystemProperty + name="IGNITE_HOME" default="${IGNITE_HOME}"/>/modules/clients/src/test/keystore/server.jks</Set> + <Set name="keyStorePassword">123456</Set> + <Set name="keyManagerPassword">123456</Set> + <Set name="trustStorePath"><SystemProperty + name="IGNITE_HOME" default="${IGNITE_HOME}"/>/modules/clients/src/test/keystore/trust-both.jks</Set> + <Set name="trustStorePassword">123456</Set> + <Set name="needClientAuth">true</Set> + </New> + + <Call name="addConnector"> + <Arg> + <New class="org.eclipse.jetty.server.ServerConnector"> + <Arg name="server"> + <Ref refid="Server"/> + </Arg> + <Arg name="factories"> + <Array type="org.eclipse.jetty.server.ConnectionFactory"> + <Item> + <New class="org.eclipse.jetty.server.SslConnectionFactory"> + <Arg><Ref refid="sslContextFactory"/></Arg> + <Arg>http/1.1</Arg> + </New> + </Item> + <Item> + <New class="org.eclipse.jetty.server.HttpConnectionFactory"> + <Arg><Ref refid="httpsCfg"/></Arg> + </New> + </Item> + </Array> + </Arg> + <Set name="host"><SystemProperty name="IGNITE_JETTY_HOST" default="localhost"/></Set> + <Set name="port"><SystemProperty name="IGNITE_JETTY_PORT" default="8091"/></Set> + <Set name="idleTimeout">30000</Set> + <Set name="reuseAddress">true</Set> + </New> + </Arg> + </Call> + + <Set name="handler"> + <New id="Handlers" class="org.eclipse.jetty.server.handler.HandlerCollection"> + <Set name="handlers"> + <Array type="org.eclipse.jetty.server.Handler"> + <Item> + <New id="Contexts" class="org.eclipse.jetty.server.handler.ContextHandlerCollection"/> + </Item> + </Array> + </Set> + </New> + </Set> + + <Set name="stopAtShutdown">false</Set> +</Configure> diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerAbstractConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerAbstractConnectionContext.java index 7220555..380c76b 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerAbstractConnectionContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerAbstractConnectionContext.java @@ -20,6 +20,7 @@ package org.apache.ignite.internal.processors.odbc; import java.util.Collections; import java.util.Map; import java.util.UUID; +import java.security.cert.Certificate; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.processors.authentication.AuthorizationContext; @@ -90,10 +91,10 @@ public abstract class ClientListenerAbstractConnectionContext implements ClientL * @return Auth context. * @throws IgniteCheckedException If failed. */ - protected AuthorizationContext authenticate(String user, String pwd) + protected AuthorizationContext authenticate(Certificate[] certificates, String user, String pwd) throws IgniteCheckedException { if (ctx.security().enabled()) - authCtx = authenticateExternal(user, pwd).authorizationContext(); + authCtx = authenticateExternal(certificates, user, pwd).authorizationContext(); else if (ctx.authentication().enabled()) { if (F.isEmpty(user)) throw new IgniteAccessControlException("Unauthenticated sessions are prohibited."); @@ -112,7 +113,7 @@ public abstract class ClientListenerAbstractConnectionContext implements ClientL /** * Do 3-rd party authentication. */ - private AuthenticationContext authenticateExternal(String user, String pwd) + private AuthenticationContext authenticateExternal(Certificate[] certificates, String user, String pwd) throws IgniteCheckedException { SecurityCredentials cred = new SecurityCredentials(user, pwd); @@ -122,6 +123,7 @@ public abstract class ClientListenerAbstractConnectionContext implements ClientL authCtx.subjectId(UUID.randomUUID()); authCtx.nodeAttributes(F.isEmpty(userAttrs) ? Collections.emptyMap() : userAttrs); authCtx.credentials(cred); + authCtx.certificates(certificates); secCtx = ctx.security().authenticate(authCtx); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java index 2a27986..d0cd6ea 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerConnectionContext.java @@ -21,6 +21,7 @@ import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.binary.BinaryReaderExImpl; import org.apache.ignite.internal.processors.authentication.AuthorizationContext; import org.apache.ignite.internal.processors.security.SecurityContext; +import org.apache.ignite.internal.util.nio.GridNioSession; import org.jetbrains.annotations.Nullable; /** @@ -46,11 +47,13 @@ public interface ClientListenerConnectionContext { /** * Initialize from handshake message. * + * + * @param ses NIO session. * @param ver Protocol version. * @param reader Reader set to the configuration part of the handshake message. * @throws IgniteCheckedException On error. */ - void initializeFromHandshake(ClientListenerProtocolVersion ver, BinaryReaderExImpl reader) + void initializeFromHandshake(GridNioSession ses, ClientListenerProtocolVersion ver, BinaryReaderExImpl reader) throws IgniteCheckedException; /** diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java index fb4dfda..1bb4ffa 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java @@ -306,12 +306,12 @@ public class ClientListenerNioListener extends GridNioServerListenerAdapter<byte ClientListenerConnectionContext connCtx = null; try { - connCtx = prepareContext(ses, clientType); + connCtx = prepareContext(clientType); ensureClientPermissions(clientType); if (connCtx.isVersionSupported(ver)) { - connCtx.initializeFromHandshake(ver, reader); + connCtx.initializeFromHandshake(ses, ver, reader); ses.addMeta(CONN_CTX_META_KEY, connCtx); } @@ -367,15 +367,15 @@ public class ClientListenerNioListener extends GridNioServerListenerAdapter<byte * @return Context. * @throws IgniteCheckedException If failed. */ - private ClientListenerConnectionContext prepareContext(GridNioSession ses, byte clientType) throws IgniteCheckedException { + private ClientListenerConnectionContext prepareContext(byte clientType) throws IgniteCheckedException { long connId = nextConnectionId(); switch (clientType) { case ODBC_CLIENT: - return new OdbcConnectionContext(ctx, ses, busyLock, connId, maxCursors); + return new OdbcConnectionContext(ctx, busyLock, connId, maxCursors); case JDBC_CLIENT: - return new JdbcConnectionContext(ctx, ses, busyLock, connId, maxCursors); + return new JdbcConnectionContext(ctx, busyLock, connId, maxCursors); case THIN_CLIENT: return new ClientConnectionContext(ctx, connId, maxCursors, thinCfg); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java index 329af01..214ae11 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/jdbc/JdbcConnectionContext.java @@ -70,9 +70,6 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte /** Supported versions. */ private static final Set<ClientListenerProtocolVersion> SUPPORTED_VERS = new HashSet<>(); - /** Session. */ - private final GridNioSession ses; - /** Shutdown busy lock. */ private final GridSpinBusyLock busyLock; @@ -104,17 +101,15 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte /** * Constructor. - * @param ctx Kernal Context. - * @param ses Session. + * @param ctx Kernal Context. * @param busyLock Shutdown busy lock. * @param connId Connection ID. * @param maxCursors Maximum allowed cursors. */ - public JdbcConnectionContext(GridKernalContext ctx, GridNioSession ses, GridSpinBusyLock busyLock, long connId, + public JdbcConnectionContext(GridKernalContext ctx, GridSpinBusyLock busyLock, long connId, int maxCursors) { super(ctx, connId); - this.ses = ses; this.busyLock = busyLock; this.maxCursors = maxCursors; @@ -132,7 +127,8 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte } /** {@inheritDoc} */ - @Override public void initializeFromHandshake(ClientListenerProtocolVersion ver, BinaryReaderExImpl reader) + @Override public void initializeFromHandshake(GridNioSession ses, + ClientListenerProtocolVersion ver, BinaryReaderExImpl reader) throws IgniteCheckedException { assert SUPPORTED_VERS.contains(ver): "Unsupported JDBC protocol version."; @@ -192,7 +188,7 @@ public class JdbcConnectionContext extends ClientListenerAbstractConnectionConte throw new IgniteCheckedException("Handshake error: " + e.getMessage(), e); } - actx = authenticate(user, passwd); + actx = authenticate(ses.certificates(), user, passwd); } parser = new JdbcMessageParser(ctx, ver); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java index 72a4a80..ccab26e 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/odbc/OdbcConnectionContext.java @@ -62,9 +62,6 @@ public class OdbcConnectionContext extends ClientListenerAbstractConnectionConte /** Supported versions. */ private static final Set<ClientListenerProtocolVersion> SUPPORTED_VERS = new HashSet<>(); - /** Session. */ - private final GridNioSession ses; - /** Shutdown busy lock. */ private final GridSpinBusyLock busyLock; @@ -92,15 +89,13 @@ public class OdbcConnectionContext extends ClientListenerAbstractConnectionConte /** * Constructor. * @param ctx Kernal Context. - * @param ses Session. * @param busyLock Shutdown busy lock. * @param connId Connection ID. * @param maxCursors Maximum allowed cursors. */ - public OdbcConnectionContext(GridKernalContext ctx, GridNioSession ses, GridSpinBusyLock busyLock, long connId, int maxCursors) { + public OdbcConnectionContext(GridKernalContext ctx, GridSpinBusyLock busyLock, long connId, int maxCursors) { super(ctx, connId); - this.ses = ses; this.busyLock = busyLock; this.maxCursors = maxCursors; @@ -118,7 +113,8 @@ public class OdbcConnectionContext extends ClientListenerAbstractConnectionConte } /** {@inheritDoc} */ - @Override public void initializeFromHandshake(ClientListenerProtocolVersion ver, BinaryReaderExImpl reader) + @Override public void initializeFromHandshake(GridNioSession ses, + ClientListenerProtocolVersion ver, BinaryReaderExImpl reader) throws IgniteCheckedException { assert SUPPORTED_VERS.contains(ver): "Unsupported ODBC protocol version."; @@ -153,7 +149,7 @@ public class OdbcConnectionContext extends ClientListenerAbstractConnectionConte nestedTxMode = NestedTxMode.fromByte(nestedTxModeVal); } - AuthorizationContext actx = authenticate(user, passwd); + AuthorizationContext actx = authenticate(ses.certificates(), user, passwd); ClientListenerResponseSender sender = new ClientListenerResponseSender() { @Override public void send(ClientListenerResponse resp) { diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientConnectionContext.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientConnectionContext.java index 7fc9eb7..6dd0cdd 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientConnectionContext.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientConnectionContext.java @@ -36,6 +36,7 @@ import org.apache.ignite.internal.processors.odbc.ClientListenerMessageParser; import org.apache.ignite.internal.processors.odbc.ClientListenerProtocolVersion; import org.apache.ignite.internal.processors.odbc.ClientListenerRequestHandler; import org.apache.ignite.internal.processors.platform.client.tx.ClientTxContext; +import org.apache.ignite.internal.util.nio.GridNioSession; /** * Thin Client connection context. @@ -155,7 +156,8 @@ public class ClientConnectionContext extends ClientListenerAbstractConnectionCon } /** {@inheritDoc} */ - @Override public void initializeFromHandshake(ClientListenerProtocolVersion ver, BinaryReaderExImpl reader) + @Override public void initializeFromHandshake(GridNioSession ses, + ClientListenerProtocolVersion ver, BinaryReaderExImpl reader) throws IgniteCheckedException { boolean hasMore; @@ -179,7 +181,7 @@ public class ClientConnectionContext extends ClientListenerAbstractConnectionCon } } - AuthorizationContext authCtx = authenticate(user, pwd); + AuthorizationContext authCtx = authenticate(ses.certificates(), user, pwd); currentVer = ver; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java index 8b97405..9f8bdb5 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/GridRestProcessor.java @@ -805,6 +805,7 @@ public class GridRestProcessor extends GridProcessorAdapter implements IgniteRes authCtx.subjectId(req.clientId()); authCtx.nodeAttributes(req.userAttributes()); authCtx.address(req.address()); + authCtx.certificates(req.certificates()); SecurityCredentials creds = credentials(req); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java index 6ad9431..10f895c 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/protocols/tcp/GridTcpRestNioListener.java @@ -416,6 +416,7 @@ public class GridTcpRestNioListener extends GridNioServerListenerAdapter<GridCli restReq.clientId(msg.clientId()); restReq.sessionToken(msg.sessionToken()); restReq.address(ses.remoteAddress()); + restReq.certificates(ses.certificates()); if (restReq.credentials() == null) { GridClientAbstractMessage msg0 = (GridClientAbstractMessage) msg; diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java index 42a687b..e5c9dc8 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/rest/request/GridRestRequest.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.processors.rest.request; import java.net.InetSocketAddress; +import java.security.cert.Certificate; import java.util.Map; import java.util.UUID; import org.apache.ignite.internal.processors.authentication.AuthorizationContext; @@ -55,6 +56,9 @@ public class GridRestRequest { /** User attributes. */ Map<String, String> userAttrs; + /** */ + private Certificate[] certs; + /** * @return Destination ID. */ @@ -183,6 +187,20 @@ public class GridRestRequest { this.userAttrs = userAttrs; } + /** + * @return Client SSL certificates. + */ + public Certificate[] certificates() { + return certs; + } + + /** + * @param certs Client SSL certificates. + */ + public void certificates(Certificate[] certs) { + this.certs = certs; + } + /** {@inheritDoc} */ @Override public String toString() { return S.toString(GridRestRequest.class, this); diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSession.java b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSession.java index 12b9b40b..979bc9f 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSession.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSession.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.util.nio; import java.net.InetSocketAddress; +import java.security.cert.Certificate; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.lang.IgniteInClosure; @@ -146,6 +147,11 @@ public interface GridNioSession { public boolean accepted(); /** + * @return Client SSL certificates + */ + @Nullable public Certificate[] certificates(); + + /** * Resumes session reads. * * @return Future representing result. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionImpl.java index 51cb558..d1574b6 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionImpl.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/GridNioSessionImpl.java @@ -18,15 +18,19 @@ package org.apache.ignite.internal.util.nio; import java.net.InetSocketAddress; +import java.security.cert.Certificate; import java.util.concurrent.atomic.AtomicLong; +import javax.net.ssl.SSLPeerUnverifiedException; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; +import org.apache.ignite.internal.util.nio.ssl.GridSslMeta; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.lang.IgniteInClosure; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.util.nio.GridNioSessionMetaKey.MAX_KEYS_CNT; +import static org.apache.ignite.internal.util.nio.GridNioSessionMetaKey.SSL_META; /** * @@ -270,6 +274,22 @@ public class GridNioSessionImpl implements GridNioSession { return accepted; } + /** */ + @Override public Certificate[] certificates() { + GridSslMeta meta = meta(SSL_META.ordinal()); + + if (meta != null) { + try { + return meta.sslEngine().getSession().getPeerCertificates(); + } + catch (SSLPeerUnverifiedException e) { + // Nothing to do. + } + } + + return null; + } + /** * @param <T> Chain type. * @return Filter chain. diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/ssl/GridNioSslFilter.java b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/ssl/GridNioSslFilter.java index 9b0c91c..7167c40 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/util/nio/ssl/GridNioSslFilter.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/util/nio/ssl/GridNioSslFilter.java @@ -175,6 +175,8 @@ public class GridNioSslFilter extends GridNioFilterAdapter { sslMeta = new GridSslMeta(); + sslMeta.sslEngine(engine); + ses.addMeta(SSL_META.ordinal(), sslMeta); handshake = true; diff --git a/modules/core/src/main/java/org/apache/ignite/plugin/security/AuthenticationContext.java b/modules/core/src/main/java/org/apache/ignite/plugin/security/AuthenticationContext.java index f48b697..3010c19 100644 --- a/modules/core/src/main/java/org/apache/ignite/plugin/security/AuthenticationContext.java +++ b/modules/core/src/main/java/org/apache/ignite/plugin/security/AuthenticationContext.java @@ -18,6 +18,7 @@ package org.apache.ignite.plugin.security; import java.net.InetSocketAddress; +import java.security.cert.Certificate; import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -50,6 +51,9 @@ public class AuthenticationContext { /** True if this is a client node context. */ private boolean client; + /** Client SSL certificates. */ + private Certificate[] certs; + /** * Gets subject type. * @@ -158,6 +162,23 @@ public class AuthenticationContext { } /** + * @return Client SSL certificates. + */ + public Certificate[] certificates() { + return certs; + } + + /** + * Set client SSL certificates. + * @param certs Client SSL certificates. + */ + public AuthenticationContext certificates(Certificate[] certs) { + this.certs = certs; + + return this; + } + + /** * @return {@code true} if this is a client node context. */ public boolean isClient() { diff --git a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java index 6b3e6f6..5d4e624 100644 --- a/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java +++ b/modules/core/src/main/java/org/apache/ignite/plugin/security/SecuritySubject.java @@ -21,6 +21,7 @@ import java.io.Serializable; import java.net.InetSocketAddress; import java.security.PermissionCollection; import java.security.ProtectionDomain; +import java.security.cert.Certificate; import java.util.UUID; import org.apache.ignite.internal.processors.security.SecurityUtils; @@ -57,6 +58,15 @@ public interface SecuritySubject extends Serializable { public InetSocketAddress address(); /** + * Gets subject client certificates, or {@code null} if SSL were not used or client certificate checking not enabled. + * + * @return Subject client certificates. + */ + public default Certificate[] certificates() { + return null; + } + + /** * Authorized permission set for the subject. * * @return Authorized permission set for the subject. diff --git a/modules/core/src/test/config/tests.properties b/modules/core/src/test/config/tests.properties index d659224..d379203 100644 --- a/modules/core/src/test/config/tests.properties +++ b/modules/core/src/test/config/tests.properties @@ -148,6 +148,7 @@ ssl.keystore.node01.path=@{IGNITE_HOME}/modules/clients/src/test/keystore/node01 ssl.keystore.node02.path=@{IGNITE_HOME}/modules/clients/src/test/keystore/node02.jks ssl.keystore.node02old.path=@{IGNITE_HOME}/modules/clients/src/test/keystore/node02old.jks ssl.keystore.node03.path=@{IGNITE_HOME}/modules/clients/src/test/keystore/node03.jks +ssl.keystore.node0102.path=@{IGNITE_HOME}/modules/clients/src/test/keystore/node0102.jks # Cluster certificate is signed by trust-one, thinServer and thinClient – by trust-two, # connectorServer and connectorClient – by trust-three. diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientSslPermissionCheckTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientSslPermissionCheckTest.java new file mode 100644 index 0000000..d65f2e4 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/client/ThinClientSslPermissionCheckTest.java @@ -0,0 +1,237 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.ignite.internal.processors.security.client; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.Consumer; +import org.apache.ignite.IgniteException; +import org.apache.ignite.Ignition; +import org.apache.ignite.client.ClientAuthorizationException; +import org.apache.ignite.client.Config; +import org.apache.ignite.client.IgniteClient; +import org.apache.ignite.client.SslMode; +import org.apache.ignite.configuration.CacheConfiguration; +import org.apache.ignite.configuration.ClientConfiguration; +import org.apache.ignite.configuration.ClientConnectorConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.IgniteEx; +import org.apache.ignite.internal.processors.security.AbstractSecurityTest; +import org.apache.ignite.internal.processors.security.impl.TestCertificateSecurityPluginProvider; +import org.apache.ignite.internal.processors.security.impl.TestSecurityData; +import org.apache.ignite.internal.util.typedef.G; +import org.apache.ignite.lang.IgniteBiTuple; +import org.apache.ignite.plugin.security.SecurityPermissionSetBuilder; +import org.apache.ignite.testframework.GridTestUtils; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static java.util.Collections.singletonMap; +import static org.apache.ignite.internal.util.lang.GridFunc.t; +import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_CREATE; +import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_DESTROY; +import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_PUT; +import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_READ; +import static org.apache.ignite.plugin.security.SecurityPermission.CACHE_REMOVE; +import static org.apache.ignite.plugin.security.SecurityPermission.TASK_EXECUTE; +import static org.apache.ignite.testframework.GridTestUtils.assertThrowsWithCause; + +/** + * Security tests for thin client. + */ +@RunWith(JUnit4.class) +public class ThinClientSslPermissionCheckTest extends AbstractSecurityTest { + /** Client. */ + private static final String CLIENT = "node01"; + + /** Client that has system permissions. */ + private static final String CLIENT_SYS_PERM = "node02"; + + /** Client that has system permissions. */ + private static final String CLIENT_CACHE_TASK_OPER = "node03"; + + /** Cache. */ + private static final String CACHE = "TEST_CACHE"; + + /** Forbidden cache. */ + private static final String FORBIDDEN_CACHE = "FORBIDDEN_TEST_CACHE"; + + /** Cache to test system oper permissions. */ + private static final String DYNAMIC_CACHE = "DYNAMIC_TEST_CACHE"; + + /** Remove all task name. */ + public static final String REMOVE_ALL_TASK = + "org.apache.ignite.internal.processors.cache.distributed.GridDistributedCacheAdapter$RemoveAllTask"; + + /** Clear task name. */ + public static final String CLEAR_TASK = + "org.apache.ignite.internal.processors.cache.GridCacheAdapter$ClearTask"; + + /** + * @param clientData Array of client security data. + */ + private IgniteConfiguration getConfiguration(TestSecurityData... clientData) throws Exception { + return getConfiguration(G.allGrids().size(), clientData); + } + + /** + * @param idx Index. + * @param clientData Array of client security data. + */ + private IgniteConfiguration getConfiguration(int idx, TestSecurityData... clientData) throws Exception { + String instanceName = getTestIgniteInstanceName(idx); + + return getConfiguration( + instanceName, + new TestCertificateSecurityPluginProvider(clientData) + ).setCacheConfiguration( + new CacheConfiguration().setName(CACHE), + new CacheConfiguration().setName(FORBIDDEN_CACHE) + ).setClientConnectorConfiguration( + new ClientConnectorConfiguration().setSslEnabled(true).setSslClientAuth(true) + .setUseIgniteSslContextFactory(false) + .setSslContextFactory(GridTestUtils.sslTrustedFactory("node01", "trustboth")) + ); + } + + /** {@inheritDoc} */ + @Override protected void beforeTestsStarted() throws Exception { + IgniteEx ignite = startGrid( + getConfiguration( + new TestSecurityData(CLIENT, + SecurityPermissionSetBuilder.create().defaultAllowAll(false) + .appendCachePermissions(CACHE, CACHE_READ, CACHE_PUT, CACHE_REMOVE) + .appendCachePermissions(FORBIDDEN_CACHE, EMPTY_PERMS) + .build() + ), + new TestSecurityData(CLIENT_SYS_PERM, + SecurityPermissionSetBuilder.create().defaultAllowAll(false) + .appendSystemPermissions(CACHE_CREATE, CACHE_DESTROY) + .build() + ), + new TestSecurityData(CLIENT_CACHE_TASK_OPER, + SecurityPermissionSetBuilder.create().defaultAllowAll(false) + .appendCachePermissions(CACHE, CACHE_REMOVE) + .appendTaskPermissions(REMOVE_ALL_TASK, TASK_EXECUTE) + .appendTaskPermissions(CLEAR_TASK, TASK_EXECUTE) + .build() + ) + ) + ); + + ignite.cluster().active(true); + } + + /** */ + @Test + public void testCacheSinglePermOperations() throws Exception { + for (IgniteBiTuple<Consumer<IgniteClient>, String> t : operations(CACHE)) + runOperation(CLIENT, t); + + for (IgniteBiTuple<Consumer<IgniteClient>, String> t : operations(FORBIDDEN_CACHE)) + assertThrowsWithCause(() -> runOperation(CLIENT, t), ClientAuthorizationException.class); + } + + /** + * That test shows the wrong case when a client has permission for a remove operation + * but a removeAll operation is forbidden for it. To have permission for the removeAll (clear) operation + * a client need to have the permission to execute {@link #REMOVE_ALL_TASK} ({@link #CLEAR_TASK}) task. + * + * @throws Exception If error occurs. + */ + @Test + public void testCacheTaskPermOperations() throws Exception { + List<IgniteBiTuple<Consumer<IgniteClient>, String>> ops = Arrays.asList( + t(c -> c.cache(CACHE).removeAll(), "removeAll"), + t(c -> c.cache(CACHE).clear(), "clear") + ); + + for (IgniteBiTuple<Consumer<IgniteClient>, String> op : ops) { + runOperation(CLIENT_CACHE_TASK_OPER, op); + + assertThrowsWithCause(() -> runOperation(CLIENT, op), ClientAuthorizationException.class); + } + } + + /** */ + @Test + public void testSysOperation() throws Exception { + try (IgniteClient sysPrmClnt = startClient(CLIENT_SYS_PERM)) { + sysPrmClnt.createCache(DYNAMIC_CACHE); + + assertTrue(sysPrmClnt.cacheNames().contains(DYNAMIC_CACHE)); + + sysPrmClnt.destroyCache(DYNAMIC_CACHE); + + assertFalse(sysPrmClnt.cacheNames().contains(DYNAMIC_CACHE)); + } + + List<IgniteBiTuple<Consumer<IgniteClient>, String>> ops = Arrays.asList( + t(c -> c.createCache(DYNAMIC_CACHE), "createCache"), + t(c -> c.destroyCache(CACHE), "destroyCache") + ); + + for (IgniteBiTuple<Consumer<IgniteClient>, String> op : ops) + assertThrowsWithCause(() -> runOperation(CLIENT, op), ClientAuthorizationException.class); + } + + /** + * @param cacheName Cache name. + */ + private Collection<IgniteBiTuple<Consumer<IgniteClient>, String>> operations(final String cacheName) { + return Arrays.asList( + t(c -> c.cache(cacheName).put("key", "value"), "put"), + t(c -> c.cache(cacheName).putAll(singletonMap("key", "value")), "putAll"), + t(c -> c.cache(cacheName).get("key"), "get)"), + t(c -> c.cache(cacheName).getAll(Collections.singleton("key")), "getAll"), + t(c -> c.cache(cacheName).containsKey("key"), "containsKey"), + t(c -> c.cache(cacheName).remove("key"), "remove"), + t(c -> c.cache(cacheName).replace("key", "value"), "replace"), + t(c -> c.cache(cacheName).putIfAbsent("key", "value"), "putIfAbsent"), + t(c -> c.cache(cacheName).getAndPut("key", "value"), "getAndPut"), + t(c -> c.cache(cacheName).getAndRemove("key"), "getAndRemove"), + t(c -> c.cache(cacheName).getAndReplace("key", "value"), "getAndReplace") + ); + } + + /** */ + private void runOperation(String clientName, IgniteBiTuple<Consumer<IgniteClient>, String> op) { + try (IgniteClient client = startClient(clientName)) { + op.get1().accept(client); + } + catch (Exception e) { + throw new IgniteException(op.get2(), e); + } + } + + /** + * @param userName User name. + */ + private IgniteClient startClient(String userName) { + return Ignition.startClient( + new ClientConfiguration().setAddresses(Config.SERVER).setSslMode(SslMode.REQUIRED) + .setSslClientCertificateKeyStorePath(GridTestUtils.keyStorePath(userName)) + .setSslClientCertificateKeyStorePassword(GridTestUtils.keyStorePassword()) + .setSslTrustCertificateKeyStorePath(GridTestUtils.keyStorePath("trustone")) + .setSslTrustCertificateKeyStorePassword(GridTestUtils.keyStorePassword()) + ); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityPluginProvider.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityPluginProvider.java new file mode 100644 index 0000000..818f306 --- /dev/null +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityPluginProvider.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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. + */ + +package org.apache.ignite.internal.processors.security.impl; + +import java.util.List; +import java.util.Arrays; +import org.apache.ignite.internal.GridKernalContext; +import org.apache.ignite.internal.processors.security.AbstractTestSecurityPluginProvider; +import org.apache.ignite.internal.processors.security.GridSecurityProcessor; + +/** */ +public class TestCertificateSecurityPluginProvider extends AbstractTestSecurityPluginProvider { + /** Users security data. */ + private final List<TestSecurityData> clientData; + + /** */ + public TestCertificateSecurityPluginProvider(TestSecurityData... clientData) { + this.clientData = Arrays.asList(clientData); + } + + /** {@inheritDoc} */ + @Override protected GridSecurityProcessor securityProcessor(GridKernalContext ctx) { + return new TestCertificateSecurityProcessor(ctx, clientData); + } +} diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityProcessor.java similarity index 64% copy from modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java copy to modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityProcessor.java index dc212cd..a59f644 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestCertificateSecurityProcessor.java @@ -18,7 +18,8 @@ package org.apache.ignite.internal.processors.security.impl; import java.net.InetSocketAddress; -import java.security.Permissions; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -34,90 +35,80 @@ import org.apache.ignite.internal.processors.security.GridSecurityProcessor; import org.apache.ignite.internal.processors.security.SecurityContext; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.plugin.security.AuthenticationContext; -import org.apache.ignite.plugin.security.SecurityBasicPermissionSet; import org.apache.ignite.plugin.security.SecurityCredentials; import org.apache.ignite.plugin.security.SecurityException; import org.apache.ignite.plugin.security.SecurityPermission; import org.apache.ignite.plugin.security.SecurityPermissionSet; import org.apache.ignite.plugin.security.SecuritySubject; +import static org.apache.ignite.plugin.security.SecurityPermissionSetBuilder.ALLOW_ALL; import static org.apache.ignite.plugin.security.SecuritySubjectType.REMOTE_NODE; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; /** * Security processor for test. */ -public class TestSecurityProcessor extends GridProcessorAdapter implements GridSecurityProcessor { +public class TestCertificateSecurityProcessor extends GridProcessorAdapter implements GridSecurityProcessor { /** Permissions. */ - public static final Map<SecurityCredentials, SecurityPermissionSet> PERMS = new ConcurrentHashMap<>(); - - /** Sandbox permissions. */ - private static final Map<SecurityCredentials, Permissions> SANDBOX_PERMS = new ConcurrentHashMap<>(); - - /** Node security data. */ - private final TestSecurityData nodeSecData; + public static final Map<String, SecurityPermissionSet> PERMS = new ConcurrentHashMap<>(); /** Users security data. */ private final Collection<TestSecurityData> predefinedAuthData; - /** Global authentication. */ - private final boolean globalAuth; - /** * Constructor. */ - public TestSecurityProcessor(GridKernalContext ctx, TestSecurityData nodeSecData, - Collection<TestSecurityData> predefinedAuthData, boolean globalAuth) { + public TestCertificateSecurityProcessor(GridKernalContext ctx, Collection<TestSecurityData> predefinedAuthData) { super(ctx); - this.nodeSecData = nodeSecData; this.predefinedAuthData = predefinedAuthData.isEmpty() ? Collections.emptyList() : new ArrayList<>(predefinedAuthData); - this.globalAuth = globalAuth; } /** {@inheritDoc} */ - @Override public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred) - throws IgniteCheckedException { - if (!PERMS.containsKey(cred)) - return null; - + @Override public SecurityContext authenticateNode(ClusterNode node, SecurityCredentials cred) { return new TestSecurityContext( new TestSecuritySubject() .setType(REMOTE_NODE) .setId(node.id()) .setAddr(new InetSocketAddress(F.first(node.addresses()), 0)) - .setLogin(cred.getLogin()) - .setPerms(PERMS.get(cred)) - .sandboxPermissions(SANDBOX_PERMS.get(cred)) + .setLogin("") + .setPerms(ALLOW_ALL) ); } /** {@inheritDoc} */ @Override public boolean isGlobalNodeAuthentication() { - return globalAuth; + return true; } /** {@inheritDoc} */ - @Override public SecurityContext authenticate(AuthenticationContext ctx) throws IgniteCheckedException { - if (ctx.credentials() == null || ctx.credentials().getLogin() == null) - return null; + @Override public SecurityContext authenticate(AuthenticationContext ctx) { + Certificate[] certs = ctx.certificates(); + + assertNotNull(certs); + + assertEquals(2, certs.length); - SecurityPermissionSet perms = PERMS.get(ctx.credentials()); + assertTrue(((X509Certificate)certs[0]).getSubjectDN().getName().matches("^CN=[a-z0-9]+$")); + assertTrue(((X509Certificate)certs[0]).getIssuerDN().getName().startsWith("C=RU, ST=SPb, L=SPb, O=Ignite, OU=Dev")); - if (perms == null) { - perms = new SecurityBasicPermissionSet(); - ((SecurityBasicPermissionSet) perms).setDefaultAllowAll(true); - } + String cn = ((X509Certificate)certs[0]).getSubjectDN().getName().substring(3); + + if (!PERMS.containsKey(cn)) + return null; return new TestSecurityContext( new TestSecuritySubject() .setType(ctx.subjectType()) .setId(ctx.subjectId()) .setAddr(ctx.address()) - .setLogin(ctx.credentials().getLogin()) - .setPerms(perms) - .sandboxPermissions(SANDBOX_PERMS.get(ctx.credentials())) + .setLogin(cn) + .setPerms(PERMS.get(cn)) + .setCerts(ctx.certificates()) ); } @@ -134,6 +125,7 @@ public class TestSecurityProcessor extends GridProcessorAdapter implements GridS /** {@inheritDoc} */ @Override public void authorize(String name, SecurityPermission perm, SecurityContext securityCtx) throws SecurityException { + if (!((TestSecurityContext)securityCtx).operationAllowed(name, perm)) throw new SecurityException("Authorization failed [perm=" + perm + ", name=" + name + @@ -154,32 +146,17 @@ public class TestSecurityProcessor extends GridProcessorAdapter implements GridS @Override public void start() throws IgniteCheckedException { super.start(); - PERMS.put(nodeSecData.credentials(), nodeSecData.getPermissions()); - SANDBOX_PERMS.put(nodeSecData.credentials(), nodeSecData.sandboxPermissions()); + ctx.addNodeAttribute(IgniteNodeAttributes.ATTR_SECURITY_CREDENTIALS, new SecurityCredentials("", "")); - ctx.addNodeAttribute(IgniteNodeAttributes.ATTR_SECURITY_CREDENTIALS, nodeSecData.credentials()); - - for (TestSecurityData data : predefinedAuthData) { - PERMS.put(data.credentials(), data.getPermissions()); - SANDBOX_PERMS.put(nodeSecData.credentials(), data.sandboxPermissions()); - } + for (TestSecurityData data : predefinedAuthData) + PERMS.put(data.credentials().getLogin().toString(), data.getPermissions()); } /** {@inheritDoc} */ @Override public void stop(boolean cancel) throws IgniteCheckedException { super.stop(cancel); - PERMS.remove(nodeSecData.credentials()); - SANDBOX_PERMS.remove(nodeSecData.credentials()); - - for (TestSecurityData data : predefinedAuthData) { - PERMS.remove(data.credentials()); - SANDBOX_PERMS.remove(data.credentials()); - } - } - - /** {@inheritDoc} */ - @Override public boolean sandboxEnabled() { - return true; + for (TestSecurityData data : predefinedAuthData) + PERMS.remove(data.credentials().getLogin().toString()); } } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java index dc212cd..d4e46c0 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecurityProcessor.java @@ -117,6 +117,7 @@ public class TestSecurityProcessor extends GridProcessorAdapter implements GridS .setAddr(ctx.address()) .setLogin(ctx.credentials().getLogin()) .setPerms(perms) + .setCerts(ctx.certificates()) .sandboxPermissions(SANDBOX_PERMS.get(ctx.credentials())) ); } diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java index c41026e..2e27355 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/security/impl/TestSecuritySubject.java @@ -19,6 +19,7 @@ package org.apache.ignite.internal.processors.security.impl; import java.net.InetSocketAddress; import java.security.PermissionCollection; +import java.security.cert.Certificate; import java.util.UUID; import org.apache.ignite.plugin.security.SecurityPermissionSet; import org.apache.ignite.plugin.security.SecuritySubject; @@ -46,6 +47,9 @@ public class TestSecuritySubject implements SecuritySubject { /** Permissions for Sandbox checks. */ private PermissionCollection sandboxPerms; + /** Client certificates. */ + private Certificate[] certs; + /** * Default constructor. */ @@ -152,6 +156,21 @@ public class TestSecuritySubject implements SecuritySubject { } /** {@inheritDoc} */ + @Override public Certificate[] certificates() { + return certs; + } + + /** + * @param perms Permissions. + */ + public TestSecuritySubject setCerts(Certificate[] certs) { + this.certs = certs; + + return this; + } + + + /** {@inheritDoc} */ @Override public String toString() { return "TestSecuritySubject{" + "id=" + id + diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/GridNioFilterChainSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/GridNioFilterChainSelfTest.java index 3dd3565..2ed6711 100644 --- a/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/GridNioFilterChainSelfTest.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/GridNioFilterChainSelfTest.java @@ -17,17 +17,13 @@ package org.apache.ignite.internal.util.nio.impl; -import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicReference; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; -import org.apache.ignite.internal.util.lang.GridMetadataAwareAdapter; import org.apache.ignite.internal.util.nio.GridNioFilterAdapter; import org.apache.ignite.internal.util.nio.GridNioFilterChain; -import org.apache.ignite.internal.util.nio.GridNioFinishedFuture; import org.apache.ignite.internal.util.nio.GridNioFuture; -import org.apache.ignite.internal.util.nio.GridNioRecoveryDescriptor; import org.apache.ignite.internal.util.nio.GridNioServerListener; import org.apache.ignite.internal.util.nio.GridNioServerListenerAdapter; import org.apache.ignite.internal.util.nio.GridNioSession; @@ -267,139 +263,4 @@ public class GridNioFilterChainSelfTest extends GridCommonAbstractTest { ses.addMeta(metaKey, att); } } - - /** - */ - public class MockNioSession extends GridMetadataAwareAdapter implements GridNioSession { - /** Local address */ - private InetSocketAddress locAddr = new InetSocketAddress(0); - - /** Remote address. */ - private InetSocketAddress rmtAddr = new InetSocketAddress(0); - - /** - * Creates empty mock session. - */ - public MockNioSession() { - // No-op. - } - - /** - * Creates new mock session with given addresses. - * - * @param locAddr Local address. - * @param rmtAddr Remote address. - */ - public MockNioSession(InetSocketAddress locAddr, InetSocketAddress rmtAddr) { - this(); - - this.locAddr = locAddr; - this.rmtAddr = rmtAddr; - } - - /** {@inheritDoc} */ - @Override public InetSocketAddress localAddress() { - return locAddr; - } - - /** {@inheritDoc} */ - @Override public InetSocketAddress remoteAddress() { - return rmtAddr; - } - - /** {@inheritDoc} */ - @Override public long bytesSent() { - return 0; - } - - /** {@inheritDoc} */ - @Override public long bytesReceived() { - return 0; - } - - /** {@inheritDoc} */ - @Override public long createTime() { - return 0; - } - - /** {@inheritDoc} */ - @Override public long closeTime() { - return 0; - } - - /** {@inheritDoc} */ - @Override public long lastReceiveTime() { - return 0; - } - - /** {@inheritDoc} */ - @Override public long lastSendTime() { - return 0; - } - - /** {@inheritDoc} */ - @Override public long lastSendScheduleTime() { - return 0; - } - - /** {@inheritDoc} */ - @Override public GridNioFuture<Boolean> close() { - return new GridNioFinishedFuture<>(true); - } - - /** {@inheritDoc} */ - @Override public GridNioFuture<?> send(Object msg) { - return new GridNioFinishedFuture<>(true); - } - - /** {@inheritDoc} */ - @Override public void sendNoFuture(Object msg, IgniteInClosure<IgniteException> ackC) { - // No-op. - } - - /** {@inheritDoc} */ - @Override public GridNioFuture<Object> resumeReads() { - return null; - } - - /** {@inheritDoc} */ - @Override public GridNioFuture<Object> pauseReads() { - return null; - } - - /** {@inheritDoc} */ - @Override public boolean accepted() { - return false; - } - - /** {@inheritDoc} */ - @Override public boolean readsPaused() { - return false; - } - - /** {@inheritDoc} */ - @Override public void outRecoveryDescriptor(GridNioRecoveryDescriptor recoveryDesc) { - // No-op. - } - - /** {@inheritDoc} */ - @Nullable @Override public GridNioRecoveryDescriptor outRecoveryDescriptor() { - return null; - } - - /** {@inheritDoc} */ - @Override public void inRecoveryDescriptor(GridNioRecoveryDescriptor recoveryDesc) { - // No-op. - } - - /** {@inheritDoc} */ - @Nullable @Override public GridNioRecoveryDescriptor inRecoveryDescriptor() { - return null; - } - - /** {@inheritDoc} */ - @Override public void systemMessage(Object msg) { - // No-op. - } - } } diff --git a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/MockNioSession.java b/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/MockNioSession.java similarity index 95% rename from modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/MockNioSession.java rename to modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/MockNioSession.java index a436ec4..0994ffc 100644 --- a/modules/clients/src/test/java/org/apache/ignite/internal/processors/rest/protocols/tcp/MockNioSession.java +++ b/modules/core/src/test/java/org/apache/ignite/internal/util/nio/impl/MockNioSession.java @@ -15,9 +15,10 @@ * limitations under the License. */ -package org.apache.ignite.internal.processors.rest.protocols.tcp; +package org.apache.ignite.internal.util.nio.impl; import java.net.InetSocketAddress; +import java.security.cert.Certificate; import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.IgniteException; import org.apache.ignite.internal.util.lang.GridMetadataAwareAdapter; @@ -135,6 +136,11 @@ public class MockNioSession extends GridMetadataAwareAdapter implements GridNioS } /** {@inheritDoc} */ + @Override public Certificate[] certificates() { + return null; + } + + /** {@inheritDoc} */ @Override public boolean readsPaused() { return false; } diff --git a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySslTrustedUntrustedTest.java b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySslTrustedUntrustedTest.java index d0188da..e44db54 100644 --- a/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySslTrustedUntrustedTest.java +++ b/modules/core/src/test/java/org/apache/ignite/spi/discovery/tcp/TcpDiscoverySslTrustedUntrustedTest.java @@ -77,6 +77,22 @@ public class TcpDiscoverySslTrustedUntrustedTest extends GridCommonAbstractTest * @throws Exception If failed. */ @Test + public void testTrustOneMultiCert() throws Exception { + checkDiscoverySuccess("node01", "trustone", "node0102", "trustone"); + } + + /** + * @throws Exception If failed. + */ + @Test + public void testTrustBothMultiCert() throws Exception { + checkDiscoverySuccess("node03", "trustboth", "node0102", "trusttwo"); + } + + /** + * @throws Exception If failed. + */ + @Test public void testDifferentCa() throws Exception { checkDiscoveryFailure("node01", "trustone", "node02", "trusttwo"); } diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java index b38f491..e466e14 100644 --- a/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java +++ b/modules/core/src/test/java/org/apache/ignite/testsuites/SecurityTestSuite.java @@ -33,6 +33,7 @@ import org.apache.ignite.internal.processors.security.client.AdditionalSecurityC import org.apache.ignite.internal.processors.security.client.AdditionalSecurityCheckWithGlobalAuthTest; import org.apache.ignite.internal.processors.security.client.ThinClientPermissionCheckSecurityTest; import org.apache.ignite.internal.processors.security.client.ThinClientPermissionCheckTest; +import org.apache.ignite.internal.processors.security.client.ThinClientSslPermissionCheckTest; import org.apache.ignite.internal.processors.security.compute.ComputePermissionCheckTest; import org.apache.ignite.internal.processors.security.compute.closure.ComputeTaskCancelRemoteSecurityContextCheckTest; import org.apache.ignite.internal.processors.security.compute.closure.ComputeTaskRemoteSecurityContextCheckTest; @@ -75,6 +76,7 @@ import org.junit.runners.Suite; CacheLoadRemoteSecurityContextCheckTest.class, ContinuousQueryRemoteSecurityContextCheckTest.class, ContinuousQueryWithTransformerRemoteSecurityContextCheckTest.class, + ThinClientSslPermissionCheckTest.class, InvalidServerTest.class, AdditionalSecurityCheckTest.class, diff --git a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java index 6b5f791..cfdd442 100644 --- a/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java +++ b/modules/core/src/test/java/org/apache/ignite/util/GridCommandHandlerClusterByClassTest.java @@ -318,7 +318,7 @@ public class GridCommandHandlerClusterByClassTest extends GridCommandHandlerClus assertContains(log, output, CommandHandler.UTILITY_NAME); } - checkHelp(output, "org.apache.ignite.util/control.sh_cache_help.output"); + checkHelp(output, "org.apache.ignite.util/" + getClass().getSimpleName() + "_cache_help.output"); } /** */ @@ -349,7 +349,7 @@ public class GridCommandHandlerClusterByClassTest extends GridCommandHandlerClus assertNotContains(log, testOutStr, "Control.sh"); - checkHelp(testOutStr, "org.apache.ignite.util/control.sh_help.output"); + checkHelp(testOutStr, "org.apache.ignite.util/" + getClass().getSimpleName() + "_help.output"); } /** diff --git a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_cache_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output similarity index 100% copy from modules/core/src/test/resources/org.apache.ignite.util/control.sh_cache_help.output copy to modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_cache_help.output diff --git a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output similarity index 100% copy from modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output copy to modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassTest_help.output diff --git a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_cache_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output similarity index 100% rename from modules/core/src/test/resources/org.apache.ignite.util/control.sh_cache_help.output rename to modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_cache_help.output diff --git a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output similarity index 98% rename from modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output rename to modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output index 1775583..c3c825f 100644 --- a/modules/core/src/test/resources/org.apache.ignite.util/control.sh_help.output +++ b/modules/core/src/test/resources/org.apache.ignite.util/GridCommandHandlerClusterByClassWithSSLTest_help.output @@ -13,13 +13,13 @@ This utility can do the following commands: control.(sh|bat) --activate Deactivate cluster (deprecated. Use --set-state instead): - control.(sh|bat) --deactivate [--force] [--yes] + control.(sh|bat) --deactivate [--yes] Print current cluster state: control.(sh|bat) --state Change cluster state: - control.(sh|bat) --set-state INACTIVE|ACTIVE|ACTIVE_READ_ONLY [--force] [--yes] + control.(sh|bat) --set-state INACTIVE|ACTIVE|ACTIVE_READ_ONLY [--yes] Parameters: ACTIVE - Activate cluster. Cache updates are allowed. diff --git a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java index 20ada42..35cd4e0 100644 --- a/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java +++ b/modules/rest-http/src/main/java/org/apache/ignite/internal/processors/rest/protocols/http/jetty/GridJettyRestHandler.java @@ -28,6 +28,7 @@ import java.io.LineNumberReader; import java.io.OutputStream; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; +import java.security.cert.X509Certificate; import java.sql.Date; import java.sql.Time; import java.sql.Timestamp; @@ -967,6 +968,11 @@ public class GridJettyRestHandler extends AbstractHandler { restReq.command(cmd); + Object certs = req.getAttribute("javax.servlet.request.X509Certificate"); + + if (certs instanceof X509Certificate[]) + restReq.certificates((X509Certificate[])certs); + // TODO: In IGNITE 3.0 we should check credentials only for AUTHENTICATE command. if (!credentials(params, IGNITE_LOGIN, IGNITE_PASSWORD, restReq)) credentials(params, USER_PARAM, PWD_PARAM, restReq); diff --git a/parent/pom.xml b/parent/pom.xml index 80f12ef..d08a7ea 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -872,8 +872,7 @@ <exclude>src/main/java/org/jsr166/*.java</exclude> <exclude>src/main/java/org/mindrot/*.java</exclude> <exclude>src/test/java/org/apache/ignite/p2p/p2p.properties</exclude><!--test depends on file content--> - <exclude>src/test/resources/org.apache.ignite.util/control.sh_cache_help.output</exclude><!--test depends on file content--> - <exclude>src/test/resources/org.apache.ignite.util/control.sh_help.output</exclude><!--test depends on file content--> + <exclude>src/test/resources/org.apache.ignite.util/*.output</exclude><!--test depends on file content--> <exclude>src/test/resources/log/ignite.log.tst</exclude><!--test resource--> <exclude>src/test/java/org/apache/ignite/spi/deployment/uri/META-INF/ignite.incorrefs</exclude><!--test resource--> <exclude>src/test/java/org/apache/ignite/spi/deployment/uri/META-INF/ignite.empty</exclude><!--should be empty-->