From 283ed5252040847ccccd95ce374b5a71a4b1b4a5 Mon Sep 17 00:00:00 2001 From: Justin Clark Date: Thu, 30 Jun 2016 17:34:07 -0700 Subject: [PATCH 1/6] Add ssh-keys core plugin --- Makefile | 2 +- plugins/ssh-keys/commands | 33 +++++++++++++++++++++++++ plugins/ssh-keys/functions | 14 +++++++++++ plugins/ssh-keys/plugin.toml | 4 +++ plugins/ssh-keys/subcommands/add | 22 +++++++++++++++++ plugins/ssh-keys/subcommands/list | 12 +++++++++ plugins/ssh-keys/subcommands/remove | 15 ++++++++++++ tests/unit/30_ssh_keys.bats | 38 +++++++++++++++++++++++++++++ tests/unit/test_helper.bash | 10 ++++++++ 9 files changed, 149 insertions(+), 1 deletion(-) create mode 100755 plugins/ssh-keys/commands create mode 100755 plugins/ssh-keys/functions create mode 100644 plugins/ssh-keys/plugin.toml create mode 100755 plugins/ssh-keys/subcommands/add create mode 100755 plugins/ssh-keys/subcommands/list create mode 100755 plugins/ssh-keys/subcommands/remove create mode 100644 tests/unit/30_ssh_keys.bats 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/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..1a2f4f19b --- /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-kegen 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..b9e3b2ddf --- /dev/null +++ b/plugins/ssh-keys/subcommands/add @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/ssh-keys/functions" + +add_keys(){ + declare desc="add a new key via sshcommand" + 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..5eb165e4f --- /dev/null +++ b/plugins/ssh-keys/subcommands/list @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/ssh-keys/functions" + +list_ssh_keys() { + declare desc="List ssh key hashes" + 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..415d3a528 --- /dev/null +++ b/plugins/ssh-keys/subcommands/remove @@ -0,0 +1,15 @@ +#!/usr/bin/env bash +set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x +source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" +source "$PLUGIN_CORE_AVAILABLE_PATH/ssh-keys/functions" + +remove_key() { + declare desc="Removes key from authorized_keys" + 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/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..206b6a04f 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -117,6 +117,12 @@ create_app() { dokku apps:create "$TEST_APP" } + +create_key() { + touch "$DOKKU_ROOT/.ssh/authorized_keys" && chown dokku:dokku "$DOKKU_ROOT/.ssh/authorized_keys" + 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 +130,10 @@ destroy_app() { return "$RC" } +destroy_key() { + rm -f "$DOKKU_ROOT/.ssh/authorized_keys" /tmp/testkey* &> /dev/null || true +} + add_domain() { dokku domains:add "$TEST_APP" "$1" } From 2d0374ec52ef0f96204aa76c436cac08a2484a93 Mon Sep 17 00:00:00 2001 From: Justin Clark Date: Fri, 1 Jul 2016 11:42:16 -0700 Subject: [PATCH 2/6] ssh-keys updates based on feedback --- deb.mk | 2 +- plugins/ssh-keys/functions | 2 +- plugins/ssh-keys/subcommands/add | 5 +++-- plugins/ssh-keys/subcommands/list | 3 ++- plugins/ssh-keys/subcommands/remove | 3 ++- 5 files changed, 9 insertions(+), 6 deletions(-) diff --git a/deb.mk b/deb.mk index 27875cc30..305320581 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/plugins/ssh-keys/functions b/plugins/ssh-keys/functions index 1a2f4f19b..32e7e971e 100755 --- a/plugins/ssh-keys/functions +++ b/plugins/ssh-keys/functions @@ -5,7 +5,7 @@ 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-kegen check." + 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() { diff --git a/plugins/ssh-keys/subcommands/add b/plugins/ssh-keys/subcommands/add index b9e3b2ddf..165993a25 100755 --- a/plugins/ssh-keys/subcommands/add +++ b/plugins/ssh-keys/subcommands/add @@ -1,10 +1,11 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_CORE_AVAILABLE_PATH/ssh-keys/functions" +source "$PLUGIN_AVAILABLE_PATH/ssh-keys/functions" -add_keys(){ +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 diff --git a/plugins/ssh-keys/subcommands/list b/plugins/ssh-keys/subcommands/list index 5eb165e4f..ce485bbd6 100755 --- a/plugins/ssh-keys/subcommands/list +++ b/plugins/ssh-keys/subcommands/list @@ -1,10 +1,11 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_CORE_AVAILABLE_PATH/ssh-keys/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 } diff --git a/plugins/ssh-keys/subcommands/remove b/plugins/ssh-keys/subcommands/remove index 415d3a528..3e7e5c5fe 100755 --- a/plugins/ssh-keys/subcommands/remove +++ b/plugins/ssh-keys/subcommands/remove @@ -1,10 +1,11 @@ #!/usr/bin/env bash set -eo pipefail; [[ $DOKKU_TRACE ]] && set -x source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" -source "$PLUGIN_CORE_AVAILABLE_PATH/ssh-keys/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 From d2854b9e900141617c827b02970d7bd23633dac0 Mon Sep 17 00:00:00 2001 From: Justin Clark Date: Fri, 1 Jul 2016 16:23:16 -0700 Subject: [PATCH 3/6] Modifying the ssh-keys tests --- tests/unit/test_helper.bash | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index 206b6a04f..4a4e188f6 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -119,7 +119,6 @@ create_app() { create_key() { - touch "$DOKKU_ROOT/.ssh/authorized_keys" && chown dokku:dokku "$DOKKU_ROOT/.ssh/authorized_keys" ssh-keygen -P "" -f /tmp/testkey &> /dev/null } @@ -131,7 +130,7 @@ destroy_app() { } destroy_key() { - rm -f "$DOKKU_ROOT/.ssh/authorized_keys" /tmp/testkey* &> /dev/null || true + rm -f /tmp/testkey* &> /dev/null || true } add_domain() { From c8405ddf343973b406044b32de76cd3ac3b613eb Mon Sep 17 00:00:00 2001 From: Justin Clark Date: Tue, 5 Jul 2016 13:19:52 -0700 Subject: [PATCH 4/6] add ssh-keys/user-auth to prevent non admin access --- plugins/ssh-keys/user-auth | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100755 plugins/ssh-keys/user-auth diff --git a/plugins/ssh-keys/user-auth b/plugins/ssh-keys/user-auth new file mode 100755 index 000000000..b07a09d9b --- /dev/null +++ b/plugins/ssh-keys/user-auth @@ -0,0 +1,11 @@ +#!/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" || true +} + +[[ "$3" == ssh-keys* ]] && check_ssh_keys_user "$@" || true From 9aef528edc80791493085b3c04fbb6264b02779f Mon Sep 17 00:00:00 2001 From: Justin Clark Date: Tue, 5 Jul 2016 13:43:08 -0700 Subject: [PATCH 5/6] Updated user-managementm docs for ssh-keys plugin --- docs/deployment/user-management.md | 41 +++++++++++++++--------------- 1 file changed, 21 insertions(+), 20 deletions(-) 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`): From fb2c7af9e103d384eac2e7678e0ce05276819667 Mon Sep 17 00:00:00 2001 From: Justin Clark Date: Tue, 5 Jul 2016 15:56:38 -0700 Subject: [PATCH 6/6] fix shellcheck issue with ssh-keys/user-auth --- plugins/ssh-keys/user-auth | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plugins/ssh-keys/user-auth b/plugins/ssh-keys/user-auth index b07a09d9b..92fd0de49 100755 --- a/plugins/ssh-keys/user-auth +++ b/plugins/ssh-keys/user-auth @@ -5,7 +5,9 @@ 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" || true + [[ "$SSH_USER" == "root" || "$SSH_NAME" == *admin* ]] || dokku_log_fail "You must be root, or a dokku admin, to execute this command" } -[[ "$3" == ssh-keys* ]] && check_ssh_keys_user "$@" || true +if [[ "$3" == ssh-keys* ]]; then + check_ssh_keys_user "$@" +fi