diff --git a/Makefile b/Makefile index 7059095a4..a193eb5f4 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ HEROKUISH_VERSION ?= 0.5.19 PROCFILE_VERSION ?= 0.11.0 PLUGN_VERSION ?= 0.5.1 SIGIL_VERSION ?= 0.6.0 -SSHCOMMAND_VERSION ?= 0.11.0 +SSHCOMMAND_VERSION ?= 0.12.0 SSHCOMMAND_URL ?= https://github.com/dokku/sshcommand/releases/download/v${SSHCOMMAND_VERSION}/sshcommand_${SSHCOMMAND_VERSION}_linux_x86_64.tgz PROCFILE_UTIL_URL ?= https://github.com/josegonzalez/go-procfile-util/releases/download/v${PROCFILE_VERSION}/procfile-util_${PROCFILE_VERSION}_linux_x86_64.tgz PLUGN_URL ?= https://github.com/dokku/plugn/releases/download/v${PLUGN_VERSION}/plugn_${PLUGN_VERSION}_linux_x86_64.tgz diff --git a/debian/control b/debian/control index 0162928fe..ff35d333c 100644 --- a/debian/control +++ b/debian/control @@ -3,7 +3,7 @@ Version: 0.22.2 Section: web Priority: optional Architecture: amd64 -Depends: locales, git, cpio, curl, man-db, netcat, sshcommand (>= 0.10.0), docker-engine-cs (>= 17.05.0) | docker-engine (>= 17.05.0) | docker-io (>= 17.05.0) | docker.io (>= 17.05.0) | docker-ce (>= 17.05.0) | docker-ee (>= 17.05.0) | moby-engine, net-tools, software-properties-common, procfile-util (>= 0.11.0), python-software-properties | python3-software-properties, rsyslog, dos2unix, jq +Depends: locales, git, cpio, curl, man-db, netcat, sshcommand (>= 0.12.0), docker-engine-cs (>= 17.05.0) | docker-engine (>= 17.05.0) | docker-io (>= 17.05.0) | docker.io (>= 17.05.0) | docker-ce (>= 17.05.0) | docker-ee (>= 17.05.0) | moby-engine, net-tools, software-properties-common, procfile-util (>= 0.11.0), python-software-properties | python3-software-properties, rsyslog, dos2unix, jq Recommends: herokuish (>= 0.3.4), parallel, dokku-update, dokku-event-listener Pre-Depends: gliderlabs-sigil, nginx (>= 1.8.0) | openresty, dnsutils, cgroupfs-mount | cgroup-lite, plugn (>= 0.3.0), sudo, python3, debconf Maintainer: Jose Diaz-Gonzalez diff --git a/docs/deployment/user-management.md b/docs/deployment/user-management.md index 5aa3e071e..0b0f68109 100644 --- a/docs/deployment/user-management.md +++ b/docs/deployment/user-management.md @@ -3,9 +3,9 @@ > New as of 0.7.0 ``` -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:remove # Remove SSH public key by name +ssh-keys:add [/path/to/key] # Add a new public key by pipe or path +ssh-keys:list [--format text|json] [] # List of all authorized Dokku public ssh keys +ssh-keys:remove [--fingerprint fingerprint|] # Remove SSH public key by name ``` When pushing to Dokku, SSH key-based authorization is the preferred authentication method, for ease of use and increased security. @@ -42,6 +42,20 @@ The keys for a specific user may be listed by specifying a second argument to th dokku ssh-keys:list admin ``` +> 0.22.3 + +The output format may be specified via the `--format` flag. Supported values include `json` and `text`. + +```shell +dokku ssh-keys:list --format json +``` + +This can additionally be used to filter to keys for a particular user: + +```shell +dokku ssh-keys:list --format json admin +``` + ### Adding SSH keys You can add your public key to Dokku with the `ssh-keys:add` command. The output will be the fingerprint of the SSH key: @@ -74,10 +88,16 @@ cat ~/.ssh/id_rsa.pub | make vagrant-acl-add As key names are unique, they can be used to remove a public SSH key. -```SHELL +```shell dokku ssh-keys:remove KEY_NAME ``` +An SSH Key can also be removed by fingerprint. + +```shell +dokku ssh-keys:remove --fingerprint FINGERPRINT +``` + ## Scoping commands to specific users Support for scoping commands to specific users can be added through plugins that take advantage of the [user-auth](/docs/development/plugin-triggers.md#user-auth) plugin trigger to handle command authorization. diff --git a/plugins/ssh-keys/subcommands/list b/plugins/ssh-keys/subcommands/list index df875ee50..2a7ad3085 100755 --- a/plugins/ssh-keys/subcommands/list +++ b/plugins/ssh-keys/subcommands/list @@ -8,10 +8,31 @@ cmd-ssh-keys-list() { declare desc="List ssh key hashes" declare cmd="ssh-keys:list" [[ "$1" == "$cmd" ]] && shift 1 - declare SSH_NAME="$1" + declare NAME="$1" + local FORMAT=text + if [[ "$NAME" == "--format" ]]; then + FORMAT="$2" + NAME="$3" + fi + + if [[ "$FORMAT" != "text" ]] && [[ "$FORMAT" != "json" ]]; then + dokku_log_fail "Invalid output format specified, supported formats: json, text" + fi + + if [[ "$FORMAT" == "text" ]]; then + FORMAT="" + fi verify_ssh_key_file - sshcommand list dokku "$SSH_NAME" + if [[ "$FORMAT" == "json" ]]; then + if [[ -n "$NAME" ]]; then + sshcommand list dokku "" "$FORMAT" | jq -cM "[.[] | select(.name == \"$NAME\")]" + else + sshcommand list dokku "" "$FORMAT" + fi + else + sshcommand list dokku "$NAME" + fi } cmd-ssh-keys-list "$@" diff --git a/plugins/ssh-keys/subcommands/remove b/plugins/ssh-keys/subcommands/remove index b8587bf52..e6da77727 100755 --- a/plugins/ssh-keys/subcommands/remove +++ b/plugins/ssh-keys/subcommands/remove @@ -8,11 +8,20 @@ cmd-ssh-keys-remove() { declare desc="Removes key from authorized_keys" declare cmd="ssh-keys:remove" [[ "$1" == "$cmd" ]] && shift 1 - declare NAME="$1" + declare NAME="$1" FINGERPRINT="$2" verify_ssh_key_file - [[ -z "$NAME" ]] && 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 $?" + if [[ "$NAME" == "--fingerprint" ]]; then + [[ -z "$FINGERPRINT" ]] && dokku_log_fail "A fingerprint is required to remove a key, ie: dokku ssh-keys:remove --fingerprint FINGERPRINT" + elif [[ -z "$NAME" ]]; then + dokku_log_fail "A name is required to remove a key, ie: dokku ssh-keys:remove " + fi + + if [[ "$NAME" == "--fingerprint" ]]; then + sshcommand acl-remove-by-fingerprint dokku "$FINGERPRINT" || dokku_log_fail "sshcommand returned an error $?" + else + sshcommand acl-remove dokku "$NAME" || dokku_log_fail "sshcommand returned an error $?" + fi } cmd-ssh-keys-remove "$@" diff --git a/rpm.mk b/rpm.mk index a2aa7f1d5..77e2e1bf4 100644 --- a/rpm.mk +++ b/rpm.mk @@ -44,7 +44,7 @@ endif --depends 'plugn' \ --depends 'procfile-util >= 0.11.0' \ --depends '/usr/bin/python3' \ - --depends 'sshcommand >= 0.10.0' \ + --depends 'sshcommand >= 0.11.0' \ --depends 'sudo' \ --after-install rpm/dokku.postinst \ --url "https://github.com/$(DOKKU_REPO_NAME)" \ diff --git a/tests/unit/ssh_keys.bats b/tests/unit/ssh_keys.bats index 44cfc08d8..37f079c1e 100644 --- a/tests/unit/ssh_keys.bats +++ b/tests/unit/ssh_keys.bats @@ -173,6 +173,62 @@ teardown() { assert_failure } +@test "(ssh-keys) ssh-keys:remove" { + run /bin/bash -c "dokku ssh-keys:add new-user /tmp/testkey.pub" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list new-user | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_equal "$output" "1" + + run /bin/bash -c "dokku ssh-keys:remove new-user" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list new-user | wc -l" + echo "output: $output" + echo "status: $status" + assert_equal "$output" "0" + + run /bin/bash -c "dokku ssh-keys:remove new-user" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:add new-user /tmp/testkey.pub" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list new-user | wc -l" + echo "output: $output" + echo "status: $status" + assert_equal "$output" "1" + + run /bin/bash -c "dokku ssh-keys:list new-user | cut -d' ' -f1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output_exists + + local fingerprint="$output" + run /bin/bash -c "dokku ssh-keys:remove --fingerprint ${fingerprint}" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list new-user | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_equal "$output" "0" +} + @test "(ssh-keys) ssh-keys:list" { run /bin/bash -c "dokku ssh-keys:list" echo "output: $output" @@ -198,4 +254,166 @@ teardown() { echo "output: $output" echo "status: $status" assert_failure + + run /bin/bash -c 'echo "" > "${DOKKU_ROOT:-/home/dokku}/.ssh/authorized_keys"' + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:add new-user /tmp/testkey.pub" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_equal "$output" "1" + + run /bin/bash -c "dokku ssh-keys:add another-user /tmp/testkey-no-newline.pub" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_equal "$output" "2" + + run /bin/bash -c "dokku ssh-keys:list new-user | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_equal "$output" "1" +} + + +@test "(ssh-keys) ssh-keys:list --format invalid" { + run /bin/bash -c "dokku ssh-keys:list --format invalid" + echo "output: $output" + echo "status: $status" + assert_failure +} + +@test "(ssh-keys) ssh-keys:list --format text" { + run /bin/bash -c "dokku ssh-keys:list --format text" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c 'echo "" >> "${DOKKU_ROOT:-/home/dokku}/.ssh/authorized_keys"' + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list --format text" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list --format text | grep name1" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c "dokku ssh-keys:list --format text name1" + echo "output: $output" + echo "status: $status" + assert_failure + + run /bin/bash -c 'echo "" > "${DOKKU_ROOT:-/home/dokku}/.ssh/authorized_keys"' + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:add new-user /tmp/testkey.pub" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list --format text | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_equal "$output" "1" + + run /bin/bash -c "dokku ssh-keys:add another-user /tmp/testkey-no-newline.pub" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list --format text | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_equal "$output" "2" + + run /bin/bash -c "dokku ssh-keys:list --format text new-user | wc -l" + echo "output: $output" + echo "status: $status" + assert_success + assert_equal "$output" "1" +} + +@test "(ssh-keys) ssh-keys:list --format json" { + run /bin/bash -c "dokku ssh-keys:list --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c 'echo "" >> "${DOKKU_ROOT:-/home/dokku}/.ssh/authorized_keys"' + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list --format json | jq -e ." + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list --format json name1" + echo "output: $output" + echo "status: $status" + assert_success + assert_output "[]" + + run /bin/bash -c 'echo "" > "${DOKKU_ROOT:-/home/dokku}/.ssh/authorized_keys"' + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:add new-user /tmp/testkey.pub" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list --format json | jq length" + echo "output: $output" + echo "status: $status" + assert_success + assert_equal "$output" "1" + + run /bin/bash -c "dokku ssh-keys:add another-user /tmp/testkey-no-newline.pub" + echo "output: $output" + echo "status: $status" + assert_success + + run /bin/bash -c "dokku ssh-keys:list --format json | jq length" + echo "output: $output" + echo "status: $status" + assert_success + assert_equal "$output" "2" + + run /bin/bash -c "dokku ssh-keys:list --format json new-user | jq length" + echo "output: $output" + echo "status: $status" + assert_success + assert_equal "$output" "1" } diff --git a/tests/unit/test_helper.bash b/tests/unit/test_helper.bash index e3fe5c21a..1cdf008ab 100644 --- a/tests/unit/test_helper.bash +++ b/tests/unit/test_helper.bash @@ -78,6 +78,15 @@ assert_equal() { fi } +assert_not_equal() { + if [[ "$1" == "$2" ]]; then + { + echo "unexpected: $1" + echo "actual: $2" + } | flunk + fi +} + # ShellCheck doesn't know about $output from Bats # shellcheck disable=SC2154 assert_output() { @@ -90,6 +99,18 @@ assert_output() { assert_equal "$expected" "$output" } +# ShellCheck doesn't know about $output from Bats +# shellcheck disable=SC2154 +assert_not_output() { + local expected + if [[ $# -eq 0 ]]; then + unexpected="$(cat -)" + else + unexpected="$1" + fi + assert_not_equal "$unexpected" "$output" +} + # ShellCheck doesn't know about $output from Bats # shellcheck disable=SC2154 assert_output_exists() {