1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
|
# Selecting an `IoServiceFactoryFactory`
As part of the their initialization, both client and server code require the specification of a `IoServiceFactoryFactory`
that is used to initialize network connections.
```java
SshServer server = ...create server instance...
server.setIoServiceFactoryFactory(new MyIoServiceFactoryFactory());
SshClient client = ... create client instance ...
client.setIoServiceFactoryFactory(new MyIoServiceFactoryFactory());
```
If not set explicitly during the client/server setup code, then a factory is automatically detected and selected when the
client/server is `#start()`-ed. The used `IoServiceFactoryFactory` is a **singleton** that is lazy created the 1st time
`DefaultIoServiceFactoryFactory#create` is invoked. The selection process is as follows:
* The `org.apache.sshd.common.io.IoServiceFactoryFactory` system property is examined for a factory specification. The
specification can be either a **fully-qualified** class name or one of the `BuiltinIoServiceFactoryFactories` values.
* If no specific factory is specified, then the [ServiceLoader#load](https://docs.oracle.com/javase/tutorial/ext/basics/spi.html#register-service-providers)
mechanism is used to detect and instantiate any registered services in any `META-INF\services\org.apache.sshd.common.io.IoServiceFactoryFactory`
location in the classpath. If **exactly one** implementation was instantiated, then it is used. If several such implementations are found then
an exception is thrown.
* Otherwise, the built-in `Nio2ServiceFactoryFactory` is used.
**Note:** the default command line scripts for SSH/SCP/SFTP client/server are set up to use NIO2 as their default provider,
unless overridden via the `-io` command line option. The `org.apache.sshd.common.io.IoServiceFactoryFactory` system property does
not apply for the command line wrappers since they look only for the `-io` option and use it to initialize the client/server **explicitly**
before starting the client/server. Therefore, the default selection process described in this section does not apply for them.
# Advanced configuration and interaction
## Properties and inheritance model
The code's behavior is highly customizable not only via non-default implementations of interfaces but also as far as
the **parameters** that govern its behavior - e.g., timeouts, min./max. values, allocated memory size, etc... All the
customization related code flow implements a **hierarchical** `PropertyResolver` inheritance model where the "closest"
entity is consulted first, and then its "owner", and so on until the required value is found. If the entire hierarchy
yielded no specific result, then some pre-configured default is used. E.g., if a channel requires some parameter in order
to decide how to behave, then the following configuration hierarchy is consulted:
* The channel-specific configuration
* The "owning" session configuration
* The "owning" client/server instance configuration
* The system properties - **Note:** any configuration value required by the code can be provided via a system property bearing
the `org.apache.sshd.config` prefix - see `SyspropsMapWrapper` for the implementation details.
The easiest way to configure a target instance (client/server/session/channel) is via one of the (many) available `PropertyResolverUtils`
`updateProperty` methods:
```java
PropertyResolverUtils.updateProperty(client, "prop1", 5L);
PropertyResolverUtils.updateProperty(server, "prop2", someInteger);
PropertyResolverUtils.updateProperty(session, "prop3", "hello world");
PropertyResolverUtils.updateProperty(channel, "prop4", false);
```
**Note**: the `updateProperty` method(s) accept **any** `Object` so care must be taken to provide the expected type. However, at
least for **primitive** values, the various `getXXXProperty` methods automatically convert compatible types:
```java
PropertyResolverUtils.updateProperty(client, "prop1", 7365L);
// all will yield 7365 converted to the relevant type
Long value = PropertyResolverUtils.getLongProperty(client, "prop1");
Integer value = PropertyResolverUtils.getLongProperty(client, "prop1");
```
including strings
```java
PropertyResolverUtils.updateProperty(client, "prop1", "7365");
// all will yield 7365
Long value = PropertyResolverUtils.getLongProperty(client, "prop1");
Integer value = PropertyResolverUtils.getLongProperty(client, "prop1");
```
### Using the inheritance model for fine-grained/targeted configuration
As previously mentioned, this hierarchical lookup model is not limited to "simple" configuration values (strings, integers, etc.), but
used also for **interfaces/implementations** such as cipher/MAC/compression/authentication/etc. factories - the exception being that
the system properties are not consulted in such a case. This code behavior provides highly customizable fine-grained/targeted control
of the code's behavior - e.g., one could impose usage of specific ciphers/authentication methods/etc. or present different public key
"identities"/welcome banner behavior/etc., based on address, username or whatever other decision parameter is deemed relevant by the
user's code. This can be done on __both__ sides of the connection - client or server. E.g., the client could present different keys
based on the server's address/identity string/welcome banner, or the server could accept only specific types of authentication methods
based on the client's address/username/etc... This can be done in conjunction with the usage of the various `EventListener`-s provided
by the code (see below).
One of the code locations where this behavior can be leveraged is when the server provides __file-based__ services (SCP, SFTP) in
order to provide a different/limited view of the available files based on the username - see the section dealing with `FileSystemFactory`-ies.
## Welcome banner configuration
According to [RFC 4252 - section 5.4](https://tools.ietf.org/html/rfc4252#section-5.4) the server may send a welcome
banner message during the authentication process. Both the message contents and the phase at which it is sent can be
configured/customized.
### Welcome banner content customization
The welcome banner contents are controlled by the `CoreModuleProperties#WELCOME_BANNER` configuration
key - there are several possible values for this key:
* A simple string - in which case its contents are the welcome banner.
* A file [URI](https://docs.oracle.com/javase/8/docs/api/java/net/URI.html) - or a string starting with `"file:/"` followed by the file path - see below.
* A [URL](https://docs.oracle.com/javase/8/docs/api/java/net/URL.html) - or a string containing "://" - in which
case the [URL#openStream()](https://docs.oracle.com/javase/8/docs/api/java/net/URL.html#openStream) method is invoked
and its contents are read.
* A [File](https://docs.oracle.com/javase/8/docs/api/java/io/File.html) or
a [Path](https://docs.oracle.com/javase/8/docs/api/java/nio/file/Path.html) - in this case, the file's contents are __re-loaded__ every time it is required and sent as the banner contents.
* The special value `CoreModuleProperties#AUTO_WELCOME_BANNER_VALUE` which generates a combined "random art" of
all the server's keys as described in `Perrig A.` and `Song D.`-s article
[Hash Visualization: a New Technique to improve Real-World Security](http://sparrow.ece.cmu.edu/~adrian/projects/validation/validation.pdf) - _International Workshop on Cryptographic Techniques and E-Commerce (CrypTEC '99)_
* One can also override the `ServerUserAuthService#resolveWelcomeBanner` method and use whatever other content customization one sees fit.
**Note:**
1. If any of the sources yields an empty string or is missing (in the case of a resource) then no welcome banner message is sent.
2. If the banner is loaded from a file or URL resource, then one can configure the [Charset](https://docs.oracle.com/javase/8/docs/api/java/nio/charset/Charset.html) used to convert the file's contents into a string via the `ServerAuthenticationManager.WELCOME_BANNER_CHARSET` configuration key (default=`UTF-8`).
3. In this context, see also the `CoreModuleProperties#WELCOME_BANNER_LANGUAGE` configuration key - which
provides control over the declared language tag, although most clients seem to ignore it.
### Welcome banner sending phase
According to [RFC 4252 - section 5.4](https://tools.ietf.org/html/rfc4252#section-5.4):
> The SSH server may send an SSH_MSG_USERAUTH_BANNER message at any time after this authentication protocol starts and before authentication is successful.
The code contains a `WelcomeBannerPhase` enumeration that can be used to configure via the `CoreModuleProperties#WELCOME_BANNER_PHASE`
configuration key the authentication phase at which the welcome banner is sent (see also the `CoreModuleProperties#DEFAULT_BANNER_PHASE` value).
In this context, note that if the `NEVER` phase is configured, no banner will be sent even if one has been configured via one of the methods mentioned previously.
## `HostConfigEntryResolver`
This interface provides the ability to intervene during the connection and authentication phases and "re-write"
the user's original parameters. The `DefaultConfigFileHostEntryResolver` instance used to set up the default
client instance follows the [SSH config file](https://www.digitalocean.com/community/tutorials/how-to-configure-custom-connection-options-for-your-ssh-client)
standards, but the interface can be replaced so as to implement whatever proprietary logic is required.
```java
SshClient client = SshClient.setUpDefaultClient();
client.setHostConfigEntryResolver(new MyHostConfigEntryResolver());
client.start();
/*
* The resolver might decide to connect to some host2/port2 using user2 and password2
* (or maybe using some key instead of the password).
*/
try (ClientSession session = client.connect(user1, host1, port1).verify(...timeout...).getSession()) {
session.addPasswordIdentity(...password1...);
session.auth().verify(...timeout...);
}
```
### SSH Jumps
The SSH client can be configured to use [SSH proxy jumps](https://en.wikibooks.org/wiki/OpenSSH/Cookbook/Proxies_and_Jump_Hosts).
A *jump host* (also known as a *jump server*) is an intermediary host or an SSH gateway to a remote network,
through which a connection can be made to another host in a dissimilar security zone, for example a demilitarized
zone (DMZ). It bridges two dissimilar security zones and offers controlled access between them.
Starting from SSHD version 2.6.0, the *ProxyJump* host configuration entry is honored when using the `SshClient`
to connect to a host. The `SshClient` built by default reads the `~/.ssh/config` file. The various CLI clients
also honor the `-J` command line option to specify one or more jumps.
In order to manually configure jumps, you need to build a `HostConfigEntry` with a `proxyJump` and use it
to connect to the server:
```java
ConnectFuture future = client.connect(new HostConfigEntry(
"", host, port, user,
proxyUser + "@" + proxyHost + ":" + proxyPort));
```
The configuration options specified in the configuration file for the jump hosts are also honored.
## `SshConfigFileReader`
Can be used to read various standard SSH [client](http://linux.die.net/man/5/ssh_config)
or [server](http://manpages.ubuntu.com/manpages/precise/en/man5/sshd_config.5.html) configuration files
and initialize the client/server respectively. Including (among other things), bind address, ciphers,
signature, MAC(s), KEX protocols, compression, welcome banner, etc..
### Reserved messages
The implementation can be used to intercept and process the [SSH_MSG_IGNORE](https://tools.ietf.org/html/rfc4253#section-11.2),
[SSH_MSG_DEBUG](https://tools.ietf.org/html/rfc4253#section-11.3) and [SSH_MSG_UNIMPLEMENTED](https://tools.ietf.org/html/rfc4253#section-11.4)
messages. The handler can be registered on either side - server or client, as well as on the session. A special
[patch](https://issues.apache.org/jira/browse/SSHD-699) has been introduced that automatically ignores such messages
if they are malformed - i.e., they never reach the handler.
#### SSH message stream "stuffing" and keys re-exchange
[RFC 4253 - section 9](https://tools.ietf.org/html/rfc4253#section-9) recommends re-exchanging keys every once in a while
based on the amount of traffic and the selected cipher - the matter is further clarified in [RFC 4251 - section 9.3.2](https://tools.ietf.org/html/rfc4251#section-9.3.2).
These recommendations are mirrored in the code via the `CoreModuleProperties` related `REKEY_TIME_LIMIT`, `REKEY_PACKETS_LIMIT`
and `REKEY_BLOCKS_LIMIT` configuration properties that can be used to configure said behavior - please be sure to read
the relevant _Javadoc_ as well as the aforementioned RFC section(s) when manipulating them. This behavior can also be
controlled programmatically by overriding the `AbstractSession#isRekeyRequired()` method.
As an added security mechanism [RFC 4251 - section 9.3.1](https://tools.ietf.org/html/rfc4251#section-9.3.1) recommends adding
"spurious" [SSH_MSG_IGNORE](https://tools.ietf.org/html/rfc4253#section-11.2) messages. This functionality is mirrored in the
`CoreModuleProperties` related `IGNORE_MESSAGE_FREQUENCY`, `IGNORE_MESSAGE_VARIANCE` and `IGNORE_MESSAGE_SIZE`
configuration properties that can be used to configure said behavior - please be sure to read the relevant _Javadoc_ as well
as the aforementioned RFC section when manipulating them. This behavior can also be controlled programmatically by overriding
the `AbstractSession#resolveIgnoreBufferDataLength()` method.
### `RequestHandler`(s)
The code supports both [global](https://tools.ietf.org/html/rfc4254#section-4) and [channel-specific](https://tools.ietf.org/html/rfc4254#section-5.4)
requests via the registration of `RequestHandler`(s). The global handlers are derived from `ConnectionServiceRequestHandler`(s) whereas the channel-specific
ones are derived from `ChannelRequestHandler`(s). In order to add a handler one need only register the correct implementation and handle the request when
it is detected. For global request handlers this is done by registering them on the server:
```java
// NOTE: the following code can be employed on BOTH client and server - the example is for the server
SshServer server = SshServer.setUpDefaultServer();
List<RequestHandler<ConnectionService>> oldGlobals = server.getGlobalRequestHandlers();
// Create a copy in case current one is null/empty/un-modifiable
List<RequestHandler<ConnectionService>> newGlobals = new ArrayList<>();
if (GenericUtils.size(oldGlobals) > 0) {
newGlobals.addAll(oldGLobals);
}
newGlobals.add(new MyGlobalRequestHandler());
server.setGlobalRequestHandlers(newGlobals);
```
For channel-specific requests, one uses the channel's `add/removeRequestHandler` method to manage its handlers. The way
request handlers are invoked when a global/channel-specific request is received is as follows:
* All currently registered handlers' `process` method is invoked with the request type string parameter (among others).
The implementation should examine the request parameters and decide whether it is able to process it.
* If the handler returns `Result.Unsupported` then the next registered handler is invoked.
In other words, processing stops at the **first** handler that returned a valid response. Thus the importance of
the `List<RequestHandler<...>>` that defines the **order** in which the handlers are invoked. **Note**: while
it is possible to register multiple handlers for the same request and rely on their order, it is highly recommended
to avoid this situation as it makes debugging the code and diagnosing problems much more difficult.
* If no handler reported a valid result value then a failure message is sent back to the peer. Otherwise, the returned
result is translated into the appropriate success/failure response (if the sender asked for a response). In this context,
the handler may choose to build and send the response within its own code, in which case it should return the
`Result.Replied` value indicating that it has done so.
```java
public class MySpecialChannelRequestHandler implements ChannelRequestHandler {
...
@Override
public Result process(Channel channel, String request, boolean wantReply, Buffer buffer) throws Exception {
if (!"my-special-request".equals(request)) {
return Result.Unsupported; // Not mine - maybe someone else can handle it
}
...handle the request - can read more parameters from the message buffer...
return Result.ReplySuccess/Failure/Replied; // signal processing result
}
}
```
#### Default registered handlers
* `exit-signal`, `exit-status` - As described in [RFC4254 section 6.10](https://tools.ietf.org/html/rfc4254#section-6.10)
* `*@putty.projects.tartarus.org` - As described in [Appendix F: SSH-2 names specified for PuTTY](http://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixF.html)
* `hostkeys-prove-00@openssh.com`, `hostkeys-00@openssh.com` - As described in [OpenSSH protocol - section 2.5](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL)
* `tcpip-forward`, `cancel-tcpip-forward` - As described in [RFC4254 section 7](https://tools.ietf.org/html/rfc4254#section-7)
* `keepalive@*` - Used by many client implementations (including this one) to "ping" the server and keep/make sure the connection is still alive.
In this context, the SSHD code allows the user to configure both the frequency and content of the heartbeat request (including whether
to send this request at all) via the `ClientFactoryManager`-s `HEARTBEAT_INTERVAL`, `HEARTBEAT_REQUEST` and `DEFAULT_KEEP_ALIVE_HEARTBEAT_STRING`
configuration properties.
* `no-more-sessions@*` - As described in [OpenSSH protocol section 2.2](https://github.com/openssh/openssh-portable/blob/master/PROTOCOL).
In this context, the code consults the `ServerFactoryManagder.MAX_CONCURRENT_SESSIONS` server-side configuration property in order to
decide whether to accept a successfully authenticated session.
## PROXY / SSLH protocol hooks
The code contains [support for "wrapper" protocols](https://issues.apache.org/jira/browse/SSHD-656) such
as [PROXY](http://www.haproxy.org/download/1.6/doc/proxy-protocol.txt) or [sslh](http://www.rutschle.net/tech/sslh.shtml).
The idea is that one can register either a `ClientProxyConnector` or `ServerProxyAcceptor` and intercept
the 1st packet being sent/received (respectively) **before** it reaches the SSHD code. This gives the programmer
the capability to write a front-end that routes outgoing/incoming packets:
* `SshClient/ClientSesssion#setClientProxyConnector` - sets a proxy that intercepts the 1st packet before being relayed to the server
* `SshServer/ServerSession#setServerProxyAcceptor` - sets a proxy that intercepts the 1st incoming packet before being processed by the server
|