blob: e5473420bf7874ed9ec72d661c5f9fd384552b8e [file] [log] [blame]
/*
* 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.handler;
import static org.apache.ignite.client.handler.ItClientHandlerTestUtils.MAGIC;
import static org.apache.ignite.internal.testframework.matchers.CompletableFutureMatcher.willCompleteSuccessfully;
import static org.apache.ignite.lang.ErrorGroups.Authentication.INVALID_CREDENTIALS_ERR;
import static org.apache.ignite.lang.ErrorGroups.Authentication.UNSUPPORTED_AUTHENTICATION_TYPE_ERR;
import static org.apache.ignite.lang.ErrorGroups.Client.PROTOCOL_COMPATIBILITY_ERR;
import static org.apache.ignite.lang.ErrorGroups.Common.INTERNAL_ERR;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import org.apache.ignite.client.handler.configuration.ClientConnectorConfiguration;
import org.apache.ignite.internal.configuration.testframework.ConfigurationExtension;
import org.apache.ignite.internal.configuration.testframework.InjectConfiguration;
import org.apache.ignite.internal.network.configuration.NetworkConfiguration;
import org.apache.ignite.internal.security.authentication.basic.BasicAuthenticationProviderChange;
import org.apache.ignite.internal.security.configuration.SecurityConfiguration;
import org.apache.ignite.internal.testframework.BaseIgniteAbstractTest;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInfo;
import org.junit.jupiter.api.extension.ExtendWith;
import org.msgpack.core.MessagePack;
/**
* Client connector integration tests with real sockets.
*/
@ExtendWith(ConfigurationExtension.class)
public class ItClientHandlerTest extends BaseIgniteAbstractTest {
private ClientHandlerModule serverModule;
private TestServer testServer;
private int serverPort;
@InjectConfiguration(rootName = "security")
private SecurityConfiguration securityConfiguration;
@InjectConfiguration
private ClientConnectorConfiguration clientConnectorConfiguration;
@InjectConfiguration
private NetworkConfiguration networkConfiguration;
@BeforeEach
public void setUp(TestInfo testInfo) {
testServer = new TestServer(null, securityConfiguration, clientConnectorConfiguration, networkConfiguration);
serverModule = testServer.start(testInfo);
serverPort = serverModule.localAddress().getPort();
}
@AfterEach
public void tearDown() throws Exception {
assertThat(serverModule.stopAsync(), willCompleteSuccessfully());
testServer.tearDown();
}
@Test
void testHandshakeInvalidMagicHeaderDropsConnection() throws Exception {
try (var sock = new Socket("127.0.0.1", serverPort)) {
OutputStream out = sock.getOutputStream();
out.write(new byte[]{63, 64, 65, 66, 67});
out.flush();
assertThrows(IOException.class, () -> writeAndFlushLoop(sock));
}
}
@Test
void testHandshakeValidReturnsSuccess() throws Exception {
try (var sock = new Socket("127.0.0.1", serverPort)) {
OutputStream out = sock.getOutputStream();
// Magic: IGNI
out.write(MAGIC);
// Handshake.
var packer = MessagePack.newDefaultBufferPacker();
packer.packInt(0);
packer.packInt(0);
packer.packInt(0);
packer.packInt(7); // Size.
packer.packInt(3); // Major.
packer.packInt(0); // Minor.
packer.packInt(0); // Patch.
packer.packInt(2); // Client type: general purpose.
packer.packBinaryHeader(0); // Features.
packer.packInt(0); // Extensions.
out.write(packer.toByteArray());
out.flush();
// Read response.
var unpacker = MessagePack.newDefaultUnpacker(sock.getInputStream());
final var magic = unpacker.readPayload(4);
unpacker.skipValue(3); // LE int zeros.
final var len = unpacker.unpackInt();
final var major = unpacker.unpackInt();
final var minor = unpacker.unpackInt();
final var patch = unpacker.unpackInt();
final var success = unpacker.tryUnpackNil();
assertTrue(success);
final var idleTimeout = unpacker.unpackLong();
final var nodeId = unpacker.unpackString();
final var nodeName = unpacker.unpackString();
unpacker.skipValue(); // Cluster id.
unpacker.skipValue(); // Cluster name.
unpacker.skipValue(); // Observable timestamp.
unpacker.skipValue(); // Major.
unpacker.skipValue(); // Minor.
unpacker.skipValue(); // Maintenance.
unpacker.skipValue(); // Patch.
unpacker.skipValue(); // Pre release.
var featuresLen = unpacker.unpackBinaryHeader();
unpacker.skipValue(featuresLen);
var extensionsLen = unpacker.unpackInt();
unpacker.skipValue(extensionsLen);
assertArrayEquals(MAGIC, magic);
assertEquals(81, len);
assertEquals(3, major);
assertEquals(0, minor);
assertEquals(0, patch);
assertEquals(5000, idleTimeout);
assertEquals("id", nodeId);
assertEquals("consistent-id", nodeName);
}
}
@Test
void testHandshakeWithUnsupportedAuthenticationType() throws Exception {
setupAuthentication("admin", "password");
try (var sock = new Socket("127.0.0.1", serverPort)) {
OutputStream out = sock.getOutputStream();
// Magic: IGNI
out.write(MAGIC);
// Handshake.
var packer = MessagePack.newDefaultBufferPacker();
packer.packInt(0);
packer.packInt(0);
packer.packInt(0);
packer.packInt(66); // Size.
packer.packInt(3); // Major.
packer.packInt(0); // Minor.
packer.packInt(0); // Patch.
packer.packInt(2); // Client type: general purpose.
packer.packBinaryHeader(0); // Features.
packer.packInt(3); // Extensions.
packer.packString("authn-type");
packer.packString("ldap");
packer.packString("authn-identity");
packer.packString("admin");
packer.packString("authn-secret");
packer.packString("password");
out.write(packer.toByteArray());
out.flush();
// Read response.
var unpacker = MessagePack.newDefaultUnpacker(sock.getInputStream());
var magic = unpacker.readPayload(4);
unpacker.readPayload(4); // Length.
final var major = unpacker.unpackInt();
final var minor = unpacker.unpackInt();
final var patch = unpacker.unpackInt();
unpacker.skipValue(); // traceId
final var code = unpacker.tryUnpackNil() ? INTERNAL_ERR : unpacker.unpackInt();
final var errClassName = unpacker.unpackString();
final var errMsg = unpacker.tryUnpackNil() ? null : unpacker.unpackString();
final var errStackTrace = unpacker.tryUnpackNil() ? null : unpacker.unpackString();
assertArrayEquals(MAGIC, magic);
assertEquals(3, major);
assertEquals(0, minor);
assertEquals(0, patch);
assertEquals(UNSUPPORTED_AUTHENTICATION_TYPE_ERR, code);
assertThat(errMsg, containsString("Unsupported authentication type: ldap"));
assertEquals(
"org.apache.ignite.security.exception.UnsupportedAuthenticationTypeException",
errClassName
);
assertNull(errStackTrace);
}
}
@Test
void testHandshakeWithAuthenticationValidCredentials() throws Exception {
setupAuthentication("admin", "password");
try (var sock = new Socket("127.0.0.1", serverPort)) {
OutputStream out = sock.getOutputStream();
// Magic: IGNI
out.write(MAGIC);
// Handshake.
var packer = MessagePack.newDefaultBufferPacker();
packer.packInt(0);
packer.packInt(0);
packer.packInt(0);
packer.packInt(67); // Size.
packer.packInt(3); // Major.
packer.packInt(0); // Minor.
packer.packInt(0); // Patch.
packer.packInt(2); // Client type: general purpose.
packer.packBinaryHeader(0); // Features.
packer.packInt(3); // Extensions.
packer.packString("authn-type");
packer.packString("basic");
packer.packString("authn-identity");
packer.packString("admin");
packer.packString("authn-secret");
packer.packString("password");
out.write(packer.toByteArray());
out.flush();
// Read response.
var unpacker = MessagePack.newDefaultUnpacker(sock.getInputStream());
final var magic = unpacker.readPayload(4);
unpacker.skipValue(3); // LE int zeros.
final var len = unpacker.unpackInt();
final var major = unpacker.unpackInt();
final var minor = unpacker.unpackInt();
final var patch = unpacker.unpackInt();
final var success = unpacker.tryUnpackNil();
assertTrue(success);
final var idleTimeout = unpacker.unpackLong();
final var nodeId = unpacker.unpackString();
final var nodeName = unpacker.unpackString();
unpacker.skipValue(); // Cluster id.
unpacker.skipValue(); // Cluster name.
unpacker.skipValue(); // Observable timestamp.
unpacker.skipValue(); // Major.
unpacker.skipValue(); // Minor.
unpacker.skipValue(); // Maintenance.
unpacker.skipValue(); // Patch.
unpacker.skipValue(); // Pre release.
var featuresLen = unpacker.unpackBinaryHeader();
unpacker.skipValue(featuresLen);
var extensionsLen = unpacker.unpackInt();
unpacker.skipValue(extensionsLen);
assertArrayEquals(MAGIC, magic);
assertEquals(81, len);
assertEquals(3, major);
assertEquals(0, minor);
assertEquals(0, patch);
assertEquals(5000, idleTimeout);
assertEquals("id", nodeId);
assertEquals("consistent-id", nodeName);
}
}
@Test
void testHandshakeWithAuthenticationInvalidCredentials() throws Exception {
setupAuthentication("admin", "password");
try (var sock = new Socket("127.0.0.1", serverPort)) {
OutputStream out = sock.getOutputStream();
// Magic: IGNI
out.write(MAGIC);
// Handshake.
var packer = MessagePack.newDefaultBufferPacker();
packer.packInt(0);
packer.packInt(0);
packer.packInt(0);
packer.packInt(75); // Size.
packer.packInt(3); // Major.
packer.packInt(0); // Minor.
packer.packInt(0); // Patch.
packer.packInt(2); // Client type: general purpose.
packer.packBinaryHeader(0); // Features.
packer.packInt(3); // Extensions.
packer.packString("authn-type");
packer.packString("basic");
packer.packString("authn-identity");
packer.packString("admin");
packer.packString("authn-secret");
packer.packString("invalid-password");
out.write(packer.toByteArray());
out.flush();
// Read response.
var unpacker = MessagePack.newDefaultUnpacker(sock.getInputStream());
var magic = unpacker.readPayload(4);
unpacker.readPayload(4); // Length.
final var major = unpacker.unpackInt();
final var minor = unpacker.unpackInt();
final var patch = unpacker.unpackInt();
unpacker.skipValue(); // traceId
final var code = unpacker.tryUnpackNil() ? INTERNAL_ERR : unpacker.unpackInt();
final var errClassName = unpacker.unpackString();
final var errMsg = unpacker.tryUnpackNil() ? null : unpacker.unpackString();
final var errStackTrace = unpacker.tryUnpackNil() ? null : unpacker.unpackString();
assertArrayEquals(MAGIC, magic);
assertEquals(3, major);
assertEquals(0, minor);
assertEquals(0, patch);
assertEquals(INVALID_CREDENTIALS_ERR, code);
assertThat(errMsg, containsString("Authentication failed"));
assertEquals("org.apache.ignite.security.exception.InvalidCredentialsException", errClassName);
assertNull(errStackTrace);
}
}
@Test
void testHandshakeWithAuthenticationEmptyCredentials() throws Exception {
setupAuthentication("admin", "password");
try (var sock = new Socket("127.0.0.1", serverPort)) {
OutputStream out = sock.getOutputStream();
// Magic: IGNI
out.write(MAGIC);
// Handshake.
var packer = MessagePack.newDefaultBufferPacker();
packer.packInt(0);
packer.packInt(0);
packer.packInt(0);
packer.packInt(7); // Size.
packer.packInt(3); // Major.
packer.packInt(0); // Minor.
packer.packInt(0); // Patch.
packer.packInt(2); // Client type: general purpose.
packer.packBinaryHeader(0); // Features.
packer.packInt(0); // Extensions.
out.write(packer.toByteArray());
out.flush();
// Read response.
var unpacker = MessagePack.newDefaultUnpacker(sock.getInputStream());
var magic = unpacker.readPayload(4);
unpacker.readPayload(4); // Length.
final var major = unpacker.unpackInt();
final var minor = unpacker.unpackInt();
final var patch = unpacker.unpackInt();
unpacker.skipValue(); // traceId
final var code = unpacker.tryUnpackNil() ? INTERNAL_ERR : unpacker.unpackInt();
final var errClassName = unpacker.unpackString();
final var errMsg = unpacker.tryUnpackNil() ? null : unpacker.unpackString();
final var errStackTrace = unpacker.tryUnpackNil() ? null : unpacker.unpackString();
assertArrayEquals(MAGIC, magic);
assertEquals(3, major);
assertEquals(0, minor);
assertEquals(0, patch);
assertEquals(INVALID_CREDENTIALS_ERR, code);
assertThat(errMsg, containsString("Authentication failed"));
assertEquals("org.apache.ignite.security.exception.InvalidCredentialsException", errClassName);
assertNull(errStackTrace);
}
}
@Test
void testHandshakeInvalidVersionReturnsError() throws Exception {
try (var sock = new Socket("127.0.0.1", serverPort)) {
OutputStream out = sock.getOutputStream();
// Magic: IGNI
out.write(MAGIC);
// Handshake.
var packer = MessagePack.newDefaultBufferPacker();
packer.packInt(0);
packer.packInt(0);
packer.packInt(0);
packer.packInt(7); // Size.
packer.packInt(2); // Major.
packer.packInt(8); // Minor.
packer.packInt(0); // Patch.
packer.packInt(2); // Client type: general purpose.
packer.packBinaryHeader(0); // Features.
packer.packInt(0); // Extensions.
out.write(packer.toByteArray());
out.flush();
// Read response.
var unpacker = MessagePack.newDefaultUnpacker(sock.getInputStream());
var magic = unpacker.readPayload(4);
unpacker.readPayload(4); // Length.
final var major = unpacker.unpackInt();
final var minor = unpacker.unpackInt();
final var patch = unpacker.unpackInt();
unpacker.skipValue(); // traceId
final var code = unpacker.tryUnpackNil() ? INTERNAL_ERR : unpacker.unpackInt();
final var errClassName = unpacker.unpackString();
final var errMsg = unpacker.tryUnpackNil() ? null : unpacker.unpackString();
final var errStackTrace = unpacker.tryUnpackNil() ? null : unpacker.unpackString();
assertArrayEquals(MAGIC, magic);
assertEquals(3, major);
assertEquals(0, minor);
assertEquals(0, patch);
assertEquals(PROTOCOL_COMPATIBILITY_ERR, code);
assertThat(errMsg, containsString("Unsupported version: 2.8.0"));
assertEquals("org.apache.ignite.lang.IgniteException", errClassName);
assertNull(errStackTrace);
}
}
private void writeAndFlushLoop(Socket socket) throws Exception {
var stop = System.currentTimeMillis() + 5000;
var out = socket.getOutputStream();
while (System.currentTimeMillis() < stop) {
out.write(1);
out.flush();
}
}
private void setupAuthentication(String username, String password) {
securityConfiguration.change(change -> {
change.changeEnabled(true);
change.changeAuthentication().changeProviders().create("basic", authenticationProviderChange -> {
authenticationProviderChange.convert(BasicAuthenticationProviderChange.class)
.changeUsers(users -> users.create(username, user -> user.changePassword(password)));
});
}).join();
}
}