Elixir SSHd

Module Version Hex Docs Total Download License Last Updated

A very simple way to add SSH server capabilities to an Elixir application.

Features

  • Simple way of adding SSH version 2.0 server capabilities to one's application, in a secured manner.
  • Acceptable for production systems; due to the secured nature of SSH version 2.0, and the ability of fine-grain access control and authentication methods available.
  • Quick way to drop in to an Elixir or Erlang REPL.
  • Easiest way to create remote accessible custom shell-like programs.

BOM notice

At runtime :esshd only requires the Erlang runtime and OTP.

During the development of :esshd, some third-party software is used and required. This third-party software is ONLY used as supporting material, for quality assurance purposes. No output or files from the third-party software has been included in any final release software.

Installation

The package can be installed by adding :esshd to your list of dependencies in mix.exs:

def deps do
  [
    {:esshd, "~> 0.3.0"}
  ]
end

After adding :esshd as a dependency, ensure it is started before your own application in mix.exs:

def application do
  [
    extra_applications: [:esshd]
  ]
end

Usage

This Elixir application offers a number of use-cases; and we recommend selecting the solution that best matches your project's desired goal.

Drop-in Secure Remote Elixir REPL

Once installed, add the following configuration to your project:

app_dir = Application.app_dir(:myapp)
priv_dir = Path.join([app_dir, "priv"])

config :esshd,
  enabled: true,
  priv_dir: priv_dir,
  handler: :elixir,
  port: 10_022,
  public_key_authenticator: "Sshd.PublicKeyAuthenticator.AuthorizedKeys"

Once the above configuration is added, your application will require OpenSSH compatible SSH host keys and an authorized_keys file stored inside of your application's priv directory.

To generate the needed OpenSSH host keys, change in to your application's priv directory and execute an appropriate command. An example of such command sequences are as follows:

$ [ -d priv ] || mkdir priv
$ chmod 700 priv
$ cd priv
$ ssh-keygen -N "" -b 256  -t ecdsa -f ssh_host_ecdsa_key
$ ssh-keygen -N "" -b 1024 -t dsa -f ssh_host_dsa_key
$ ssh-keygen -N "" -b 2048 -t rsa -f ssh_host_rsa_key
$ echo 127.0.0.1,127.0.0.1 `cat ssh_host_ecdsa_key.pub` > known_hosts
$ chmod 644 known_hosts

Finally, add all OpenSSH public keys to be accepted in to the authorized_keys file within your application's priv directory.

Drop-in Secure Remote Erlang REPL

Once installed, add the following configuration to your project:

app_dir = Application.app_dir(:myapp)
priv_dir = Path.join([app_dir, "priv"])

config :esshd,
  enabled: true,
  priv_dir: priv_dir,
  handler: :erlang,
  port: 10_022,
  public_key_authenticator: "Sshd.PublicKeyAuthenticator.AuthorizedKeys"

Once the above configuration is added, your application will require OpenSSH compatible SSH host keys and an authorized_keys file stored inside of your application's priv directory.

To generate the needed OpenSSH host keys, change in to your application's priv directory and execute an appropriate command. An example of such command sequences are as follows:

$ [ -d priv ] || mkdir priv
$ chmod 700 priv
$ cd priv
$ ssh-keygen -N "" -b 256  -t ecdsa -f ssh_host_ecdsa_key
$ ssh-keygen -N "" -b 1024 -t dsa -f ssh_host_dsa_key
$ ssh-keygen -N "" -b 2048 -t rsa -f ssh_host_rsa_key
$ echo 127.0.0.1,127.0.0.1 `cat ssh_host_ecdsa_key.pub` > known_hosts
$ chmod 644 known_hosts

Finally, add all OpenSSH public keys to be accepted in to the authorized_keys file within your application's priv directory.

Custom Access Control and Authorization

esshd was designed around the concept of easily changing the methods employed in each of access control and authorization by changing the utilized "handler" of each component - by way of Elixir Behaviors.

The following behaviors exist and may be implemented and easily configured for use, at application boot time.

  • Sshd.AccessList: offers control over the connecting remote IP address and ports, by simple return of a boolean value that states if the remote connection is accepted. While it may seem simplistic, at first, a behavior may be as complex as time- based, quantity of already connected peers, etc.
  • Sshd.PasswordAuthenticator: offers a means for username and password verification. The behavior offers NO throttling or any such complexity and MUST be securely interfaced to a trusted library that performs correct password handling, even under the case of an invalid password - to prevent detection of actual valid user accounts.
  • Sshd.PublicKeyAuthenticator: offers a means for username and public key verification. While many authentication libraries may not offer this ability - when tied to also tasked with password authentication - one could still tie to their user back-end storage to accommodate this behavior. Also, much like password authentication, correct handling it imperative; see above.
  • Sshd.ShellHandler: offers a means for custom, do-it-yourself remote shells, whereby you are in full control, and may implement any IO to-and-from standard input and output streams. An example of such a shell is included, as it is a mildly complex topic.

Configuration Options

The following configuration options are available, with the default setting shown:

  • access_list :: string(Sshd.AccessList.Default): A string containing the fully qualified module that implements the Sshd.AccessList behavior.
  • enabled :: boolean(true): Determines if the SSH server is enabled or not. Useful in complex applications to disable all incoming SSH connection functionality, without a full recompile and deploy.
  • handler :: :erlang | :elixir | {m, f, a}: The handler that should respond to incoming SSH connections. It may be one of the simple atoms, for simplicity; or, it may be a tuple containing a module, function, and arguments. Note that the arguments are not presently used. The module, function, and arguments option must implement the Sshd.ShellHandler behavior.
  • idle_time :: integer(86_400_000 * 3): The amount of time, in milliseconds, an idle connection may remain, before being automatically disconnected. This does not effect actively utilized connections.
  • max_sessions :: integer(50): The maximum number of simultaneous users connected at one time.
  • negotiation_timeout :: integer(11_000): The amount of time, in milliseconds, that a connection has to begin correct phases of entering in to a valid SSH connection, before being flat out disconnected. This setting helps to keep server utilization down due to port scans and other similar problems.
  • parallel_login :: boolean(false): Determines if simultaneous connections are permitted in the authentication phase. This does not effect if multiple users may be connected simultaneously.
  • password_authenticator :: string(Sshd.PasswordAuthenticator.Default): A string containing the fully qualified module that implements the Sshd.PasswordAuthenticator behavior.
  • port :: integer(10_022): The TCP port number of the SSH server process.
  • preferred_algorithms :: tuple: The acceptable hashes, ciphers, and key exchange mechanizisms of the SSH server. The default settings are the same as the underlying default_algorithms/0 function. For information about configuring this complex setting, please read the similarily named configuration option within the function daemon/2.
  • priv_dir :: string(): specifies the location to your own application's priv directory, or any other directory. This directory is utilized for the SSH host keys and is utilized by the Sshd.PublicKeyAuthenticator.AuthorizedKeys module for both user keys and the authorized_keys file.
  • public_key_authenticator :: string(Sshd.PublicKeyAuthenticator.Default): A string containing the fully qualified module that implements the Sshd.PublicKeyAuthenticator behavior.

License

Copyright (C) 2017-2021 Joseph Benden.

Licensed 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.