Repository: ignite Updated Branches: refs/heads/master 5dc4de83f -> 61c9ffb7a
IGNITE-8076: Java Thin Client: authentication. This closes #3720. Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/c1e7da15 Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/c1e7da15 Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/c1e7da15 Branch: refs/heads/master Commit: c1e7da15cc188ad0addce5002b2868c5a6e4dc8c Parents: f80d3a9 Author: Alexey Kukushkin <[email protected]> Authored: Mon Apr 2 11:08:05 2018 +0300 Committer: devozerov <[email protected]> Committed: Mon Apr 2 11:10:53 2018 +0300 ---------------------------------------------------------------------- .../main/java/org/apache/ignite/Ignition.java | 4 +- .../client/ClientConfigurationException.java | 51 ----- .../internal/client/thin/ReliableChannel.java | 7 +- .../internal/client/thin/TcpClientChannel.java | 71 ++++-- .../internal/client/thin/TcpIgniteClient.java | 5 +- .../odbc/ClientListenerNioListener.java | 15 +- .../client/ClientConnectionContext.java | 54 ++++- .../platform/client/ClientRequestHandler.java | 18 +- .../org/apache/ignite/client/SecurityTest.java | 158 ------------- .../org/apache/ignite/client/SecurityTest.java | 228 +++++++++++++++++++ .../Client/ClientConnectionTest.cs | 5 +- 11 files changed, 358 insertions(+), 258 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/c1e7da15/modules/core/src/main/java/org/apache/ignite/Ignition.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/Ignition.java b/modules/core/src/main/java/org/apache/ignite/Ignition.java index 229dbdb..835896e 100644 --- a/modules/core/src/main/java/org/apache/ignite/Ignition.java +++ b/modules/core/src/main/java/org/apache/ignite/Ignition.java @@ -22,6 +22,7 @@ import java.net.URL; import java.util.List; import java.util.Objects; import java.util.UUID; +import org.apache.ignite.client.ClientException; import org.apache.ignite.configuration.ClientConfiguration; import org.apache.ignite.configuration.IgniteConfiguration; import org.apache.ignite.internal.IgnitionEx; @@ -29,7 +30,6 @@ import org.apache.ignite.internal.client.thin.TcpIgniteClient; import org.apache.ignite.internal.util.typedef.internal.U; import org.apache.ignite.spi.discovery.DiscoverySpi; import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi; -import org.apache.ignite.client.ClientConfigurationException; import org.apache.ignite.client.IgniteClient; import org.apache.ignite.thread.IgniteThread; import org.jetbrains.annotations.Nullable; @@ -584,7 +584,7 @@ public class Ignition { * @param cfg Thin client configuration. * @return Successfully opened thin client connection. */ - public static IgniteClient startClient(ClientConfiguration cfg) throws ClientConfigurationException { + public static IgniteClient startClient(ClientConfiguration cfg) throws ClientException { Objects.requireNonNull(cfg, "cfg"); return TcpIgniteClient.start(cfg); http://git-wip-us.apache.org/repos/asf/ignite/blob/c1e7da15/modules/core/src/main/java/org/apache/ignite/client/ClientConfigurationException.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/client/ClientConfigurationException.java b/modules/core/src/main/java/org/apache/ignite/client/ClientConfigurationException.java deleted file mode 100644 index aac37eb..0000000 --- a/modules/core/src/main/java/org/apache/ignite/client/ClientConfigurationException.java +++ /dev/null @@ -1,51 +0,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. - */ - -package org.apache.ignite.client; - -/** - * Indicates Ignite client configuration error. - */ -public class ClientConfigurationException extends ClientException { - /** Serial version uid. */ - private static final long serialVersionUID = 0L; - - /** - * Default constructor. - */ - public ClientConfigurationException() { - } - - /** - * Constructs a new exception with the specified detail message. - * - * @param msg the detail message. - */ - public ClientConfigurationException(String msg) { - super(msg); - } - - /** - * Constructs a new exception with the specified cause and a detail - * message of <tt>(cause==null ? null : cause.toString())</tt>. - * - * @param cause the cause. - */ - public ClientConfigurationException(Throwable cause) { - super(cause); - } -} http://git-wip-us.apache.org/repos/asf/ignite/blob/c1e7da15/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ReliableChannel.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ReliableChannel.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ReliableChannel.java index 83a9bc5..392b8f8 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ReliableChannel.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/ReliableChannel.java @@ -31,7 +31,6 @@ import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; import org.apache.ignite.IgniteCheckedException; -import org.apache.ignite.client.ClientConfigurationException; import org.apache.ignite.client.ClientConnectionException; import org.apache.ignite.client.ClientException; import org.apache.ignite.configuration.ClientConfiguration; @@ -68,7 +67,7 @@ final class ReliableChannel implements AutoCloseable { ReliableChannel( Function<ClientChannelConfiguration, Result<ClientChannel>> chFactory, ClientConfiguration clientCfg - ) throws ClientConfigurationException { + ) throws ClientException { if (chFactory == null) throw new NullPointerException("chFactory"); @@ -164,7 +163,7 @@ final class ReliableChannel implements AutoCloseable { /** * @return host:port_range address lines parsed as {@link InetSocketAddress}. */ - private static List<InetSocketAddress> parseAddresses(String[] addrs) throws ClientConfigurationException { + private static List<InetSocketAddress> parseAddresses(String[] addrs) throws ClientException { Collection<HostAndPortRange> ranges = new ArrayList<>(addrs.length); for (String a : addrs) { @@ -177,7 +176,7 @@ final class ReliableChannel implements AutoCloseable { )); } catch (IgniteCheckedException e) { - throw new ClientConfigurationException(e); + throw new ClientException(e); } } http://git-wip-us.apache.org/repos/asf/ignite/blob/c1e7da15/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpClientChannel.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpClientChannel.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpClientChannel.java index e462903..404793a 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpClientChannel.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpClientChannel.java @@ -32,6 +32,8 @@ import java.security.NoSuchAlgorithmException; import java.security.UnrecoverableKeyException; import java.security.cert.CertificateException; import java.security.cert.X509Certificate; +import java.util.Arrays; +import java.util.Collection; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiFunction; import java.util.function.Consumer; @@ -47,6 +49,11 @@ import javax.net.ssl.SSLSocketFactory; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; +import org.apache.ignite.client.ClientAuthenticationException; +import org.apache.ignite.client.ClientConnectionException; +import org.apache.ignite.client.SslMode; +import org.apache.ignite.client.SslProtocol; +import org.apache.ignite.configuration.ClientConfiguration; import org.apache.ignite.internal.binary.BinaryRawWriterEx; import org.apache.ignite.internal.binary.BinaryReaderExImpl; import org.apache.ignite.internal.binary.BinaryWriterExImpl; @@ -55,16 +62,23 @@ import org.apache.ignite.internal.binary.streams.BinaryHeapOutputStream; import org.apache.ignite.internal.binary.streams.BinaryInputStream; import org.apache.ignite.internal.binary.streams.BinaryOffheapOutputStream; import org.apache.ignite.internal.binary.streams.BinaryOutputStream; -import org.apache.ignite.client.ClientAuthenticationException; -import org.apache.ignite.configuration.ClientConfiguration; -import org.apache.ignite.client.ClientConnectionException; -import org.apache.ignite.client.SslMode; -import org.apache.ignite.client.SslProtocol; /** * Implements {@link ClientChannel} over TCP. */ class TcpClientChannel implements ClientChannel { + /** Protocol version: 1.1.0. */ + private static final ProtocolVersion V1_1_0 = new ProtocolVersion((short)1, (short)1, (short)0); + + /** Protocol version 1 0 0. */ + private static final ProtocolVersion V1_0_0 = new ProtocolVersion((short)1, (short)0, (short)0); + + /** Supported protocol versions. */ + private static final Collection<ProtocolVersion> supportedVers = Arrays.asList(V1_1_0, V1_0_0); + + /** Protocol version agreed with the server. */ + private ProtocolVersion ver = V1_1_0; + /** Channel. */ private final Socket sock; @@ -74,9 +88,6 @@ class TcpClientChannel implements ClientChannel { /** Input stream. */ private final InputStream in; - /** Version. */ - private final ProtocolVersion ver = new ProtocolVersion((short)1, (short)0, (short)0); - /** Request id. */ private final AtomicLong reqId = new AtomicLong(1); @@ -213,7 +224,7 @@ class TcpClientChannel implements ClientChannel { private void handshake(String user, String pwd) throws ClientConnectionException, ClientAuthenticationException { handshakeReq(user, pwd); - handshakeRes(); + handshakeRes(user, pwd); } /** Send handshake request. */ @@ -226,7 +237,7 @@ class TcpClientChannel implements ClientChannel { req.writeShort(ver.patch()); req.writeByte((byte)2); // client code, always 2 - if (user != null && user.length() > 0) { + if (ver.compareTo(V1_1_0) >= 0 && user != null && user.length() > 0) { req.writeByteArray(marshalString(user)); req.writeByteArray(marshalString(pwd)); } @@ -238,7 +249,8 @@ class TcpClientChannel implements ClientChannel { } /** Receive and handle handshake response. */ - private void handshakeRes() throws ClientConnectionException, ClientAuthenticationException { + private void handshakeRes(String user, String pwd) + throws ClientConnectionException, ClientAuthenticationException { int resSize = new BinaryHeapInputStream(read(4)).readInt(); if (resSize <= 0) @@ -249,17 +261,32 @@ class TcpClientChannel implements ClientChannel { if (!res.readBoolean()) { // success flag ProtocolVersion srvVer = new ProtocolVersion(res.readShort(), res.readShort(), res.readShort()); - String err = new BinaryReaderExImpl(null, res, null, true).readString(); - - if (err != null && err.toUpperCase().matches(".*USER.*INCORRECT.*")) - throw new ClientAuthenticationException(); - else - throw new ClientProtocolError(String.format( - "Protocol version mismatch: client %s / server %s. Server details: %s", - ver, - srvVer, - err - )); + try (BinaryReaderExImpl r = new BinaryReaderExImpl(null, res, null, true)) { + String err = r.readString(); + + if (err != null && err.toUpperCase().matches(".*USER.*INCORRECT.*")) + throw new ClientAuthenticationException(); + else if (ver.equals(srvVer)) + throw new ClientProtocolError(err); + else if (!supportedVers.contains(srvVer) || + (srvVer.compareTo(V1_1_0) < 0 && user != null && user.length() > 0)) + // Server version is not supported by this client OR server version is less than 1.1.0 supporting + // authentication and authentication is required. + throw new ClientProtocolError(String.format( + "Protocol version mismatch: client %s / server %s. Server details: %s", + ver, + srvVer, + err + )); + else { // retry with server version + ver = srvVer; + + handshake(user, pwd); + } + } + catch (IOException e) { + throw new ClientConnectionException(e); + } } } http://git-wip-us.apache.org/repos/asf/ignite/blob/c1e7da15/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java index 99d05df..7beeb79 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/client/thin/TcpIgniteClient.java @@ -33,7 +33,6 @@ import org.apache.ignite.cache.query.FieldsQueryCursor; import org.apache.ignite.cache.query.SqlFieldsQuery; import org.apache.ignite.client.ClientCache; import org.apache.ignite.client.ClientCacheConfiguration; -import org.apache.ignite.client.ClientConfigurationException; import org.apache.ignite.client.ClientException; import org.apache.ignite.client.IgniteClient; import org.apache.ignite.configuration.ClientConfiguration; @@ -73,7 +72,7 @@ public class TcpIgniteClient implements IgniteClient { * Private constructor. Use {@link TcpIgniteClient#start(ClientConfiguration)} to create an instance of * {@link TcpClientChannel}. */ - private TcpIgniteClient(ClientConfiguration cfg) throws ClientConfigurationException { + private TcpIgniteClient(ClientConfiguration cfg) throws ClientException { Function<ClientChannelConfiguration, Result<ClientChannel>> chFactory = chCfg -> { try { return new Result<>(new TcpClientChannel(chCfg)); @@ -189,7 +188,7 @@ public class TcpIgniteClient implements IgniteClient { * @param cfg Thin client configuration. * @return Successfully opened thin client connection. */ - public static IgniteClient start(ClientConfiguration cfg) throws ClientConfigurationException { + public static IgniteClient start(ClientConfiguration cfg) throws ClientException { return new TcpIgniteClient(cfg); } http://git-wip-us.apache.org/repos/asf/ignite/blob/c1e7da15/modules/core/src/main/java/org/apache/ignite/internal/processors/odbc/ClientListenerNioListener.java ---------------------------------------------------------------------- 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 f472a9f..53b14d7 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 @@ -33,6 +33,7 @@ import org.apache.ignite.internal.util.GridSpinBusyLock; import org.apache.ignite.internal.util.nio.GridNioServerListenerAdapter; import org.apache.ignite.internal.util.nio.GridNioSession; import org.apache.ignite.internal.util.nio.GridNioSessionMetaKey; +import org.apache.ignite.internal.util.typedef.internal.U; import org.jetbrains.annotations.Nullable; /** @@ -87,6 +88,7 @@ public class ClientListenerNioListener extends GridNioServerListenerAdapter<byte this.busyLock = busyLock; this.maxCursors = cliConnCfg.getMaxOpenCursorsPerConnection(); this.cliConnCfg = cliConnCfg; + log = ctx.log(getClass()); } @@ -134,7 +136,7 @@ public class ClientListenerNioListener extends GridNioServerListenerAdapter<byte req = parser.decode(msg); } catch (Exception e) { - log.error("Failed to parse client request.", e); + U.error(log, "Failed to parse client request.", e); ses.close(); @@ -167,7 +169,7 @@ public class ClientListenerNioListener extends GridNioServerListenerAdapter<byte ses.send(outMsg); } catch (Exception e) { - log.error("Failed to process client request [req=" + req + ']', e); + U.error(log, "Failed to process client request [req=" + req + ']', e); ses.send(parser.encode(handler.handleException(e, req))); } @@ -192,7 +194,7 @@ public class ClientListenerNioListener extends GridNioServerListenerAdapter<byte byte cmd = reader.readByte(); if (cmd != ClientListenerRequest.HANDSHAKE) { - log.error("Unexpected client request (will close session): " + ses.remoteAddress()); + U.warn(log, "Unexpected client request (will close session): " + ses.remoteAddress()); ses.close(); @@ -221,16 +223,13 @@ public class ClientListenerNioListener extends GridNioServerListenerAdapter<byte ses.addMeta(CONN_CTX_META_KEY, connCtx); } - else { - log.warning("Unsupported version: " + ver.toString()); - + else throw new IgniteCheckedException("Unsupported version."); - } connCtx.handler().writeHandshake(writer); } catch (IgniteCheckedException e) { - log.error("Error on handshake. " + e.getMessage(), e); + U.warn(log, "Error during handshake [rmtAddr=" + ses.remoteAddress() + ", msg=" + e.getMessage() + ']'); ClientListenerProtocolVersion currVer; http://git-wip-us.apache.org/repos/asf/ignite/blob/c1e7da15/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientConnectionContext.java ---------------------------------------------------------------------- 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 2d81f35..7ab2d33 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 @@ -17,8 +17,13 @@ package org.apache.ignite.internal.processors.platform.client; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import org.apache.ignite.IgniteCheckedException; import org.apache.ignite.internal.GridKernalContext; import org.apache.ignite.internal.binary.BinaryReaderExImpl; +import org.apache.ignite.internal.processors.authentication.AuthorizationContext; import org.apache.ignite.internal.processors.odbc.ClientListenerConnectionContext; import org.apache.ignite.internal.processors.odbc.ClientListenerMessageParser; import org.apache.ignite.internal.processors.odbc.ClientListenerProtocolVersion; @@ -33,11 +38,17 @@ public class ClientConnectionContext implements ClientListenerConnectionContext /** Version 1.0.0. */ private static final ClientListenerProtocolVersion VER_1_0_0 = ClientListenerProtocolVersion.create(1, 0, 0); + /** Version 1.1.0. */ + private static final ClientListenerProtocolVersion VER_1_1_0 = ClientListenerProtocolVersion.create(1, 1, 0); + + /** Supported versions. */ + private static final Collection<ClientListenerProtocolVersion> SUPPORTED_VERS = Arrays.asList(VER_1_1_0, VER_1_0_0); + /** Message parser. */ private final ClientMessageParser parser; /** Request handler. */ - private final ClientRequestHandler handler; + private ClientRequestHandler handler; /** Handle registry. */ private final ClientResourceRegistry resReg = new ClientResourceRegistry(); @@ -63,7 +74,7 @@ public class ClientConnectionContext implements ClientListenerConnectionContext kernalCtx = ctx; parser = new ClientMessageParser(ctx); - handler = new ClientRequestHandler(this); + this.maxCursors = maxCursors; } @@ -87,17 +98,48 @@ public class ClientConnectionContext implements ClientListenerConnectionContext /** {@inheritDoc} */ @Override public boolean isVersionSupported(ClientListenerProtocolVersion ver) { - return VER_1_0_0.equals(ver); + return SUPPORTED_VERS.contains(ver); } /** {@inheritDoc} */ @Override public ClientListenerProtocolVersion currentVersion() { - return VER_1_0_0; + return VER_1_1_0; } /** {@inheritDoc} */ - @Override public void initializeFromHandshake(ClientListenerProtocolVersion ver, BinaryReaderExImpl reader) { - // No-op. + @Override public void initializeFromHandshake(ClientListenerProtocolVersion ver, BinaryReaderExImpl reader) + throws IgniteCheckedException { + boolean hasMore; + + String user = null; + String pwd = null; + AuthorizationContext authCtx = null; + + if (ver.compareTo(VER_1_1_0) >= 0) { + try { + hasMore = reader.available() > 0; + } + catch (IOException e) { + throw new IgniteCheckedException("Handshake error: " + e.getMessage(), e); + } + + if (hasMore) { + user = reader.readString(); + pwd = reader.readString(); + } + } + + if (kernalCtx.authentication().enabled()) { + if (user == null || user.length() == 0) + throw new IgniteCheckedException("Unauthenticated sessions are prohibited."); + + authCtx = kernalCtx.authentication().authenticate(user, pwd); + + if (authCtx == null) + throw new IgniteCheckedException("Unknown authentication error."); + } + + handler = new ClientRequestHandler(this, authCtx); } /** {@inheritDoc} */ http://git-wip-us.apache.org/repos/asf/ignite/blob/c1e7da15/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java ---------------------------------------------------------------------- diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java index 3f6c082..faa50bc 100644 --- a/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java +++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/platform/client/ClientRequestHandler.java @@ -18,6 +18,7 @@ package org.apache.ignite.internal.processors.platform.client; import org.apache.ignite.internal.binary.BinaryWriterExImpl; +import org.apache.ignite.internal.processors.authentication.AuthorizationContext; import org.apache.ignite.internal.processors.odbc.ClientListenerRequest; import org.apache.ignite.internal.processors.odbc.ClientListenerRequestHandler; import org.apache.ignite.internal.processors.odbc.ClientListenerResponse; @@ -29,20 +30,33 @@ public class ClientRequestHandler implements ClientListenerRequestHandler { /** Client context. */ private final ClientConnectionContext ctx; + /** Auth context. */ + private final AuthorizationContext authCtx; + /** * Constructor. * * @param ctx Kernal context. */ - ClientRequestHandler(ClientConnectionContext ctx) { + ClientRequestHandler(ClientConnectionContext ctx, AuthorizationContext authCtx) { assert ctx != null; this.ctx = ctx; + this.authCtx = authCtx; } /** {@inheritDoc} */ @Override public ClientListenerResponse handle(ClientListenerRequest req) { - return ((ClientRequest) req).process(ctx); + if (authCtx != null) + AuthorizationContext.context(authCtx); + + try { + return ((ClientRequest)req).process(ctx); + } + finally { + if (authCtx != null) + AuthorizationContext.clear(); + } } /** {@inheritDoc} */ http://git-wip-us.apache.org/repos/asf/ignite/blob/c1e7da15/modules/core/src/test/java/org/apache/ignite/client/SecurityTest.java ---------------------------------------------------------------------- diff --git a/modules/core/src/test/java/org/apache/ignite/client/SecurityTest.java b/modules/core/src/test/java/org/apache/ignite/client/SecurityTest.java deleted file mode 100644 index 603ac6c..0000000 --- a/modules/core/src/test/java/org/apache/ignite/client/SecurityTest.java +++ /dev/null @@ -1,158 +0,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. - */ - -package org.apache.ignite.client; - -import java.util.AbstractMap.SimpleEntry; -import java.util.function.Function; -import org.apache.ignite.Ignite; -import org.apache.ignite.IgniteCheckedException; -import org.apache.ignite.Ignition; -import org.apache.ignite.configuration.ClientConfiguration; -import org.apache.ignite.configuration.ClientConnectorConfiguration; -import org.apache.ignite.configuration.DataRegionConfiguration; -import org.apache.ignite.configuration.DataStorageConfiguration; -import org.apache.ignite.configuration.IgniteConfiguration; -import org.apache.ignite.internal.IgniteEx; -import org.apache.ignite.internal.processors.authentication.AuthorizationContext; -import org.apache.ignite.ssl.SslContextFactory; -import org.junit.Test; - -import static org.junit.Assert.assertNull; -import static org.junit.Assert.assertTrue; - -/** - * Thin client security test. - */ -public class SecurityTest { - /** Test SSL/TLS encryption. */ - @Test - public void testEncryption() throws Exception { - // Server-side security configuration - IgniteConfiguration srvCfg = Config.getServerConfiguration(); - - SslContextFactory sslCfg = new SslContextFactory(); - - Function<String, String> rsrcPath = rsrc -> SecurityTest.class.getResource(rsrc).getPath(); - - sslCfg.setKeyStoreFilePath(rsrcPath.apply("/server.jks")); - sslCfg.setKeyStorePassword("123456".toCharArray()); - sslCfg.setTrustStoreFilePath(rsrcPath.apply("/trust.jks")); - sslCfg.setTrustStorePassword("123456".toCharArray()); - - srvCfg.setClientConnectorConfiguration(new ClientConnectorConfiguration() - .setSslEnabled(true) - .setSslClientAuth(true) - ); - - srvCfg.setSslContextFactory(sslCfg); - - // Client-side security configuration - ClientConfiguration clientCfg = new ClientConfiguration().setAddresses(Config.SERVER); - - try (Ignite ignored = Ignition.start(srvCfg)) { - boolean failed; - - try (IgniteClient client = Ignition.startClient(clientCfg)) { - client.<Integer, String>cache(Config.DEFAULT_CACHE_NAME).put(1, "1"); - - failed = false; - } - catch (Exception ex) { - failed = true; - } - - assertTrue("Client connection without SSL must fail", failed); - - // Not using user-supplied SSL Context Factory: - try (IgniteClient client = Ignition.startClient(clientCfg - .setSslMode(SslMode.REQUIRED) - .setSslClientCertificateKeyStorePath(rsrcPath.apply("/client.jks")) - .setSslClientCertificateKeyStoreType("JKS") - .setSslClientCertificateKeyStorePassword("123456") - .setSslTrustCertificateKeyStorePath(rsrcPath.apply("/trust.jks")) - .setSslTrustCertificateKeyStoreType("JKS") - .setSslTrustCertificateKeyStorePassword("123456") - .setSslKeyAlgorithm("SunX509") - .setSslTrustAll(false) - .setSslProtocol(SslProtocol.TLS) - )) { - client.<Integer, String>cache(Config.DEFAULT_CACHE_NAME).put(1, "1"); - } - - // Using user-supplied SSL Context Factory - try (IgniteClient client = Ignition.startClient(clientCfg - .setSslMode(SslMode.REQUIRED) - .setSslContextFactory(sslCfg) - )) { - client.<Integer, String>cache(Config.DEFAULT_CACHE_NAME).put(1, "1"); - } - } - } - - /** Test authentication. */ - @Test - public void testAuthentication() throws IgniteCheckedException { - try (Ignite ignite = Ignition.start(Config.getServerConfiguration() - .setAuthenticationEnabled(true) - .setDataStorageConfiguration(new DataStorageConfiguration() - .setDefaultDataRegionConfiguration(new DataRegionConfiguration().setPersistenceEnabled(true)) - ) - )) { - ignite.cluster().active(true); - - Function<SimpleEntry<String, String>, Exception> authenticate = cred -> { - ClientConfiguration clientCfg = new ClientConfiguration().setAddresses(Config.SERVER) - .setUserName(cred.getKey()) - .setUserPassword(cred.getValue()); - - try (IgniteClient client = Ignition.startClient(clientCfg)) { - client.getOrCreateCache("testAuthentication"); - } - catch (Exception e) { - return e; - } - - return null; - }; - - assertTrue( - "Authentication with invalid credentials succeeded", - authenticate.apply(new SimpleEntry<>("bad-user", "bad-password")) - instanceof ClientAuthenticationException - ); - - SimpleEntry<String, String> validCred = new SimpleEntry<>("user", "password"); - - IgniteEx igniteEx = (IgniteEx)ignite; - - AuthorizationContext.context(igniteEx.context().authentication().authenticate("ignite", "ignite")); - - try { - igniteEx.context().authentication().addUser(validCred.getKey(), validCred.getValue()); - } - catch (IgniteCheckedException ignore) { - // Ignore "user already exists" exception - } - - assertNull( - "Authentication with valid credentials failed", - authenticate.apply(validCred) - ); - } - } -} http://git-wip-us.apache.org/repos/asf/ignite/blob/c1e7da15/modules/indexing/src/test/java/org/apache/ignite/client/SecurityTest.java ---------------------------------------------------------------------- diff --git a/modules/indexing/src/test/java/org/apache/ignite/client/SecurityTest.java b/modules/indexing/src/test/java/org/apache/ignite/client/SecurityTest.java new file mode 100644 index 0000000..dc57f0c --- /dev/null +++ b/modules/indexing/src/test/java/org/apache/ignite/client/SecurityTest.java @@ -0,0 +1,228 @@ +/* + * 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.client; + +import java.nio.file.Paths; +import java.util.AbstractMap.SimpleEntry; +import java.util.function.Function; +import org.apache.ignite.Ignite; +import org.apache.ignite.IgniteCheckedException; +import org.apache.ignite.Ignition; +import org.apache.ignite.cache.query.SqlFieldsQuery; +import org.apache.ignite.configuration.ClientConfiguration; +import org.apache.ignite.configuration.ClientConnectorConfiguration; +import org.apache.ignite.configuration.DataRegionConfiguration; +import org.apache.ignite.configuration.DataStorageConfiguration; +import org.apache.ignite.configuration.IgniteConfiguration; +import org.apache.ignite.internal.util.typedef.internal.U; +import org.apache.ignite.ssl.SslContextFactory; +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +/** + * Thin client security test. + */ +public class SecurityTest { + /** Ignite home. */ + private static final String IGNITE_HOME = U.getIgniteHome(); + + /** + * Setup before each test. + */ + @Before + public void beforeEach() throws IgniteCheckedException { + U.resolveWorkDirectory(U.defaultWorkDirectory(), "db", true); + } + + /** Test SSL/TLS encryption. */ + @Test + public void testEncryption() throws Exception { + // Server-side security configuration + IgniteConfiguration srvCfg = Config.getServerConfiguration(); + + SslContextFactory sslCfg = new SslContextFactory(); + + Function<String, String> rsrcPath = rsrc -> Paths.get( + IGNITE_HOME == null ? "." : IGNITE_HOME, + "modules", + "core", + "src", + "test", + "resources", + rsrc + ).toString(); + + sslCfg.setKeyStoreFilePath(rsrcPath.apply("/server.jks")); + sslCfg.setKeyStorePassword("123456".toCharArray()); + sslCfg.setTrustStoreFilePath(rsrcPath.apply("/trust.jks")); + sslCfg.setTrustStorePassword("123456".toCharArray()); + + srvCfg.setClientConnectorConfiguration(new ClientConnectorConfiguration() + .setSslEnabled(true) + .setSslClientAuth(true) + ); + + srvCfg.setSslContextFactory(sslCfg); + + // Client-side security configuration + ClientConfiguration clientCfg = new ClientConfiguration().setAddresses(Config.SERVER); + + try (Ignite ignored = Ignition.start(srvCfg)) { + boolean failed; + + try (IgniteClient client = Ignition.startClient(clientCfg)) { + client.<Integer, String>cache(Config.DEFAULT_CACHE_NAME).put(1, "1"); + + failed = false; + } + catch (Exception ex) { + failed = true; + } + + assertTrue("Client connection without SSL must fail", failed); + + // Not using user-supplied SSL Context Factory: + try (IgniteClient client = Ignition.startClient(clientCfg + .setSslMode(SslMode.REQUIRED) + .setSslClientCertificateKeyStorePath(rsrcPath.apply("/client.jks")) + .setSslClientCertificateKeyStoreType("JKS") + .setSslClientCertificateKeyStorePassword("123456") + .setSslTrustCertificateKeyStorePath(rsrcPath.apply("/trust.jks")) + .setSslTrustCertificateKeyStoreType("JKS") + .setSslTrustCertificateKeyStorePassword("123456") + .setSslKeyAlgorithm("SunX509") + .setSslTrustAll(false) + .setSslProtocol(SslProtocol.TLS) + )) { + client.<Integer, String>cache(Config.DEFAULT_CACHE_NAME).put(1, "1"); + } + + // Using user-supplied SSL Context Factory + try (IgniteClient client = Ignition.startClient(clientCfg + .setSslMode(SslMode.REQUIRED) + .setSslContextFactory(sslCfg) + )) { + client.<Integer, String>cache(Config.DEFAULT_CACHE_NAME).put(1, "1"); + } + } + } + + /** Test valid user authentication. */ + @Test + public void testInvalidUserAuthentication() throws Exception { + try (Ignite ignored = igniteWithAuthentication(); + IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(Config.SERVER) + .setUserName("JOE") + .setUserPassword("password") + ) + ) { + Exception authError = null; + + try { + client.getOrCreateCache("testAuthentication"); + } + catch (Exception e) { + authError = e; + } + + assertNotNull("Authentication with invalid credentials succeeded", authError); + assertTrue("Invalid type of authentication error", authError instanceof ClientAuthenticationException); + } + } + + /** Test valid user authentication. */ + @Test + public void testValidUserAuthentication() throws Exception { + final String USER = "joe"; + final String PWD = "password"; + + try (Ignite ignored = igniteWithAuthentication(new SimpleEntry<>(USER, PWD)); + IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(Config.SERVER) + .setUserName(USER) + .setUserPassword(PWD) + ) + ) { + client.getOrCreateCache("testAuthentication"); + } + } + + /** Test user cannot create user. */ + @Test + public void testUserCannotCreateUser() throws Exception { + final String USER = "joe"; + final String PWD = "password"; + + try (Ignite ignored = igniteWithAuthentication(new SimpleEntry<>(USER, PWD)); + IgniteClient client = Ignition.startClient(new ClientConfiguration().setAddresses(Config.SERVER) + .setUserName(USER) + .setUserPassword(PWD) + ) + ) { + Exception authError = null; + + try { + client.query( + new SqlFieldsQuery(String.format("CREATE USER \"%s\" WITH PASSWORD '%s'", "joe2", "password")) + ).getAll(); + } + catch (Exception e) { + authError = e; + } + + assertNotNull("User created another user", authError); + } + } + + /** + * @return Ignite configuration with authentication enabled + */ + @SafeVarargs + private static Ignite igniteWithAuthentication(SimpleEntry<String, String>... users) throws Exception { + Ignite ignite = Ignition.start(Config.getServerConfiguration() + .setAuthenticationEnabled(true) + .setDataStorageConfiguration(new DataStorageConfiguration() + .setDefaultDataRegionConfiguration(new DataRegionConfiguration().setPersistenceEnabled(true)) + ) + ); + + ignite.cluster().active(true); + + for (SimpleEntry<String, String> u : users) + createUser(u.getKey(), u.getValue()); + + return ignite; + } + + /** + * Create user. + */ + private static void createUser(String user, String pwd) throws Exception { + try (IgniteClient client = Ignition.startClient(new ClientConfiguration() + .setAddresses(Config.SERVER) + .setUserName("ignite") + .setUserPassword("ignite") + )) { + client.query( + new SqlFieldsQuery(String.format("CREATE USER \"%s\" WITH PASSWORD '%s'", user, pwd)) + ).getAll(); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/c1e7da15/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs index 2169edc..9da9a03 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Client/ClientConnectionTest.cs @@ -22,6 +22,7 @@ namespace Apache.Ignite.Core.Tests.Client using System.Linq; using System.Net; using System.Net.Sockets; + using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Apache.Ignite.Core.Client; @@ -130,8 +131,8 @@ namespace Apache.Ignite.Core.Tests.Client Assert.AreEqual(ClientStatusCode.Fail, ex.StatusCode); - Assert.AreEqual("Client handshake failed: 'Unsupported version.'. " + - "Client version: -1.-1.-1. Server version: 1.0.0", ex.Message); + Assert.IsTrue(Regex.IsMatch(ex.Message, "Client handshake failed: 'Unsupported version.'. " + + "Client version: -1.-1.-1. Server version: [0-9]+.[0-9]+.[0-9]+")); } }
