diff --git a/Makefile b/Makefile index 25081480b..b4db46895 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ DOKKU_VERSION = master -SSHCOMMAND_URL ?= https://raw.githubusercontent.com/dokku/sshcommand/v0.4.0/sshcommand +SSHCOMMAND_URL ?= https://raw.githubusercontent.com/dokku/sshcommand/v0.5.0/sshcommand PLUGN_URL ?= https://github.com/dokku/plugn/releases/download/v0.2.1/plugn_0.2.1_linux_x86_64.tgz SIGIL_URL ?= https://github.com/gliderlabs/sigil/releases/download/v0.4.0/sigil_0.4.0_Linux_x86_64.tgz STACK_URL ?= https://github.com/gliderlabs/herokuish.git diff --git a/deb.mk b/deb.mk index 96f5e1001..f4b70387c 100644 --- a/deb.mk +++ b/deb.mk @@ -26,7 +26,7 @@ Simplifies running a single command over SSH, and manages authorized keys (ACL) and users in order to do so. endef SSHCOMMAND_REPO_NAME ?= dokku/sshcommand -SSHCOMMAND_VERSION ?= 0.4.0 +SSHCOMMAND_VERSION ?= 0.5.0 SSHCOMMAND_ARCHITECTURE = amd64 SSHCOMMAND_PACKAGE_NAME = sshcommand_$(SSHCOMMAND_VERSION)_$(SSHCOMMAND_ARCHITECTURE).deb SSHCOMMAND_URL ?= https://raw.githubusercontent.com/dokku/sshcommand/v$(SSHCOMMAND_VERSION)/sshcommand diff --git a/docs/deployment/user-management.md b/docs/deployment/user-management.md index 1eaaa7459..d600f45ee 100644 --- a/docs/deployment/user-management.md +++ b/docs/deployment/user-management.md @@ -1,40 +1,41 @@ # User Management -While it is possible to use password-based authorization to push to Dokku, it is preferable to use key-based authentication for security. +When pushing to Dokku, ssh key based authorization is the preferred authentication method, for ease of use and increased security. -Users in dokku are managed via the `~/dokku/.ssh/authorized_keys` file. While you *can* manually edit this file, it is **highly** recommended that you follow the below steps to manage users on a dokku server. +Users in Dokku are managed via the `~/dokku/.ssh/authorized_keys` file. It is **highly** recommended that you follow the steps below to manage users on a Dokku server. -## SSHCommand +## Dokku ssh-keys command -Dokku uses the [`sshcommand`](https://github.com/dokku/sshcommand) utility to manage ssh keys for the dokku user. The following is the usage output for sshcommand. +The `dokku ssh-keys` command(s) allow you to manage ssh keys used to push to the Dokku server. The following is the usage output for `dokku ssh-keys`: ``` -sshcommand create # creates a user forced to run command when SSH connects -sshcommand acl-add # adds named SSH key to user from STDIN -sshcommand acl-remove # removes SSH key by name -sshcommand help # displays the usage help message +$ dokku ssh-keys:help +Usage: dokku ssh-keys[:COMMAND] + +Manage public ssh keys that are allowed to connect to Dokku + +Additional commands: + ssh-keys:add [/path/to/key] Add a new public key by pipe or path + ssh-keys:list List of all authorized Dokku public ssh keys + ssh-keys Manage public ssh keys that are allowed to connect to Dokku + ssh-keys:remove Remove SSH public key by name ``` -In dokku's case, the `` section is *always* `dokku`, as this is the system user that the dokku binary performs all it's actions. Keys are given unique names, which can be used in conjunction with the [user-auth](/dokku/development/plugin-triggers/#user-auth) plugin trigger to handle command authorization. +Keys are given unique names, which can be used in conjunction with the [user-auth](/dokku/development/plugin-triggers/#user-auth) plugin trigger to handle command authorization. In Dokku's case, the unique _name_ is just for ease of identifying the keys, the ssh (git) user is *always* `dokku`, as this is the system user that the `dokku` binary uses to perform all it's actions. ## Adding deploy users -You can add your public key to the dokku user's `~/dokku/.ssh/authorized_keys` file with the following command: +You can add your public key to Dokku with the following command: + +`NAME` is the username prefer to use to refer to this particular key. Including the word `admin` in the name will grant the user privileges to add additional keys remotely. ```shell -# from your local machine -# replace dokku.me with your domain name or the host's IP -# replace root with your server's root user -# USER is the username you use to refer to this particular key -cat ~/.ssh/id_rsa.pub | ssh root@dokku.me "sudo sshcommand acl-add dokku USER" +$ dokku ssh-keys:add ``` -At it's base, the `sshcommand` *must* be run under a user with sudo access, as it sets keys for the dokku user. - -For instance, if you stored your public key at `~/.ssh/id_rsa.pub-open` and are deploying to EC2 where the default root-enabled user is `ubuntu`, you can run the following command to add your key under the `superuser` username: - +Admin users and root can also add keys remotely: ```shell -cat ~/.ssh/id_rsa.pub-open | ssh ubuntu@dokku.me "sudo sshcommand acl-add dokku superuser" +cat | ssh dokku@dokku.me ssh-keys:add ``` If you are using the vagrant installation, you can also use the `make vagrant-acl-add` target to add your public key to dokku (it will use your host username as the `USER`): diff --git a/plugins/ssh-keys/commands b/plugins/ssh-keys/commands new file mode 100755 index 000000000..bfd36a0ce --- /dev/null +++ b/plugins/ssh-keys/commands @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +[[ " help ssh-keys:help " == *" $1 "* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x + +case "$1" in + help | ssh-keys:help) + help_content_func () { + declare desc="return ssh-keys plugin help content" + cat< [/path/to/key], Add a new public key by pipe or path + ssh-keys:remove , Remove SSH public key by name +help_content + } + + if [[ $1 = "ssh-keys:help" ]] ; then + echo -e 'Usage: dokku ssh-keys[:COMMAND]' + echo '' + echo 'Manage public ssh keys that are allowed to connect to Dokku' + echo '' + echo 'Additional commands:' + help_content_func | sort | column -c2 -t -s, + else + help_content_func + fi + ;; + + *) + exit "$DOKKU_NOT_IMPLEMENTED_EXIT" + ;; + +esac diff --git a/plugins/ssh-keys/functions b/plugins/ssh-keys/functions new file mode 100755 index 000000000..32e7e971e --- /dev/null +++ b/plugins/ssh-keys/functions @@ -0,0 +1,14 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +verify_ssh_key_file() { + declare desc="Test that public key is valid" + [[ -s ${DOKKU_ROOT}/.ssh/authorized_keys ]] || dokku_log_fail "No public keys found." + ssh-keygen -l -f "${DOKKU_ROOT}/.ssh/authorized_keys" &> /dev/null || dokku_log_fail "${DOKKU_ROOT}/.ssh/authorized_keys failed ssh-keygen check." +} + +verify_ssh_key_exists() { + declare desc="Test that public key exists" + [[ -e ${DOKKU_ROOT}/.ssh/authorized_keys ]] || dokku_log_fail "No public keys found." +} diff --git a/plugins/ssh-keys/plugin.toml b/plugins/ssh-keys/plugin.toml new file mode 100644 index 000000000..7d17ef091 --- /dev/null +++ b/plugins/ssh-keys/plugin.toml @@ -0,0 +1,4 @@ +[plugin] +description = "dokku core ssh-keys plugin" +version = "0.6.4" +[plugin.config] diff --git a/plugins/ssh-keys/subcommands/add b/plugins/ssh-keys/subcommands/add new file mode 100755 index 000000000..165993a25 --- /dev/null +++ b/plugins/ssh-keys/subcommands/add @@ -0,0 +1,23 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/ssh-keys/functions" + +add_keys() { + declare desc="add a new key via sshcommand" + local cmd="ssh-keys:add" + shift + local name="$1" key_file="$2" key_contents key_from_pipe + [[ -p /dev/stdin ]] && read -r key_from_pipe + if [[ -n "$key_from_pipe" ]]; then + ssh-keygen -lf /dev/stdin <<< "$key_from_pipe" &> /dev/null || dokku_log_fail "Key piped in is not a valid ssh public key" + key_contents="$key_from_pipe" + elif [[ -n "$key_file" ]]; then + key_contents="$(cat "$key_file")" + fi + [[ -n "$name" && -n "$key_contents" ]] || dokku_log_fail "Two arguments are required if not piping, ie: dokku ssh-keys:add " + verify_ssh_key_exists + echo "$key_contents" | sshcommand acl-add dokku "$name" || dokku_log_fail "sshcommand returned an error: $?" +} + +add_keys "$@" diff --git a/plugins/ssh-keys/subcommands/list b/plugins/ssh-keys/subcommands/list new file mode 100755 index 000000000..ce485bbd6 --- /dev/null +++ b/plugins/ssh-keys/subcommands/list @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/ssh-keys/functions" + +list_ssh_keys() { + declare desc="List ssh key hashes" + local cmd="ssh-keys:list" + verify_ssh_key_file + sshcommand list dokku +} + +list_ssh_keys "$@" diff --git a/plugins/ssh-keys/subcommands/remove b/plugins/ssh-keys/subcommands/remove new file mode 100755 index 000000000..3e7e5c5fe --- /dev/null +++ b/plugins/ssh-keys/subcommands/remove @@ -0,0 +1,16 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_AVAILABLE_PATH/ssh-keys/functions" + +remove_key() { + declare desc="Removes key from authorized_keys" + local cmd="ssh-keys:remove" + shift + local name="$1" + verify_ssh_key_file + [[ -z $1 ]] && dokku_log_fail "A name is required to remove a key, ie: dokku ssh-keys:remove " + sshcommand acl-remove dokku "$name" || dokku_log_fail "sshcommand returned an error $?" +} + +remove_key "$@" diff --git a/plugins/ssh-keys/user-auth b/plugins/ssh-keys/user-auth new file mode 100755 index 000000000..92fd0de49 --- /dev/null +++ b/plugins/ssh-keys/user-auth @@ -0,0 +1,13 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" + +check_ssh_keys_user() { + declare desc="check user running ssh-keys" + local SSH_USER=$1 SSH_NAME=$2 + [[ "$SSH_USER" == "root" || "$SSH_NAME" == *admin* ]] || dokku_log_fail "You must be root, or a dokku admin, to execute this command" +} + +if [[ "$3" == ssh-keys* ]]; then + check_ssh_keys_user "$@" +fi diff --git a/tests/unit/30_ssh_keys.bats b/tests/unit/30_ssh_keys.bats new file mode 100644 index 000000000..7bd43907e --- /dev/null +++ b/tests/unit/30_ssh_keys.bats @@ -0,0 +1,38 @@ +#!/usr/bin/env bats + +load test_helper + +setup() { + create_key +} + +teardown() { + destroy_key +} + +@test "(ssh-keys) ssh-keys:add, ssh-keys:list, ssh-keys:remove" { + run /bin/bash -c "dokku ssh-keys:add name1 /tmp/testkey.pub" + echo "output: "$output + echo "status: "$status + assert_success + run /bin/bash -c "cat /tmp/testkey.pub | dokku ssh-keys:add name2" + echo "output: "$output + echo "status: "$status + assert_success + run /bin/bash -c "dokku ssh-keys:list | grep name1 && dokku ssh-keys:list | grep name2" + echo "output: "$output + echo "status: "$status + assert_success + run /bin/bash -c "dokku ssh-keys:remove name1" + echo "output: "$output + echo "status: "$status + assert_success + run /bin/bash -c "dokku ssh-keys:list | grep name1" + echo "output: "$output + echo "status: "$status + assert_failure + run /bin/bash -c "dokku ssh-keys:list | grep name2" + echo "output: "$output + echo "status: "$status + assert_success +} diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index 8f518750c..4a4e188f6 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -117,6 +117,11 @@ create_app() { dokku apps:create "$TEST_APP" } + +create_key() { + ssh-keygen -P "" -f /tmp/testkey &> /dev/null +} + destroy_app() { local RC="$1"; local RC=${RC:=0} local APP="$2"; local TEST_APP=${APP:=$TEST_APP} @@ -124,6 +129,10 @@ destroy_app() { return "$RC" } +destroy_key() { + rm -f /tmp/testkey* &> /dev/null || true +} + add_domain() { dokku domains:add "$TEST_APP" "$1" }