This is an automated email from the ASF dual-hosted git repository. twolf pushed a commit to branch dev_3.0 in repository https://gitbox.apache.org/repos/asf/mina-sshd.git
commit 38efed38b150ae96d8dff2d812a249c8caff8954 Author: Thomas Wolf <tw...@apache.org> AuthorDate: Sat Apr 5 16:23:14 2025 +0200 Server-side HAProxyProtocolFilter Provide an IoFilter that can be added at the front of the filter chain of a server session to handle the HAProxy protocol header. The filter handles both protocol versions 1 and 2. Remove all the ServerProxyAcceptor handling in the server session, and completely drop this interface. The session doesn't need to know about this anymore; user code can just add the filter and have this handled transparently for the session. Move and rewrite the existing ServerProxyAcceptorTest. --- .../server/filter/HAProxyProtocolFilter.java | 90 +++++++++ .../proxyprotocol/ProxyProtocolAcceptor.java | 27 ++- .../proxyprotocolv2/ProxyProtocolV2Acceptor.java | 26 ++- .../server/session/ServerProxyAcceptorTest.java | 211 +++++++++++++++++++++ .../apache/sshd/server/ServerFactoryManager.java | 2 - .../java/org/apache/sshd/server/SshServer.java | 12 -- .../sshd/server/session/AbstractServerSession.java | 32 ---- .../sshd/server/session/ServerProxyAcceptor.java | 53 ------ .../server/session/ServerProxyAcceptorHolder.java | 29 --- .../apache/sshd/server/session/ServerSession.java | 1 - .../sshd/server/ServerProxyAcceptorTest.java | 154 --------------- 11 files changed, 341 insertions(+), 296 deletions(-) diff --git a/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/filter/HAProxyProtocolFilter.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/filter/HAProxyProtocolFilter.java new file mode 100644 index 000000000..91a226d48 --- /dev/null +++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/filter/HAProxyProtocolFilter.java @@ -0,0 +1,90 @@ +/* + * 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.sshd.contrib.server.filter; + +import java.util.Objects; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.sshd.common.filter.InputHandler; +import org.apache.sshd.common.filter.IoFilter; +import org.apache.sshd.common.filter.OutputHandler; +import org.apache.sshd.common.util.Readable; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.common.util.buffer.ByteArrayBuffer; +import org.apache.sshd.contrib.server.session.proxyprotocol.ProxyProtocolAcceptor; +import org.apache.sshd.contrib.server.session.proxyprotocolv2.ProxyProtocolV2Acceptor; +import org.apache.sshd.server.session.ServerSession; + +/** + * A {@link IoFilter} that parses a proxy protocol header (either version 1 or 2) and sets the client address reported + * on the {@link ServerSession}. Useful if a server is running behind a HAProxy. + * + * <p> + * The filter is intended to be added in first place in a {@link ServerSession}'s filter chain. + * </p> + * + * @see <a href="https://www.haproxy.org/download/1.8/doc/proxy-protocol.txt">HAProxy Protocol 1 Documentation</a> + * @see <a href="https://www.haproxy.org/download/2.7/doc/proxy-protocol.txt">HAProxy Protocol 2 Documentation</a> + */ +public class HAProxyProtocolFilter extends IoFilter { + + private AtomicReference<InputHandler> input = new AtomicReference<>(); + + public HAProxyProtocolFilter(ServerSession session) { + input.set(new ProxyHeaderReceiver(session)); + } + + @Override + public InputHandler in() { + return input.get(); + } + + @Override + public OutputHandler out() { + return null; + } + + private class ProxyHeaderReceiver implements InputHandler { + + private final ServerSession session; + + private final ProxyProtocolAcceptor handler = new ProxyProtocolV2Acceptor(); + + private Buffer buffer = new ByteArrayBuffer(); + + ProxyHeaderReceiver(ServerSession session) { + this.session = Objects.requireNonNull(session); + } + + @Override + public synchronized void received(Readable message) throws Exception { + if (buffer == null) { + owner().passOn(message); + } else { + buffer.putBuffer(message); + if (handler.acceptServerProxyMetadata(session, buffer)) { + buffer.compact(); + owner().passOn(buffer); + input.set(null); + buffer = null; + } + } + } + } +} diff --git a/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/session/proxyprotocol/ProxyProtocolAcceptor.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/session/proxyprotocol/ProxyProtocolAcceptor.java index 21f65b08a..ef3a3845c 100644 --- a/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/session/proxyprotocol/ProxyProtocolAcceptor.java +++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/session/proxyprotocol/ProxyProtocolAcceptor.java @@ -28,7 +28,6 @@ import org.apache.sshd.common.util.buffer.Buffer; import org.apache.sshd.common.util.buffer.BufferUtils; import org.apache.sshd.common.util.logging.AbstractLoggingBean; import org.apache.sshd.server.session.AbstractServerSession; -import org.apache.sshd.server.session.ServerProxyAcceptor; import org.apache.sshd.server.session.ServerSession; /** @@ -38,18 +37,35 @@ import org.apache.sshd.server.session.ServerSession; * @see <A HREF="https://gist.github.com/codingtony/a8684c9ffa08ad56899f94d3b6c2a040">Tony Bussieres contribution</A> * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ -public class ProxyProtocolAcceptor extends AbstractLoggingBean implements ServerProxyAcceptor { +public class ProxyProtocolAcceptor extends AbstractLoggingBean { // 108 bytes is the largest buffer needed for the PROXY protocol, but we are a bit more lenient public static final int MAX_PROXY_HEADER_LENGTH = Byte.MAX_VALUE; public static final String PROX_PROTOCOL_PREFIX = "PROXY"; + // 'P' 'R' 'O' 'X' 'Y' ' ' private static final byte[] PROXY_HEADER = new byte[] { 0x50, 0x52, 0x4F, 0x58, 0x59, 0x20 }; public ProxyProtocolAcceptor() { super(); } - @Override + /** + * Invoked by the ProxyProtocolFilter; may get called multiple times if the proxy header is not received in a single + * message. Should parse the proxy header from the beginning of the given {@link Buffer} and return {@code true} if + * the header was fully consumed (or there is no proxy header at all), and {@code false} if the buffer contains only + * a partial (potential) proxy header. + * + * <p> + * Upon a successful return, calling code assumes that the proxy header has been consumed from the buffer and the + * buffer's read position is right after the last byte of the proxy header. + * + * @param session the {@link ServerSession} instance + * @param buffer the received data + * @return {@code true} if successfully extracted the remote client peer meta-data or if there is no proxy + * header at all, {@code false} if more data is required. + * @throws Exception if failed to correctly extract and parse the meta-data, in which case the session will be + * closed + */ public boolean acceptServerProxyMetadata(ServerSession session, Buffer buffer) throws Exception { int mark = buffer.rpos(); int dataLen = buffer.available(); @@ -87,7 +103,7 @@ public class ProxyProtocolAcceptor extends AbstractLoggingBean implements Server proxyPayload.setLength(ppLen - 1); } - return parseProxyHeader(session, proxyPayload.toString(), mark, buffer); + return parseProxyHeader(session, proxyPayload.toString()); } // Could not see LF before MAX_PROXY_HEADER_LENGTH expired @@ -95,8 +111,7 @@ public class ProxyProtocolAcceptor extends AbstractLoggingBean implements Server return false; } - protected boolean parseProxyHeader(ServerSession session, String proxyHeader, int markPosition, Buffer buffer) - throws Exception { + protected boolean parseProxyHeader(ServerSession session, String proxyHeader) { boolean debugEnabled = log.isDebugEnabled(); if (debugEnabled) { log.debug("parseProxyHeader(session={}) parsing header='{}'", session, proxyHeader); diff --git a/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/session/proxyprotocolv2/ProxyProtocolV2Acceptor.java b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/session/proxyprotocolv2/ProxyProtocolV2Acceptor.java index 05d5e9330..f6a3c4e0b 100644 --- a/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/session/proxyprotocolv2/ProxyProtocolV2Acceptor.java +++ b/sshd-contrib/src/main/java/org/apache/sshd/contrib/server/session/proxyprotocolv2/ProxyProtocolV2Acceptor.java @@ -41,9 +41,14 @@ import org.apache.sshd.server.session.ServerSession; */ public class ProxyProtocolV2Acceptor extends ProxyProtocolAcceptor { + // CR LF CR LF NUL CR LF 'Q' 'U' 'I' 'T' LF private static final byte[] PROXY_V2_HEADER = new byte[] { 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A }; + // Minimum protocol V2 header length: the magic header (12 bytes), a version byte, a protocol byte, and a two-byte + // MSB-first unsigned short. + private static final int MIN_HEADER_LENGTH = PROXY_V2_HEADER.length + 4; + private static final char FIELD_SEPARATOR = ' '; public ProxyProtocolV2Acceptor() { @@ -54,10 +59,10 @@ public class ProxyProtocolV2Acceptor extends ProxyProtocolAcceptor { public boolean acceptServerProxyMetadata(ServerSession session, Buffer buffer) throws Exception { int mark = buffer.rpos(); int dataLen = buffer.available(); - if (dataLen < PROXY_V2_HEADER.length) { + if (dataLen < MIN_HEADER_LENGTH) { if (log.isDebugEnabled()) { log.debug("acceptServerProxyMetadata(session={}) incomplete data - {}/{}", session, dataLen, - PROXY_V2_HEADER.length); + MIN_HEADER_LENGTH); } return false; } @@ -88,9 +93,17 @@ public class ProxyProtocolV2Acceptor extends ProxyProtocolAcceptor { proxyPayload.append(FIELD_SEPARATOR).append(familyAndTransport.name()); // Read the data length int dataLength = buffer.getUShort(); + if (dataLength > buffer.available()) { + if (log.isDebugEnabled()) { + log.debug("readProxyV2Header(session={}) incomplete data after header - {}/{}", session, buffer.available(), + dataLength); + } + buffer.rpos(markPosition); + return false; + } // Unix Socket are not supported by SSHD if (familyAndTransport.hasSockAddress()) { - log.warn("parseProxyHeader(session={}) unsupported sub-protocol - {} - continue as usual", session, + log.warn("readProxyV2Header(session={}) unsupported sub-protocol - {} - continue as usual", session, familyAndTransport); // Skip socket address data AddressData.skipUnprocessedData(log, session, buffer, FamilyAndTransport.UNSPEC, dataLength); @@ -100,12 +113,11 @@ public class ProxyProtocolV2Acceptor extends ProxyProtocolAcceptor { AddressData data = AddressData.extractAddressData(log, session, buffer, familyAndTransport, dataLength); proxyPayload.append(FIELD_SEPARATOR).append(data); // Parse the converted proxy header - return parseProxyHeader(session, proxyPayload.toString(), markPosition, buffer); + return parseProxyHeader(session, proxyPayload.toString()); } @Override - protected boolean parseProxyHeader(ServerSession session, String proxyHeader, int markPosition, Buffer buffer) - throws Exception { + protected boolean parseProxyHeader(ServerSession session, String proxyHeader) { String[] proxyFields = GenericUtils.split(proxyHeader, FIELD_SEPARATOR); // Trim all fields just in case more than one space used for (int index = 0; index < proxyFields.length; index++) { @@ -117,6 +129,6 @@ public class ProxyProtocolV2Acceptor extends ProxyProtocolAcceptor { log.debug("parseProxyHeader(session={}) local proxy check", session); return true; } - return super.parseProxyHeader(session, proxyHeader, markPosition, buffer); + return super.parseProxyHeader(session, proxyHeader); } } diff --git a/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/session/ServerProxyAcceptorTest.java b/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/session/ServerProxyAcceptorTest.java new file mode 100644 index 000000000..5e711c212 --- /dev/null +++ b/sshd-contrib/src/test/java/org/apache/sshd/contrib/server/session/ServerProxyAcceptorTest.java @@ -0,0 +1,211 @@ +/* + * 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.sshd.contrib.server.session; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Stream; + +import org.apache.sshd.client.SshClient; +import org.apache.sshd.client.session.ClientSession; +import org.apache.sshd.common.filter.FilterChain; +import org.apache.sshd.common.filter.InputHandler; +import org.apache.sshd.common.filter.IoFilter; +import org.apache.sshd.common.filter.OutputHandler; +import org.apache.sshd.common.io.IoWriteFuture; +import org.apache.sshd.common.random.JceRandom; +import org.apache.sshd.common.random.Random; +import org.apache.sshd.common.session.Session; +import org.apache.sshd.common.session.SessionListener; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.common.util.buffer.ByteArrayBuffer; +import org.apache.sshd.common.util.net.SshdSocketAddress; +import org.apache.sshd.contrib.server.filter.HAProxyProtocolFilter; +import org.apache.sshd.server.SshServer; +import org.apache.sshd.server.session.ServerSession; +import org.apache.sshd.util.test.BaseTestSupport; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +/** + * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> + */ +class ServerProxyAcceptorTest extends BaseTestSupport { + + private static final SshdSocketAddress EXPECTED_CLIENT_ADDRESS = new SshdSocketAddress("7.3.6.5", 7365); + + private static final Random RND = new JceRandom(); + + private SshServer sshd; + private SshClient client; + + ServerProxyAcceptorTest() { + super(); + } + + @BeforeEach + void setUp() throws Exception { + sshd = setupTestServer(); + client = setupTestClient(); + } + + @AfterEach + void tearDown() throws Exception { + if (sshd != null) { + sshd.stop(true); + } + if (client != null) { + client.stop(); + } + } + + static Stream<Arguments> parameters() { + // A V1 proxy header + final byte[] v1Header = "PROXY TCP4 7.3.6.5 7.3.6.6 7365 443\r\n".getBytes(StandardCharsets.US_ASCII); + // A V2 proxy header + Buffer buf = new ByteArrayBuffer(); + buf.putRawBytes(new byte[] { 0x0D, 0x0A, 0x0D, 0x0A, 0x00, 0x0D, 0x0A, 0x51, 0x55, 0x49, 0x54, 0x0A }); + buf.putByte((byte) 0x21); + buf.putByte((byte) 0x11); + final int pos = buf.wpos(); + int length = 2 * Integer.BYTES + 2 * Short.BYTES; + buf.putShort(length); + buf.putUInt(0x07030605); + buf.putUInt(0x07030606); + buf.putShort(7365); + buf.putShort(443); + byte[] v2Header = buf.getCompactData(); + int end = buf.wpos(); + buf.wpos(end + 33333); + RND.fill(buf.array(), end, 33333); + end += 33333; + buf.wpos(pos); + buf.putShort(end - pos - 2); + buf.wpos(end); + return Stream.of( + Arguments.of("V1 separate", v1Header, false), + Arguments.of("V1 combined", v1Header, true), + Arguments.of("V2 separate", v2Header, false), + Arguments.of("V2 combined", v2Header, true), + Arguments.of("No proxy header", null, false), + Arguments.of("V2 large", buf.getCompactData(), false)); + } + + @ParameterizedTest(name = "{0}") + @MethodSource("parameters") + void clientAddressOverride(String name, byte[] proxyMessage, boolean combine) throws Exception { + AtomicReference<SocketAddress> actualClientAddress1 = new AtomicReference<>(); + AtomicReference<SocketAddress> actualClientAddress2 = new AtomicReference<>(); + sshd.addSessionListener(new SessionListener() { + + @Override + public void sessionStarting(Session session) { + if (session instanceof ServerSession) { + // Register the HAProxy filter + FilterChain filters = session.getFilterChain(); + filters.addFirst(new HAProxyProtocolFilter((ServerSession) session)); + } + } + + @Override + public void sessionEvent(Session session, Event event) { + if (session instanceof ServerSession) { + actualClientAddress1.set(((ServerSession) session).getClientAddress()); + } + } + + @Override + public void sessionClosed(Session session) { + if (session instanceof ServerSession) { + actualClientAddress2.set(((ServerSession) session).getClientAddress()); + } + } + }); + sshd.start(); + + client.addSessionListener(new SessionListener() { + + @Override + public void sessionStarting(Session session) { + // Add a filter that writes the fake HAProxy header before the first message + FilterChain filters = session.getFilterChain(); + filters.addFirst(new IoFilter() { + + private boolean proxyHeaderSent; + + @Override + public InputHandler in() { + return null; + } + + @Override + public OutputHandler out() { + return (cmd, message) -> { + if (proxyMessage != null && !proxyHeaderSent) { + if (combine) { + byte[] combined = Arrays.copyOf(proxyMessage, proxyMessage.length + message.available()); + message.getRawBytes(combined, proxyMessage.length, message.available()); + IoWriteFuture future = owner().send(cmd, new ByteArrayBuffer(combined)); + proxyHeaderSent = true; + return future; + } + owner().send(-1, new ByteArrayBuffer(proxyMessage)); + proxyHeaderSent = true; + } + return owner().send(cmd, message); + }; + } + }); + } + }); + client.start(); + + CountDownLatch sessionClosed = new CountDownLatch(1); + try (ClientSession session + = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()).verify(CONNECT_TIMEOUT).getSession()) { + session.addPasswordIdentity(getCurrentTestName()); + session.auth().verify(AUTH_TIMEOUT); + session.close(false).addListener(f -> sessionClosed.countDown()); + } finally { + client.stop(); + } + sessionClosed.await(CLOSE_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS); + SocketAddress address1 = actualClientAddress1.get(); + assertNotNull(address1, "Client address should be set"); + SocketAddress address2 = actualClientAddress2.get(); + assertSame(address1, address2, "Client address should not change"); + assertInstanceOf(InetSocketAddress.class, address1); + InetSocketAddress inet = (InetSocketAddress) address1; + if (proxyMessage != null) { + assertEquals(EXPECTED_CLIENT_ADDRESS.getHostName(), inet.getHostString(), "Host mismatch"); + assertEquals(EXPECTED_CLIENT_ADDRESS.getPort(), inet.getPort(), "Port mismatch"); + } else { + assertEquals("127.0.0.1", inet.getHostString(), "Host mismatch"); + } + } +} diff --git a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java index bd7788e73..3ec73dcc4 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/ServerFactoryManager.java @@ -22,7 +22,6 @@ import java.util.List; import org.apache.sshd.common.FactoryManager; import org.apache.sshd.server.command.CommandFactory; -import org.apache.sshd.server.session.ServerProxyAcceptorHolder; import org.apache.sshd.server.shell.ShellFactory; import org.apache.sshd.server.subsystem.SubsystemFactory; @@ -34,7 +33,6 @@ import org.apache.sshd.server.subsystem.SubsystemFactory; */ public interface ServerFactoryManager extends FactoryManager, - ServerProxyAcceptorHolder, ServerAuthenticationManager { /** diff --git a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java index d956d2dde..24eb21d27 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/SshServer.java @@ -54,7 +54,6 @@ import org.apache.sshd.server.auth.password.PasswordAuthenticator; import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator; import org.apache.sshd.server.command.CommandFactory; import org.apache.sshd.server.session.ServerConnectionServiceFactory; -import org.apache.sshd.server.session.ServerProxyAcceptor; import org.apache.sshd.server.session.ServerUserAuthServiceFactory; import org.apache.sshd.server.session.SessionFactory; import org.apache.sshd.server.shell.ShellFactory; @@ -96,7 +95,6 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa protected String host; protected int port; - private ServerProxyAcceptor proxyAcceptor; private ShellFactory shellFactory; private SessionFactory sessionFactory; private CommandFactory commandFactory; @@ -171,16 +169,6 @@ public class SshServer extends AbstractFactoryManager implements ServerFactoryMa this.sessionFactory = sessionFactory; } - @Override - public ServerProxyAcceptor getServerProxyAcceptor() { - return proxyAcceptor; - } - - @Override - public void setServerProxyAcceptor(ServerProxyAcceptor proxyAcceptor) { - this.proxyAcceptor = proxyAcceptor; - } - @Override public CommandFactory getCommandFactory() { return commandFactory; diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java index 5500df52b..1a7a7ebce 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/session/AbstractServerSession.java @@ -77,7 +77,6 @@ import org.apache.sshd.server.auth.pubkey.PublickeyAuthenticator; * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> */ public abstract class AbstractServerSession extends AbstractSession implements ServerSession { - private ServerProxyAcceptor proxyAcceptor; private SocketAddress clientAddress; private PasswordAuthenticator passwordAuthenticator; private PublickeyAuthenticator publickeyAuthenticator; @@ -97,17 +96,6 @@ public abstract class AbstractServerSession extends AbstractSession implements S return (ServerFactoryManager) super.getFactoryManager(); } - @Override - public ServerProxyAcceptor getServerProxyAcceptor() { - return resolveEffectiveProvider( - ServerProxyAcceptor.class, proxyAcceptor, getFactoryManager().getServerProxyAcceptor()); - } - - @Override - public void setServerProxyAcceptor(ServerProxyAcceptor proxyAcceptor) { - this.proxyAcceptor = proxyAcceptor; - } - @Override public SocketAddress getClientAddress() { return resolvePeerAddress(clientAddress); @@ -412,28 +400,8 @@ public abstract class AbstractServerSession extends AbstractSession implements S @Override protected boolean readIdentification(Buffer buffer) throws Exception { - ServerProxyAcceptor acceptor = getServerProxyAcceptor(); int rpos = buffer.rpos(); boolean debugEnabled = log.isDebugEnabled(); - if (acceptor != null) { - try { - boolean completed = acceptor.acceptServerProxyMetadata(this, buffer); - if (!completed) { - buffer.rpos(rpos); // restore original buffer position - return false; // more data required - } - } catch (Throwable t) { - warn("readIdentification({}) failed ({}) to accept proxy metadata: {}", - this, t.getClass().getSimpleName(), t.getMessage(), t); - - if (t instanceof IOException) { - throw (IOException) t; - } else { - throw new SshException(t); - } - } - } - List<String> ident = doReadIdentification(buffer, true); int numLines = GenericUtils.size(ident); clientVersion = (numLines <= 0) ? null : ident.remove(numLines - 1); diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerProxyAcceptor.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerProxyAcceptor.java deleted file mode 100644 index 0edd68708..000000000 --- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerProxyAcceptor.java +++ /dev/null @@ -1,53 +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.sshd.server.session; - -import org.apache.sshd.common.util.buffer.Buffer; - -/** - * Provides a way to implement proxied connections where some metadata about the client is sent <U>before</U> the actual - * SSH protocol is executed - e.g., the <A HREF=@http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt">PROXY - * protocol</A>. - * - * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - */ -@FunctionalInterface -public interface ServerProxyAcceptor { - /** - * Invoked <U>before</U> any attempt is made to retrieve the SSH client identification data of the standard SSH - * protocol. The implementor should extract whatever data it needs from the data buffer. <B>Note:</B> the method may - * be called <U>several times</U> for the <U>same</U> session even though the original proxy data was successfully - * extracted. This happens in case the client identification line following it is incomplete and thus requires - * waiting for more incoming packets. - * - * @param session The {@link ServerSession} instance - * @param buffer The received data {@link Buffer} - if not the 1st time this method is called because data was - * lacking on last invocation, then the buffer is guaranteed to contain the data from all the - * previous incomplete invocations plus any new received data. If not enough information is - * available, the buffer's read position should be restored to its original value when the method - * was invoked. - * @return {@code true} if successfully extracted the remote client peer meta-data, {@code false} if more - * data is required. Upon successful return the buffer read position is assumed to indicate the - * first character of the SSH identification line - * @throws Exception If failed to correctly extract and parse the meta-data, in which case the session will be - * closed - */ - boolean acceptServerProxyMetadata(ServerSession session, Buffer buffer) throws Exception; -} diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerProxyAcceptorHolder.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerProxyAcceptorHolder.java deleted file mode 100644 index ef7cf8d4a..000000000 --- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerProxyAcceptorHolder.java +++ /dev/null @@ -1,29 +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.sshd.server.session; - -/** - * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - */ -public interface ServerProxyAcceptorHolder { - ServerProxyAcceptor getServerProxyAcceptor(); - - void setServerProxyAcceptor(ServerProxyAcceptor proxyAcceptor); -} diff --git a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java index b566ef54e..33665f267 100644 --- a/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java +++ b/sshd-core/src/main/java/org/apache/sshd/server/session/ServerSession.java @@ -33,7 +33,6 @@ import org.apache.sshd.server.ServerFactoryManager; */ public interface ServerSession extends Session, - ServerProxyAcceptorHolder, ServerAuthenticationManager { /** diff --git a/sshd-core/src/test/java/org/apache/sshd/server/ServerProxyAcceptorTest.java b/sshd-core/src/test/java/org/apache/sshd/server/ServerProxyAcceptorTest.java deleted file mode 100644 index 413494065..000000000 --- a/sshd-core/src/test/java/org/apache/sshd/server/ServerProxyAcceptorTest.java +++ /dev/null @@ -1,154 +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.sshd.server; - -import java.net.SocketAddress; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; - -import org.apache.sshd.client.SshClient; -import org.apache.sshd.client.session.ClientSession; -import org.apache.sshd.common.io.IoSession; -import org.apache.sshd.common.session.Session; -import org.apache.sshd.common.session.SessionListener; -import org.apache.sshd.common.util.buffer.Buffer; -import org.apache.sshd.common.util.buffer.ByteArrayBuffer; -import org.apache.sshd.common.util.io.IoUtils; -import org.apache.sshd.common.util.net.SshdSocketAddress; -import org.apache.sshd.server.ServerTest.TestEchoShellFactory; -import org.apache.sshd.server.session.AbstractServerSession; -import org.apache.sshd.server.session.ServerProxyAcceptor; -import org.apache.sshd.server.session.ServerSession; -import org.apache.sshd.util.test.BaseTestSupport; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.MethodOrderer.MethodName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * @author <a href="mailto:d...@mina.apache.org">Apache MINA SSHD Project</a> - */ -@TestMethodOrder(MethodName.class) -public class ServerProxyAcceptorTest extends BaseTestSupport { - private SshServer sshd; - private SshClient client; - - public ServerProxyAcceptorTest() { - super(); - } - - @BeforeEach - void setUp() throws Exception { - sshd = setupTestServer(); - sshd.setShellFactory(new TestEchoShellFactory()); - client = setupTestClient(); - } - - @AfterEach - void tearDown() throws Exception { - if (sshd != null) { - sshd.stop(true); - } - if (client != null) { - client.stop(); - } - } - - @Test - void clientAddressOverride() throws Exception { - SshdSocketAddress expectedClientAddress = new SshdSocketAddress("7.3.6.5", 7365); - String proxyMetadata = getCurrentTestName() - + " " + expectedClientAddress.getHostName() - + " " + expectedClientAddress.getPort(); - byte[] metaDataBytes = (proxyMetadata + IoUtils.EOL).getBytes(StandardCharsets.UTF_8); - sshd.setServerProxyAcceptor(new ServerProxyAcceptor() { - private final AtomicInteger invocationCount = new AtomicInteger(0); - - @Override - public boolean acceptServerProxyMetadata(ServerSession session, Buffer buffer) throws Exception { - if (buffer.available() < metaDataBytes.length) { - return false; // wait for more data - } - - byte[] rawData = new byte[metaDataBytes.length]; - buffer.getRawBytes(rawData); - outputDebugMessage("acceptServerProxyMetadata(%s) proxy data: %s", session, - new String(rawData, StandardCharsets.UTF_8)); - assertArrayEquals(metaDataBytes, rawData, "Mismatched meta data"); - - int count = invocationCount.incrementAndGet(); - if (count == 1) { - ((AbstractServerSession) session).setClientAddress(expectedClientAddress); - } else { - assertSame(expectedClientAddress, - session.getClientAddress(), - "Mismatched client address for invocation #" + count); - } - return true; // proxy completed - } - }); - - Semaphore sessionSignal = new Semaphore(0); - sshd.addSessionListener(new SessionListener() { - @Override - public void sessionEvent(Session session, Event event) { - verifyClientAddress(event.name(), session); - if (Event.KeyEstablished.equals(event)) { - sessionSignal.release(); - } - } - - @Override - public void sessionClosed(Session session) { - verifyClientAddress("sessionClosed", session); - } - - private void verifyClientAddress(String location, Session session) { - assertObjectInstanceOf(location + ": not a server session", ServerSession.class, session); - SocketAddress actualClientAddress = ((ServerSession) session).getClientAddress(); - assertSame(expectedClientAddress, actualClientAddress, location + ": mismatched client address instance"); - } - }); - sshd.start(); - - client.setClientProxyConnector(session -> { - IoSession ioSession = session.getIoSession(); - ioSession.writeBuffer(new ByteArrayBuffer(metaDataBytes)); - }); - client.start(); - - try (ClientSession session - = client.connect(getCurrentTestName(), TEST_LOCALHOST, sshd.getPort()).verify(CONNECT_TIMEOUT).getSession()) { - session.addPasswordIdentity(getCurrentTestName()); - session.auth().verify(AUTH_TIMEOUT); - assertTrue(sessionSignal.tryAcquire(DEFAULT_TIMEOUT.toMillis(), TimeUnit.MILLISECONDS), - "Failed to receive session signal on time"); - } finally { - client.stop(); - } - } -}