├── .gitignore ├── LICENSE ├── Makefile ├── README.md ├── make_keys ├── rebar.config ├── src ├── erl_sshd.app.src ├── erl_sshd.erl ├── erl_sshd_app.erl └── erl_sshd_sup.erl └── test └── erl_sshd_SUITE.erl /.gitignore: -------------------------------------------------------------------------------- 1 | .eunit 2 | deps 3 | *.o 4 | *.beam 5 | *.plt 6 | erl_crash.dump 7 | ebin 8 | rel/example_project 9 | .concrete/DEV_MODE 10 | .rebar 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | APPS = kernel stdlib sasl erts ssl tools runtime_tools crypto inets \ 2 | public_key mnesia syntax_tools compiler 3 | COMBO_PLT = $(HOME)/.erl_sshd_combo_dialyzer_plt 4 | 5 | .PHONY: all compile deps test clean distclean ct 6 | 7 | all: compile 8 | 9 | compile: 10 | ./rebar get-deps compile 11 | 12 | deps: 13 | ./rebar get-deps 14 | 15 | eunit: compile 16 | ./rebar -v skip_deps=true eunit 17 | 18 | ct: compile 19 | ./rebar -v ct $(CTARGS) 20 | 21 | distclean: clean 22 | ./rebar delete-deps 23 | 24 | clean: 25 | ./rebar clean 26 | 27 | build_plt: compile 28 | dialyzer --build_plt --output_plt $(COMBO_PLT) --apps $(APPS) \ 29 | deps/*/ebin 30 | 31 | check_plt: compile 32 | dialyzer --check_plt --plt $(COMBO_PLT) --apps $(APPS) \ 33 | deps/*/ebin 34 | 35 | dialyzer: compile 36 | @echo 37 | @echo Use "'make check_plt'" to check PLT prior to using this target. 38 | @echo Use "'make build_plt'" to build PLT prior to using this target. 39 | @echo 40 | dialyzer --plt $(COMBO_PLT) ebin 41 | 42 | compile test clean: rebar 43 | 44 | keys: compile 45 | rm -f priv/system_dir/ssh_host_rsa_key* 46 | ssh-keygen -t rsa -P "" -f priv/system_dir/ssh_host_rsa_key 47 | rm -f priv/user_dir/authorized_keys id_rsa id_rsa.pub 48 | ssh-keygen -t rsa -P "" -f id_rsa 49 | chmod 600 id_rsa 50 | cp id_rsa.pub priv/user_dir/authorized_keys 51 | 52 | rebar: 53 | wget -c http://github.com/rebar/rebar/wiki/rebar 54 | chmod +x $@ 55 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # erl_sshd 2 | Wrapper around Erlang ssh module to make it easy to add an ssh shell 3 | to an Erlang node. Add erl_sshd as rebar dependency and add 4 | erl_sshd configuration to your release's sys.config file. 5 | 6 | ## Requirements 7 | * Erlang 17+ 8 | 9 | ## Using 10 | Add erl_sshd as a dependency in your rebar.config file. Then follow the 11 | configuration instructions 12 | 13 | ## Configuration 14 | erl_sshd is configured via its application environment variables. You 15 | can set these in your release's `sys.config` file (see example). 16 | 17 | The `port` environment variable is the listener port number. 18 | 19 | The `app` environment variable is the name of the application that is 20 | using erl_sshd as a dependency. erl_sshd looks in the priv directory 21 | of this application for the system key and authorized keys file. 22 | 23 | You can use security keys and/or usernames and passwords to gain access to the 24 | shell. 25 | 26 | ### Using keys 27 | The host key and the `authorized_keys` file holding the user authorized 28 | keys are in `priv/erl_sshd`. 29 | 30 | You can generate keys with: 31 | 32 | ```sh 33 | % deps/erl_sshd/makekeys 34 | ``` 35 | 36 | This creates a system key as `priv/erl_sshd/ssh_host_rsa_key`, 37 | a public and private user key in the top directory, and a 38 | authorized keys file as `priv/erl_sshd/authorized_keys`. The 39 | authorize keys file contains the generated public user key. 40 | 41 | You may also copy your own keys into the `authorized_keys` file. 42 | 43 | From the top level you can connect to your node using: 44 | 45 | ```sh 46 | % ssh hostname -p 11122 -i id_rsa 47 | ``` 48 | 49 | ### Using usernames and passwords 50 | Follow the instructions above to create keys. The username/password 51 | authentication requires a system key to identify the host. 52 | 53 | The `passwords` environment variable is a list of 54 | two element tuples where the first element is the username and the 55 | second element is the password. These are strings. For example: 56 | 57 | ```erlang 58 | {passwords, [{"lincx","nbv123"}]} 59 | ``` 60 | 61 | allows the user `lincx` to connect with the password `nbv123`. 62 | 63 | ### Example 64 | 65 | ```erlang 66 | [ 67 | {erl_sshd, [ 68 | {app, dobby}, 69 | {port, 11122}, 70 | {passwords, [{"lincx","nbv1234"}, 71 | {"bobs","youruncle"}]} 72 | ]}, 73 | ... % environment variables for other applications in the node 74 | ]. 75 | ``` 76 | -------------------------------------------------------------------------------- /make_keys: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PRIVDIR="priv/erl_sshd" 4 | 5 | # Make the private dir 6 | mkdir -p "$PRIVDIR" 7 | 8 | # make host keys 9 | rm -f "$PRIVDIR/ssh_host_rsa_key*" 10 | ssh-keygen -t rsa -P "" -f "$PRIVDIR/ssh_host_rsa_key" 11 | 12 | # make user keys 13 | rm -f "$PRIVDIR/authorized_keys" id_rsa id_rsa.pub 14 | ssh-keygen -t rsa -P "" -f id_rsa 15 | chmod 600 id_rsa 16 | 17 | # use the user's public key as the first authorized key 18 | cp id_rsa.pub "$PRIVDIR/authorized_keys" 19 | -------------------------------------------------------------------------------- /rebar.config: -------------------------------------------------------------------------------- 1 | {require_otp_vsn, "1[78]"}. 2 | 3 | {cover_enabled, true}. 4 | {cover_print_enabled, true}. 5 | 6 | {eunit_opts, [verbose]}. 7 | {eunit_compile_opts, [{i, "../"}]}. 8 | -------------------------------------------------------------------------------- /src/erl_sshd.app.src: -------------------------------------------------------------------------------- 1 | {application, erl_sshd, 2 | [ 3 | {description, ""}, 4 | {vsn, "1"}, 5 | {registered, []}, 6 | {applications, [ 7 | kernel, 8 | stdlib, 9 | ssh 10 | ]}, 11 | {mod, { erl_sshd_app, []}}, 12 | {env, [{port, 11111}]} 13 | ]}. 14 | -------------------------------------------------------------------------------- /src/erl_sshd.erl: -------------------------------------------------------------------------------- 1 | -module(erl_sshd). 2 | -behaviour(gen_server). 3 | -define(SERVER, ?MODULE). 4 | 5 | %% ------------------------------------------------------------------ 6 | %% API Function Exports 7 | %% ------------------------------------------------------------------ 8 | 9 | -export([start_link/0]). 10 | 11 | %% ------------------------------------------------------------------ 12 | %% gen_server Function Exports 13 | %% ------------------------------------------------------------------ 14 | 15 | -export([init/1, handle_call/3, handle_cast/2, handle_info/2, 16 | terminate/2, code_change/3]). 17 | 18 | %% ------------------------------------------------------------------ 19 | %% API Function Definitions 20 | %% ------------------------------------------------------------------ 21 | 22 | start_link() -> 23 | gen_server:start_link(?MODULE, [], []). 24 | 25 | %% ------------------------------------------------------------------ 26 | %% gen_server Function Definitions 27 | %% ------------------------------------------------------------------ 28 | 29 | init(_) -> 30 | Passwords = application:get_env(erl_sshd, passwords, []), 31 | Port = application:get_env(erl_sshd, port, 11111), 32 | MasterApp = application:get_env(erl_sshd, app, erl_sshd), 33 | PrivDir = filename:join([code:priv_dir(MasterApp), "erl_sshd"]), 34 | gen_server:cast(self(), start), 35 | {ok, #{port => Port, 36 | priv_dir => PrivDir, 37 | passwords => Passwords, 38 | pid => undefined}}. 39 | 40 | handle_call(Request, _From, State) -> 41 | {stop, {unimplemented, call, Request}, State}. 42 | 43 | handle_cast(start, State = #{port := Port, 44 | priv_dir := PrivDir, 45 | passwords := Passwords}) -> 46 | {ok, Pid} = ssh:daemon(Port, [{system_dir, PrivDir}, 47 | {user_dir, PrivDir}, 48 | {user_passwords, Passwords}]), 49 | link(Pid), 50 | {noreply, State#{pid => Pid}, hibernate}; 51 | handle_cast(Msg, State) -> 52 | {stop, {unimplemented, cast, Msg}, State}. 53 | 54 | handle_info(Info, State) -> 55 | {stop, {unimplemented, info, Info}, State}. 56 | 57 | terminate(_Reason, _State) -> 58 | ok. 59 | 60 | code_change(_OldVsn, State, _Extra) -> 61 | {ok, State}. 62 | 63 | %% ------------------------------------------------------------------ 64 | %% Internal Function Definitions 65 | %% ------------------------------------------------------------------ 66 | 67 | -------------------------------------------------------------------------------- /src/erl_sshd_app.erl: -------------------------------------------------------------------------------- 1 | -module(erl_sshd_app). 2 | 3 | -behaviour(application). 4 | 5 | %% Application callbacks 6 | -export([start/2, stop/1]). 7 | 8 | %% =================================================================== 9 | %% Application callbacks 10 | %% =================================================================== 11 | 12 | start(_StartType, _StartArgs) -> 13 | erl_sshd_sup:start_link(). 14 | 15 | stop(_State) -> 16 | ok. 17 | -------------------------------------------------------------------------------- /src/erl_sshd_sup.erl: -------------------------------------------------------------------------------- 1 | -module(erl_sshd_sup). 2 | 3 | -behaviour(supervisor). 4 | 5 | %% API 6 | -export([start_link/0]). 7 | 8 | %% Supervisor callbacks 9 | -export([init/1]). 10 | 11 | %% Helper macro for declaring children of supervisor 12 | -define(CHILD(I, Type), {I, {I, start_link, []}, permanent, 5000, Type, [I]}). 13 | 14 | %% =================================================================== 15 | %% API functions 16 | %% =================================================================== 17 | 18 | start_link() -> 19 | supervisor:start_link({local, ?MODULE}, ?MODULE, []). 20 | 21 | %% =================================================================== 22 | %% Supervisor callbacks 23 | %% =================================================================== 24 | 25 | init([]) -> 26 | {ok, { {one_for_one, 5, 10}, [ 27 | ?CHILD(erl_sshd, worker) 28 | ]} }. 29 | 30 | -------------------------------------------------------------------------------- /test/erl_sshd_SUITE.erl: -------------------------------------------------------------------------------- 1 | %%%============================================================================= 2 | %%% @copyright (C) 2015, Erlang Solutions Ltd 3 | %%% @author Marc Sugiyama 4 | %%% @doc erl_sshd test 5 | %%% @end 6 | %%%============================================================================= 7 | -module(erl_sshd_SUITE). 8 | -copyright("2015, Erlang Solutions Ltd."). 9 | 10 | %% Note: This directive should only be used in test suites. 11 | -compile(export_all). 12 | -include_lib("common_test/include/ct.hrl"). 13 | -include_lib("eunit/include/eunit.hrl"). 14 | 15 | -define(PORT, 15570). 16 | -define(USERNAME, "username"). 17 | -define(PASSWORD, "password"). 18 | 19 | % create keys locally before running tests 20 | 21 | %%%============================================================================= 22 | %%% Callbacks 23 | %%%============================================================================= 24 | 25 | suite() -> 26 | [{timetrap,{minutes,10}}]. 27 | 28 | init_per_suite(Config) -> 29 | start_applications(), 30 | case is_erl_sshd_running() of 31 | false -> 32 | ct:pal(Reason = "sshd server is not running"), 33 | {skip, Reason}; 34 | true -> 35 | Config 36 | end. 37 | 38 | end_per_testcase(_,_) -> 39 | ok. 40 | 41 | all() -> 42 | [can_connect]. 43 | 44 | %%%============================================================================= 45 | %%% Testcases 46 | %%%============================================================================= 47 | 48 | can_connect(_Config) -> 49 | %% GIVEN 50 | 51 | %% WHEN 52 | {ok, Connection} = ssh:connect("127.0.0.1", ?PORT, 53 | [{silently_accept_hosts, true}, 54 | {user_interaction, false}, 55 | {user, ?USERNAME}, 56 | {password, ?PASSWORD}]), 57 | 58 | %% THEN 59 | ok = ssh:close(Connection). 60 | 61 | %%%============================================================================= 62 | %%% Internal functions 63 | %%%============================================================================= 64 | 65 | start_applications() -> 66 | ok = application:load(erl_sshd), 67 | ok = set_env(port, ?PORT), 68 | ok = set_env(app, erl_sshd), 69 | ok = set_env(passwords, [{?USERNAME,?PASSWORD}]), 70 | application:ensure_all_started(erl_sshd). 71 | 72 | is_erl_sshd_running() -> 73 | proplists:is_defined(erl_sshd, application:which_applications()). 74 | 75 | set_env(Key, Value) -> 76 | ok = application:set_env(erl_sshd, Key, Value). 77 | --------------------------------------------------------------------------------