pax_global_header00006660000000000000000000000064150576334730014527gustar00rootroot0000000000000052 comment=7f8b8254db406afae4f7535c5d74a84e23ed878c dokku-sshcommand-7f8b825/000077500000000000000000000000001505763347300153455ustar00rootroot00000000000000dokku-sshcommand-7f8b825/.github/000077500000000000000000000000001505763347300167055ustar00rootroot00000000000000dokku-sshcommand-7f8b825/.github/.ruby-version000066400000000000000000000000061505763347300213460ustar00rootroot000000000000003.3.1 dokku-sshcommand-7f8b825/.github/Gemfile000066400000000000000000000001311505763347300201730ustar00rootroot00000000000000source 'https://rubygems.org' ruby file: ".ruby-version" gem "fpm" gem "package_cloud" dokku-sshcommand-7f8b825/.github/Gemfile.lock000066400000000000000000000023471505763347300211350ustar00rootroot00000000000000GEM remote: https://rubygems.org/ specs: arr-pm (0.0.12) backports (3.25.0) cabin (0.9.0) clamp (1.3.2) domain_name (0.6.20240107) dotenv (3.1.4) fpm (1.16.0) arr-pm (~> 0.0.11) backports (>= 2.6.2) cabin (>= 0.6.0) clamp (>= 1.0.0) pleaserun (~> 0.0.29) rexml stud highline (2.0.3) http-accept (1.7.0) http-cookie (1.0.5) domain_name (~> 0.5) insist (1.0.0) json_pure (2.3.1) mime-types (3.5.2) mime-types-data (~> 3.2015) mime-types-data (3.2024.0507) mustache (0.99.8) netrc (0.11.0) package_cloud (0.3.14) highline (~> 2.0.0) json_pure (~> 2.3.0) rainbow (= 2.2.2) rest-client (~> 2.0) thor (~> 1.2) pleaserun (0.0.32) cabin (> 0) clamp dotenv insist mustache (= 0.99.8) stud rainbow (2.2.2) rake rake (13.2.1) rest-client (2.1.0) http-accept (>= 1.7.0, < 2.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 4.0) netrc (~> 0.8) rexml (3.3.9) stud (0.0.23) thor (1.4.0) PLATFORMS arm64-darwin-23 ruby DEPENDENCIES fpm package_cloud RUBY VERSION ruby 3.3.1p55 BUNDLED WITH 2.5.9 dokku-sshcommand-7f8b825/.github/dependabot.yml000066400000000000000000000006431505763347300215400ustar00rootroot00000000000000--- version: 2 updates: - package-ecosystem: "bundler" directory: "/.github" schedule: interval: "daily" - package-ecosystem: "docker" directory: "/" schedule: interval: "weekly" labels: - "type: dependencies" - package-ecosystem: github-actions directory: "/" schedule: interval: daily open-pull-requests-limit: 10 labels: - "type: dependencies" dokku-sshcommand-7f8b825/.github/linters/000077500000000000000000000000001505763347300203655ustar00rootroot00000000000000dokku-sshcommand-7f8b825/.github/linters/.markdown-lint.yml000066400000000000000000000003361505763347300237560ustar00rootroot00000000000000--- default: true # Line length # https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md013 MD013: false # Inline HTML # https://github.com/DavidAnson/markdownlint/blob/main/doc/Rules.md#md033 MD033: false dokku-sshcommand-7f8b825/.github/linters/.yamllint.yml000066400000000000000000000000641505763347300230170ustar00rootroot00000000000000--- extends: default rules: line-length: disable dokku-sshcommand-7f8b825/.github/workflows/000077500000000000000000000000001505763347300207425ustar00rootroot00000000000000dokku-sshcommand-7f8b825/.github/workflows/bump-version.yml000066400000000000000000000024441505763347300241170ustar00rootroot00000000000000--- name: "bump-version" # yamllint disable-line rule:truthy on: workflow_dispatch: inputs: bump_type: description: "Bump type" default: "patch" required: true type: choice options: - patch - minor - major env: GITHUB_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} jobs: bump-version: name: bump-version runs-on: ubuntu-24.04 steps: - name: Checkout uses: actions/checkout@v5 with: fetch-depth: 0 token: ${{ env.GITHUB_ACCESS_TOKEN }} - name: Get Latest Tag id: latest-tag run: | echo GIT_LATEST_TAG="$(git describe --tags "$(git rev-list --tags --max-count=1)")" >>"$GITHUB_OUTPUT" - name: Compute Next Tag id: next-tag uses: docker://ghcr.io/dokku/semver-generator:latest with: bump: ${{ github.event.inputs.bump_type }} input: ${{ steps.latest-tag.outputs.GIT_LATEST_TAG }} - name: Create and Push Tag run: | git config --global user.name 'Dokku Bot' git config --global user.email no-reply@dokku.com git tag "$GIT_NEXT_TAG" git push origin "$GIT_NEXT_TAG" env: GIT_NEXT_TAG: ${{ steps.next-tag.outputs.version }} dokku-sshcommand-7f8b825/.github/workflows/ci.yml000066400000000000000000000014331505763347300220610ustar00rootroot00000000000000--- name: CI # yamllint disable-line rule:truthy on: pull_request: branches: - "*" push: branches: - "main" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: build: name: build runs-on: ubuntu-24.04 steps: - uses: actions/checkout@v5 - name: setup run: | make version ci-setup env: PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} GITHUB_ACCESS_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }} - name: build run: make build-docker-image build-in-docker - name: test run: make validate-in-docker - name: upload packages uses: actions/upload-artifact@v4 with: name: build path: build dokku-sshcommand-7f8b825/.github/workflows/lint.yml000066400000000000000000000032751505763347300224420ustar00rootroot00000000000000--- name: "lint" # yamllint disable-line rule:truthy on: pull_request: branches: - "*" push: branches: - "main" concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: hadolint: name: hadolint runs-on: ubuntu-24.04 steps: - name: Clone uses: actions/checkout@v5 - name: Run hadolint uses: hadolint/hadolint-action@3fc49fb50d59c6ab7917a2e4195dba633e515b29 markdown-lint: name: markdown-lint runs-on: ubuntu-24.04 steps: - name: Clone uses: actions/checkout@v5 - name: Run markdown-lint uses: avto-dev/markdown-lint@04d43ee9191307b50935a753da3b775ab695eceb with: config: ".github/linters/.markdown-lint.yml" args: "./README.md" shellcheck: name: shellcheck runs-on: ubuntu-24.04 steps: - name: Clone uses: actions/checkout@v5 - name: Run shellcheck uses: ludeeus/action-shellcheck@00cae500b08a931fb5698e11e79bfbd38e612a38 env: SHELLCHECK_OPTS: -e SC2034 shfmt: name: shfmt runs-on: ubuntu-24.04 steps: - name: Clone uses: actions/checkout@v5 - name: Run shfmt uses: luizm/action-sh-checker@17bd25a6ee188d2b91f677060038f4ba37ba14b2 env: SHFMT_OPTS: -l -bn -ci -i 2 -d with: sh_checker_shellcheck_disable: true yamllint: name: yamllint runs-on: ubuntu-24.04 steps: - name: Clone uses: actions/checkout@v5 - name: Run yamllint uses: ibiqlik/action-yamllint@2576378a8e339169678f9939646ee3ee325e845c with: config_file: ".github/linters/.yamllint.yml" dokku-sshcommand-7f8b825/.github/workflows/tagged-release.yml000066400000000000000000000034071505763347300243420ustar00rootroot00000000000000--- name: "tagged-release" # yamllint disable-line rule:truthy on: push: tags: - "*" permissions: attestations: write id-token: write contents: write jobs: tagged-release: name: tagged-release runs-on: ubuntu-24.04 env: CI_BRANCH: release PACKAGECLOUD_REPOSITORY: dokku/dokku VERSION: ${{ github.ref_name }} steps: - name: Checkout uses: actions/checkout@v5 - name: Get Repository Name id: repo-name run: | echo "REPOSITORY_NAME=$(echo "${{ github.repository }}" | cut -d '/' -f 2)" >> $GITHUB_OUTPUT - name: Build binaries run: | mkdir -p dist make version build/linux/${{ steps.repo-name.outputs.REPOSITORY_NAME }} cp build/linux/${{ steps.repo-name.outputs.REPOSITORY_NAME }} dist/${{ steps.repo-name.outputs.REPOSITORY_NAME }} - name: Setup Ruby uses: ruby/setup-ruby@v1.257.0 with: bundler-cache: true working-directory: .github - name: Build Debian Packages run: | bundle exec make build/deb/${{ steps.repo-name.outputs.REPOSITORY_NAME }}_${{ github.ref_name }}_all.deb cp build/deb/*.deb dist/ env: BUNDLE_GEMFILE: .github/Gemfile - name: Upload Artifacts uses: actions/upload-artifact@v4 with: name: dist path: dist/* - name: Release to PackageCloud run: bundle exec make release-packagecloud env: BUNDLE_GEMFILE: .github/Gemfile PACKAGECLOUD_TOKEN: ${{ secrets.PACKAGECLOUD_TOKEN }} - name: Release uses: softprops/action-gh-release@v2 with: files: dist/* generate_release_notes: true make_latest: "true" dokku-sshcommand-7f8b825/.gitignore000066400000000000000000000000421505763347300173310ustar00rootroot00000000000000.env* sshcommand.bak test-results dokku-sshcommand-7f8b825/Dockerfile000066400000000000000000000005531505763347300173420ustar00rootroot00000000000000FROM golang:1.25.1-bookworm # hadolint ignore=DL3027 RUN apt-get update \ && apt install apt-transport-https build-essential curl gnupg2 jq lintian rsync rubygems-integration ruby-dev ruby software-properties-common sudo -qy \ && apt-get clean \ && rm -rf /var/lib/apt/lists/* # hadolint ignore=DL3028 RUN gem install --quiet rake fpm package_cloud dokku-sshcommand-7f8b825/LICENSE000066400000000000000000000020401505763347300163460ustar00rootroot00000000000000Copyright (C) 2014 Jeff Lindsay Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. dokku-sshcommand-7f8b825/Makefile000066400000000000000000000143661505763347300170170ustar00rootroot00000000000000.PHONY: ci-dependencies shellcheck bats install lint unit-tests test NAME = sshcommand EMAIL = sshcommand@josediazgonzalez.com MAINTAINER = dokku MAINTAINER_NAME = Jose Diaz-Gonzalez REPOSITORY = sshcommand HARDWARE = $(shell uname -m) SYSTEM_NAME = $(shell uname -s | tr '[:upper:]' '[:lower:]') BASE_VERSION ?= 0.17.1 IMAGE_NAME ?= $(MAINTAINER)/$(REPOSITORY) PACKAGECLOUD_REPOSITORY ?= dokku/dokku-betafish ifeq ($(CI_BRANCH),release) VERSION ?= $(BASE_VERSION) DOCKER_IMAGE_VERSION = $(VERSION) else VERSION = $(shell echo "${BASE_VERSION}")build+$(shell git rev-parse --short HEAD) DOCKER_IMAGE_VERSION = $(shell echo "${BASE_VERSION}")build-$(shell git rev-parse --short HEAD) endif version: @sed -i.bak 's/SSHCOMMAND_VERSION=""/SSHCOMMAND_VERSION="$(VERSION)"/' sshcommand && rm sshcommand.bak @echo "$(CI_BRANCH)" @echo "$(VERSION)" @./sshcommand version define PACKAGE_DESCRIPTION Turn SSH into a thin client specifically for your app Simplifies running a single command over SSH, and manages authorized keys (ACL) and users in order to do so. endef export PACKAGE_DESCRIPTION LIST = build release release-packagecloud validate targets = $(addsuffix -in-docker, $(LIST)) .env.docker: @rm -f .env.docker @touch .env.docker @echo "CI_BRANCH=$(CI_BRANCH)" >> .env.docker @echo "GITHUB_ACCESS_TOKEN=$(GITHUB_ACCESS_TOKEN)" >> .env.docker @echo "IMAGE_NAME=$(IMAGE_NAME)" >> .env.docker @echo "PACKAGECLOUD_REPOSITORY=$(PACKAGECLOUD_REPOSITORY)" >> .env.docker @echo "PACKAGECLOUD_TOKEN=$(PACKAGECLOUD_TOKEN)" >> .env.docker @echo "VERSION=$(VERSION)" >> .env.docker build: pre-build @$(MAKE) build/darwin/$(NAME) @$(MAKE) build/linux/$(NAME) @$(MAKE) build/deb/$(NAME)_$(VERSION)_all.deb build-docker-image: docker build --rm -q -t $(IMAGE_NAME):build . $(targets): %-in-docker: .env.docker docker run \ --env-file .env.docker \ --rm \ --volume /var/lib/docker:/var/lib/docker \ --volume /var/run/docker.sock:/var/run/docker.sock:ro \ --volume ${PWD}:/src/github.com/$(MAINTAINER)/$(REPOSITORY) \ --workdir /src/github.com/$(MAINTAINER)/$(REPOSITORY) \ $(IMAGE_NAME):build make -e $(@:-in-docker=) build/darwin/$(NAME): chmod +x sshcommand mkdir -p build/darwin cp -f sshcommand build/darwin/sshcommand build/linux/$(NAME): chmod +x sshcommand mkdir -p build/linux cp -f sshcommand build/linux/sshcommand build/deb/$(NAME)_$(VERSION)_all.deb: build/linux/$(NAME) chmod 644 LICENSE export SOURCE_DATE_EPOCH=$(shell git log -1 --format=%ct) \ && mkdir -p build/deb \ && fpm \ --architecture all \ --category admin \ --depends adduser \ --depends coreutils \ --depends jq \ --depends libc-bin \ --depends openssh-client \ --description "$$PACKAGE_DESCRIPTION" \ --input-type dir \ --license 'MIT License' \ --maintainer "$(MAINTAINER_NAME) <$(EMAIL)>" \ --name $(NAME) \ --output-type deb \ --package build/deb/$(NAME)_$(VERSION)_all.deb \ --url "https://github.com/$(MAINTAINER)/$(REPOSITORY)" \ --vendor "" \ --version $(VERSION) \ --verbose \ build/linux/$(NAME)=/usr/bin/$(NAME) \ LICENSE=/usr/share/doc/$(NAME)/copyright clean: rm -rf build release validation ci-setup: docker version rm -f ~/.gitconfig bin/gh-release: mkdir -p bin curl -o bin/gh-release.tgz -sL https://github.com/progrium/gh-release/releases/download/v2.3.0/gh-release_2.3.0_$(SYSTEM_NAME)_$(HARDWARE).tgz tar xf bin/gh-release.tgz -C bin chmod +x bin/gh-release bin/gh-release-body: mkdir -p bin curl -o bin/gh-release-body "https://raw.githubusercontent.com/dokku/gh-release-body/master/gh-release-body" chmod +x bin/gh-release-body release: bin/gh-release bin/gh-release-body rm -rf release && mkdir release tar -zcf release/$(NAME)_$(VERSION)_linux_$(HARDWARE).tgz -C build/linux $(NAME) tar -zcf release/$(NAME)_$(VERSION)_darwin_$(HARDWARE).tgz -C build/darwin $(NAME) cp build/deb/$(NAME)_$(VERSION)_all.deb release/$(NAME)_$(VERSION)_all.deb bin/gh-release create $(MAINTAINER)/$(REPOSITORY) $(VERSION) $(shell git rev-parse --abbrev-ref HEAD) bin/gh-release-body $(MAINTAINER)/$(REPOSITORY) v$(VERSION) release-packagecloud: @$(MAKE) release-packagecloud-deb release-packagecloud-deb: build/deb/$(NAME)_$(VERSION)_all.deb package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/jammy build/deb/$(NAME)_$(VERSION)_all.deb package_cloud push $(PACKAGECLOUD_REPOSITORY)/ubuntu/noble build/deb/$(NAME)_$(VERSION)_all.deb package_cloud push $(PACKAGECLOUD_REPOSITORY)/debian/bullseye build/deb/$(NAME)_$(VERSION)_all.deb package_cloud push $(PACKAGECLOUD_REPOSITORY)/debian/bookworm build/deb/$(NAME)_$(VERSION)_all.deb package_cloud push $(PACKAGECLOUD_REPOSITORY)/debian/trixie build/deb/$(NAME)_$(VERSION)_all.deb package_cloud push $(PACKAGECLOUD_REPOSITORY)/raspbian/bullseye build/deb/$(NAME)_$(VERSION)_all.deb validate: test mkdir -p validation lintian build/deb/$(NAME)_$(VERSION)_all.deb || true dpkg-deb --info build/deb/$(NAME)_$(VERSION)_all.deb dpkg -c build/deb/$(NAME)_$(VERSION)_all.deb cd validation && ar -x ../build/deb/$(NAME)_$(VERSION)_all.deb ls -lah build/deb validation sha1sum build/deb/$(NAME)_$(VERSION)_all.deb test: lint unit-tests lint: shellcheck bats @echo linting... # SC2034: VAR appears unused - https://github.com/koalaman/shellcheck/wiki/SC2034 # desc is used to declare the description of the function @$(QUIET) find . -not -path '*/\.*' | xargs file | egrep "shell|bash" | awk '{ print $$1 }' | sed 's/://g' | xargs shellcheck -e SC2034 unit-tests: /usr/local/bin/sshcommand @echo running unit tests... @mkdir -p test-results/bats @$(QUIET) TERM=linux bats --report-formatter junit --timing -o test-results/bats tests/unit pre-build: git config --global --add safe.directory $(shell pwd) git status /usr/local/bin/sshcommand: @echo installing sshcommand cp ./sshcommand /usr/local/bin/sshcommand chmod +x /usr/local/bin/sshcommand shellcheck: ifneq ($(shell shellcheck --version >/dev/null 2>&1 ; echo $$?),0) ifeq ($(SYSTEM_NAME),darwin) brew install shellcheck else sudo apt-get update -qq && sudo apt-get install -qq -y shellcheck endif endif bats: ifeq ($(SYSTEM_NAME),darwin) ifneq ($(shell bats --version >/dev/null 2>&1 ; echo $$?),0) brew install bats-core endif else git clone https://github.com/bats-core/bats-core.git /tmp/bats cd /tmp/bats && sudo ./install.sh /usr/local rm -rf /tmp/bats endif dokku-sshcommand-7f8b825/README.md000066400000000000000000000057051505763347300166330ustar00rootroot00000000000000# sshcommand Simplifies running a single command over SSH, and manages authorized keys (ACL) and users in order to do so. It basically simplifies running: ```shell ssh user@server 'ls -l ' ``` into: ```shell ssh ls@server ``` ## Commands ```shell sshcommand create # Creates a local system user and installs sshcommand skeleton sshcommand acl-add # Adds named SSH key to user from STDIN or argument sshcommand acl-remove # Removes SSH key by name sshcommand acl-remove-by-fingerprint # Removes SSH key by fingerprint sshcommand list [] [] # Lists SSH keys by user, an optional name and a optional output format (JSON) sshcommand help # Shows help information sshcommand version # Shows version ``` ## Example On a server, create a new command user: ```shell sshcommand create cmd /path/to/command ``` On your computer, add authorized keys with your key: ```shell cat ~/.ssh/id_rsa.pub | ssh root@server sshcommand acl-add cmd progrium ``` If the public key is already on the server, you may also specify it as an argument: ```shell ssh root@server sshcommand acl-add cmd progrium ~/.ssh/id_rsa.pub ``` By default, key names and fingerprints must be unique. Both of these checks can be disabled by setting the following environment variables to `false`: ```shell export SSHCOMMAND_CHECK_DUPLICATE_FINGERPRINT="false" export SSHCOMMAND_CHECK_DUPLICATE_NAME="false" ``` Now anywhere with the private key you can easily run: ```shell ssh cmd@server ``` Anything you pass as the command string will be appended to the command. You can use this to pass arguments or if your command takes subcommands, expose those subcommands easily. ```shell /path/to/command subcommand ``` Can be run remotely with: ```shell ssh cmd@server subcommand ``` When adding an authorized key, you can also specify custom options for `AUTHORIZED_KEYS` by specifying the `SSHCOMMAND_ALLOWED_KEYS` environment variable. This should be a list of comma-separated options. The default keys are as follows: ```shell no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding ``` This can be useful for cases where the ssh server does not allow certain options or you wish to further constrain a user's environment. Please see `man sshd` for more information. Existing keys can be listed via the `list` subcommand: ```shell # in text format sshcommand list cmd # filter by a particular name sshcommand list cmd progrium # in json format sshcommand list cmd "" json # with name filtering sshcommand list cmd progrium json # ignore validation errors (though they will be printed to stderr) export SSHCOMMAND_IGNORE_LIST_WARNINGS=true sshcommand list cmd ``` dokku-sshcommand-7f8b825/package.json000066400000000000000000000004071505763347300176340ustar00rootroot00000000000000{ "name": "sshcommand", "version": "0.17.1", "description": "Turn SSH into a thin client specifically for your app", "global": "true", "install": "cp sshcommand /usr/local/bin && chmod +x /usr/local/bin/sshcommand", "scripts": [ "sshcommand" ] }dokku-sshcommand-7f8b825/sshcommand000077500000000000000000000241141505763347300174310ustar00rootroot00000000000000#!/usr/bin/env bash set -eo pipefail [[ $SSHCOMMAND_TRACE ]] && set -x shopt -s nocasematch # For case insensitive string matching, for the first parameter if [[ -f /etc/defaults/sshcommand ]]; then # shellcheck disable=SC1091 source /etc/defaults/sshcommand fi declare SSHCOMMAND_VERSION="" declare SSHCOMMAND_CHECK_DUPLICATE_FINGERPRINT=${SSHCOMMAND_CHECK_DUPLICATE_FINGERPRINT:="true"} declare SSHCOMMAND_CHECK_DUPLICATE_NAME=${SSHCOMMAND_CHECK_DUPLICATE_NAME:="true"} cmd-help() { declare desc="Shows help information for a command" declare args="$*" if [[ "$args" ]]; then for cmd; do true; done # last arg local fn="sshcommand-$cmd" fn-info "$fn" 1 fi } fn-args() { declare desc="Inspect a function's arguments" local argline argline=$(type "$1" | grep declare | grep -v "declare desc" | head -1) echo -e "${argline// /"\\n"}" | awk -F= '/=/{print "<"$1">"}' | tr "\\n" " " } fn-desc() { declare desc="Inspect a function's description" desc="" eval "$(type "$1" | grep desc | head -1)" echo "$desc" } fn-info() { declare desc="Inspects a function" declare fn="$1" showsource="$2" local fn_name="${1//sshcommand-/}" echo "$fn_name $(fn-args "$fn")" echo " $(fn-desc "$fn")" echo if [[ "$showsource" ]]; then type "$fn" | tail -n +2 echo fi } fn-print-os-id() { declare desc="Returns the release id of the operating system" local OSRELEASE="${SSHCOMMAND_OSRELEASE:="/etc/os-release"}" if [[ -f $OSRELEASE ]]; then sed -n 's#^ID=\(.*\)#\1#p' "$OSRELEASE" | tr -d '"' else echo unknown fi return 0 } fn-adduser() { declare desc="Add a user to the system" local l_user l_platform l_user=$1 l_platform="$(fn-print-os-id)" case $l_platform in alpine) adduser -D -g "" -s /bin/bash "$l_user" passwd -u "$l_user" ;; debian* | ubuntu | raspbian*) adduser --disabled-password --gecos "" "$l_user" ;; arch | amzn) useradd -m -s /bin/bash "$l_user" usermod -L -aG "$l_user" "$l_user" ;; *) useradd -m -s /bin/bash "$l_user" groupadd "$l_user" usermod -L -aG "$l_user" "$l_user" ;; esac } fn-verify-file() { declare desc="Test that public key is valid" declare file="$1" local has_errors=false local key line=0 local TMP_KEY_FILE TMP_KEY_FILE=$(mktemp "/tmp/dokku-${DOKKU_PID}-${FUNCNAME[0]}.XXXXXX") # shellcheck disable=SC2064 trap "rm -rf '$TMP_KEY_FILE' >/dev/null" RETURN INT TERM EXIT SSHCOMMAND_IGNORE_LIST_WARNINGS="${SSHCOMMAND_IGNORE_LIST_WARNINGS:-false}" while read -r key; do line=$((line + 1)) [[ -z "$key" ]] && continue [[ "$key" =~ ^#.*$ ]] && continue echo "$key" >"$TMP_KEY_FILE" if ! ssh-keygen -lf "$TMP_KEY_FILE" &>/dev/null; then has_errors=true if [[ "$SSHCOMMAND_IGNORE_LIST_WARNINGS" == "false" ]]; then log-warn "${file} line $line failed ssh-keygen check." else log-warn "${file} line $line failed ssh-keygen check, ignoring." fi fi done <"${file}" if [[ "$has_errors" == "true" ]]; then return 1 fi } log-fail() { declare desc="Log fail formatter" echo "$@" 1>&2 exit 1 } log-warn() { declare desc="Log warn formatter" echo "$@" 1>&2 } log-verbose() { declare desc="Log verbose formatter" if [[ -n "$SSHCOMMAND_VERBOSE_OUTPUT" ]]; then echo "$@" fi } sshcommand-create() { declare desc="Creates a local system user and installs sshcommand skeleton" declare USER="$1" COMMAND="$2" local USERHOME if [[ -z "$USER" ]] || [[ -z "$COMMAND" ]]; then log-fail "Usage: sshcommand create" "$(fn-args "sshcommand-create")" fi if id -u "$USER" >/dev/null 2>&1; then log-verbose "User '$USER' already exists" else fn-adduser "$USER" fi USERHOME=$(sh -c "echo ~$USER") mkdir -p "$USERHOME/.ssh" touch "$USERHOME/.ssh/authorized_keys" chmod 0700 "$USERHOME/.ssh/authorized_keys" echo "$COMMAND" >"$USERHOME/.sshcommand" chown -R "$USER" "$USERHOME" } sshcommand-acl-add() { declare desc="Adds named SSH key to user from STDIN or argument" declare USER="$1" NAME="$2" KEY_FILE="$3" local ALLOWED_KEYS FINGERPRINT KEY KEY_FILE KEY_PREFIX NEW_KEY USERHOME if [[ -z "$USER" ]] || [[ -z "$NAME" ]]; then log-fail "Usage: sshcommand acl-add" "$(fn-args "sshcommand-acl-add")" fi getent passwd "$USER" >/dev/null || false USERHOME=$(sh -c "echo ~$USER") NEW_KEY=$(grep "NAME=\\\\\"$NAME"\\\\\" "$USERHOME/.ssh/authorized_keys" || true) if [[ "$SSHCOMMAND_CHECK_DUPLICATE_NAME" == "true" ]] && [[ -n "$NEW_KEY" ]]; then log-fail "Duplicate ssh key name" fi if [[ -z "$KEY_FILE" ]]; then KEY=$(cat) else KEY=$(cat "$KEY_FILE") fi local count count="$(wc -l <<<"$KEY")" [[ "$count" -eq 1 ]] || log-fail "Too many keys provided, set one per invocation of sshcommand acl-add " FINGERPRINT=$(ssh-keygen -lf <(echo "command=\"dummy to fail when options already exist\" $KEY") | awk '{print $2}') if [[ ! "$FINGERPRINT" =~ :.* ]]; then log-fail "Invalid ssh public key" fi if [[ "$SSHCOMMAND_CHECK_DUPLICATE_FINGERPRINT" == "true" ]] && grep -qF "$FINGERPRINT" "$USERHOME/.ssh/authorized_keys"; then log-fail "Duplicate ssh public key specified" fi ALLOWED_KEYS="${SSHCOMMAND_ALLOWED_KEYS:="no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding"}" KEY_PREFIX="command=\"FINGERPRINT=$FINGERPRINT NAME=\\\"$NAME\\\" \`cat $USERHOME/.sshcommand\` \$SSH_ORIGINAL_COMMAND\",$ALLOWED_KEYS" echo "$KEY_PREFIX $KEY" >>"$USERHOME/.ssh/authorized_keys" chmod 0700 "$USERHOME/.ssh/authorized_keys" echo "$FINGERPRINT" } sshcommand-acl-remove() { declare desc="Removes SSH key by name" declare USER="$1" NAME="$2" local USERHOME if [[ -z "$USER" ]] || [[ -z "$NAME" ]]; then log-fail "Usage: sshcommand acl-remove" "$(fn-args "sshcommand-acl-remove")" fi getent passwd "$USER" >/dev/null || false USERHOME=$(sh -c "echo ~$USER") sed --in-place "/ NAME=\\\\\"$NAME\\\\\" /d" "$USERHOME/.ssh/authorized_keys" chmod 0700 "$USERHOME/.ssh/authorized_keys" } sshcommand-acl-remove-by-fingerprint() { declare desc="Removes SSH key by fingerprint" declare USER="$1" FINGERPRINT="$2" local USERHOME if [[ -z "$USER" ]] || [[ -z "$FINGERPRINT" ]]; then log-fail "Usage: sshcommand acl-remove-by-fingerprint" "$(fn-args "sshcommand-acl-remove-by-fingerprint")" fi getent passwd "$USER" >/dev/null || false USERHOME=$(sh -c "echo ~$USER") # shellcheck disable=SC1117 sed --in-place "\#\"FINGERPRINT=$FINGERPRINT #d" "$USERHOME/.ssh/authorized_keys" chmod 0700 "$USERHOME/.ssh/authorized_keys" } sshcommand-list() { declare desc="Lists SSH keys by user, an optional name and a optional output format (JSON)" declare userhome USER="$1" NAME="$2" OUTPUT_TYPE="${3:-$2}" [[ -z "$USER" ]] && log-fail "Usage: sshcommand list" "$(fn-args "sshcommand-list")" SSHCOMMAND_IGNORE_LIST_WARNINGS="${SSHCOMMAND_IGNORE_LIST_WARNINGS:-false}" getent passwd "$USER" >/dev/null || log-fail "\"$USER\" is not a user on this system" userhome=$(sh -c "echo ~$USER") if [[ ! -e "$userhome/.ssh/authorized_keys" ]]; then log-warn "authorized_keys not found for $USER" if [[ "$SSHCOMMAND_IGNORE_LIST_WARNINGS" == "false" ]]; then return 1 fi return fi if [[ ! -s "$userhome/.ssh/authorized_keys" ]]; then log-warn "authorized_keys is empty for $USER" if [[ "$SSHCOMMAND_IGNORE_LIST_WARNINGS" == "false" ]]; then return 1 fi if [[ "$OUTPUT_TYPE" == "json" ]]; then echo "[]" fi return fi if ! fn-verify-file "$userhome/.ssh/authorized_keys" && [[ "$SSHCOMMAND_IGNORE_LIST_WARNINGS" == "false" ]]; then return 1 fi if [[ -n "$OUTPUT_TYPE" ]] && [[ "$OUTPUT_TYPE" == "json" ]]; then data=$(sed --silent --regexp-extended \ 's/^command="FINGERPRINT=(\S+) NAME=(\\"|)(.*)\2 `.*",(\S+) (.*)/{ "fingerprint": "\1", "name": "\3", "SSHCOMMAND_ALLOWED_KEYS": "\4", "public-key": "\5" }/p' \ "$userhome/.ssh/authorized_keys" | tr '\n' ',' | sed '$s/,$/\n/') if [[ -n "$NAME" ]]; then echo "[${data}]" | jq -cM --arg NAME "$NAME" 'map( select (.name == $NAME) )' else echo "[${data}]" fi else OUTPUT="$(sed --silent --regexp-extended \ 's/^command="FINGERPRINT=(\S+) NAME=(\\"|)(.*)\2 `.*",(\S+).*/\1 NAME="\3" SSHCOMMAND_ALLOWED_KEYS="\4"/p' \ "$userhome/.ssh/authorized_keys")" if [[ -n "$NAME" ]]; then echo "$OUTPUT" | grep "NAME=\"$NAME\"" else echo "$OUTPUT" fi fi } sshcommand-help() { declare desc="Shows help information" declare COMMAND="$1" if [[ -n "$COMMAND" ]]; then cmd-help "$COMMAND" return 0 fi echo "sshcommand ${SSHCOMMAND_VERSION}" echo "" printf " %-25s %-30s %s\\n" "create" "$(fn-args "sshcommand-create")" "$(fn-desc "sshcommand-create")" printf " %-25s %-30s %s\\n" "acl-add" "$(fn-args "sshcommand-acl-add")" "$(fn-desc "sshcommand-acl-add")" printf " %-25s %-30s %s\\n" "acl-remove" "$(fn-args "sshcommand-acl-remove")" "$(fn-desc "sshcommand-acl-remove")" printf " %-25s %-30s %s\\n" "acl-remove-by-fingerprint" "$(fn-args "sshcommand-acl-remove-by-fingerprint")" "$(fn-desc "sshcommand-acl-remove-by-fingerprint")" printf " %-25s %-30s %s\\n" "list" "$(fn-args "sshcommand-list")" "$(fn-desc "sshcommand-list")" printf " %-25s %-30s %s\\n" "help" "$(fn-args "sshcommand-help")" "$(fn-desc "sshcommand-help")" printf " %-25s %-30s %s\\n" "version" "$(fn-args "sshcommand-version")" "$(fn-desc "sshcommand-version")" } sshcommand-version() { declare desc="Shows version" echo "sshcommand ${SSHCOMMAND_VERSION}" } main() { declare COMMAND_SUFFIX="$1" if [[ -z "$COMMAND_SUFFIX" ]]; then sshcommand-help "$@" exit 1 fi if [[ "$COMMAND_SUFFIX" == "-h" ]] || [[ "$COMMAND_SUFFIX" == "--help" ]]; then COMMAND_SUFFIX="help" fi if [[ "$COMMAND_SUFFIX" == "-v" ]] || [[ "$COMMAND_SUFFIX" == "--version" ]]; then COMMAND_SUFFIX="version" fi local cmd="sshcommand-$COMMAND_SUFFIX" shift 1 if declare -f "$cmd" >/dev/null; then $cmd "$@" else log-fail "Invalid command" fi } # shellcheck disable=SC2128 if [[ "$0" == "$BASH_SOURCE" ]]; then main "$@" fi dokku-sshcommand-7f8b825/tests/000077500000000000000000000000001505763347300165075ustar00rootroot00000000000000dokku-sshcommand-7f8b825/tests/unit/000077500000000000000000000000001505763347300174665ustar00rootroot00000000000000dokku-sshcommand-7f8b825/tests/unit/core.bats000066400000000000000000000171171505763347300213000ustar00rootroot00000000000000#!/usr/bin/env bats load test_helper setup() { create_user create_test_key "$TEST_KEY_NAME" } teardown() { delete_user delete_test_keys } check_authorized_keys_entry() { # shellcheck disable=SC2034 local KEYFILE_NAME="$1" local ENTRY_ID="$2" run bash -c "sed -n 's/.*\(NAME=\\\\\"${ENTRY_ID}\\\\\"\).*/\1/p' /home/${TEST_USER}/.ssh/authorized_keys" echo "entry: $(grep "$ENTRY_ID" "/home/${TEST_USER}/.ssh/authorized_keys")" echo "output: $output" echo "status: $status" assert_output "NAME=\\\"$ENTRY_ID\\\"" } check_custom_allowed_keys() { local ALLOWED_KEYS="$1" run bash -c "grep ${ALLOWED_KEYS} /home/${TEST_USER}/.ssh/authorized_keys" echo "entry: $(cat "/home/${TEST_USER}/.ssh/authorized_keys")" echo "output: $output" echo "status: $status" assert_success } @test "(core) sshcommand create" { delete_user run bash -c "sshcommand create $TEST_USER ls > /dev/null" echo "output: $output" echo "status: $status" assert_success run bash -c "test -f ~${TEST_USER}/.ssh/authorized_keys" echo "output: $output" echo "status: $status" assert_success run bash -c "grep -F ls ~${TEST_USER}/.sshcommand" echo "output: $output" echo "status: $status" assert_success } @test "(core) sshcommand acl-add" { run bash -c "cat ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub | sshcommand acl-add $TEST_USER user1" echo "output: $output" echo "status: $status" assert_success create_test_key new_key run bash -c "cat ${TEST_KEY_DIR}/new_key.pub | sshcommand acl-add $TEST_USER user2" echo "output: $output" echo "status: $status" assert_success check_authorized_keys_entry "$TEST_KEY_NAME" user1 check_authorized_keys_entry new_key user2 } @test "(core) sshcommand acl-add (as argument)" { run bash -c "sshcommand acl-add $TEST_USER user1 ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub" echo "output: $output" echo "status: $status" assert_success create_test_key new_key run bash -c "sshcommand acl-add $TEST_USER user2 ${TEST_KEY_DIR}/new_key.pub" echo "output: $output" echo "status: $status" assert_success check_authorized_keys_entry "$TEST_KEY_NAME" user1 check_authorized_keys_entry new_key user2 } @test "(core) sshcommand acl-add (custom allowed keys)" { run bash -c "cat ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub | SSHCOMMAND_ALLOWED_KEYS=keys-user1 sshcommand acl-add $TEST_USER user1" echo "output: $output" echo "status: $status" assert_success create_test_key new_key run bash -c "cat ${TEST_KEY_DIR}/new_key.pub | sshcommand acl-add $TEST_USER user2" echo "output: $output" echo "status: $status" assert_success check_authorized_keys_entry "$TEST_KEY_NAME" user1 check_authorized_keys_entry new_key user2 check_custom_allowed_keys keys-user1 check_custom_allowed_keys no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding } @test "(core) sshcommand acl-add (bad key failure)" { run bash -c "echo test_key | sshcommand acl-add $TEST_USER user1" echo "output: $output" echo "status: $status" assert_failure } @test "(core) sshcommand acl-add (with identifier space)" { run bash -c "cat ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub | sshcommand acl-add $TEST_USER 'broken user'" echo "output: $output" echo "status: $status" assert_success check_authorized_keys_entry "$TEST_KEY_NAME" 'broken user' } @test "(core) sshcommand acl-add (with authorized_keys with options)" { run bash -c "sshcommand acl-add $TEST_USER user1 ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub" echo "output: "$output echo "status: "$status assert_success run bash -c "sshcommand acl-add $TEST_USER user2 /home/${TEST_USER}/.ssh/authorized_keys" echo "output: "$output echo "status: "$status assert_failure } @test "(core) sshcommand acl-add (multiple keys)" { create_test_key second_key run bash -c "cat ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub ${TEST_KEY_DIR}/second_key.pub | sshcommand acl-add $TEST_USER user1" echo "output: $output" echo "status: $status" assert_failure } @test "(core) sshcommand acl-add (duplicate key)" { run bash -c "cat ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub | sshcommand acl-add $TEST_USER user1" echo "output: $output" echo "status: $status" assert_success run bash -c "cat ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub | sshcommand acl-add $TEST_USER user1" echo "output: $output" echo "status: $status" assert_failure check_authorized_keys_entry "$TEST_KEY_NAME" user1 run bash -c "cat ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub | sshcommand acl-add $TEST_USER user2" echo "output: $output" echo "status: $status" assert_failure run bash -c "cat ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub | SSHCOMMAND_CHECK_DUPLICATE_NAME=false sshcommand acl-add $TEST_USER user2" echo "output: $output" echo "status: $status" assert_failure run bash -c "cat ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub | SSHCOMMAND_CHECK_DUPLICATE_NAME=false SSHCOMMAND_CHECK_DUPLICATE_FINGERPRINT=false sshcommand acl-add $TEST_USER user2" echo "output: $output" echo "status: $status" assert_success } @test "(core) sshcommand acl-remove" { run bash -c "cat ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub | sshcommand acl-add $TEST_USER user1" echo "output: $output" echo "status: $status" assert_success run bash -c "grep -F \"$(<"${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub")\" ~${TEST_USER}/.ssh/authorized_keys | grep user1" echo "output: $output" echo "status: $status" assert_success run bash -c "sshcommand acl-remove $TEST_USER user1" echo "output: $output" echo "status: $status" assert_success run bash -c "grep -F \"$(<"${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub")\" ~${TEST_USER}/.ssh/authorized_keys | grep user1" echo "output: $output" echo "status: $status" assert_failure } @test "(core) sshcommand list" { run bash -c "cat ${TEST_KEY_DIR}/${TEST_KEY_NAME}.pub | SSHCOMMAND_ALLOWED_KEYS=keys-user1 sshcommand acl-add $TEST_USER user1" echo "output: $output" echo "status: $status" assert_success assert_equal \ "$(ssh-keygen -l -f "/home/${TEST_USER}/.ssh/authorized_keys" | awk '{print $2}') NAME=\"user1\" SSHCOMMAND_ALLOWED_KEYS=\"keys-user1\"" \ "$(sshcommand list "$TEST_USER")" run bash -c "sshcommand acl-remove $TEST_USER user1 && sshcommand list" echo "output: $output" echo "status: $status" assert_failure cp tests/unit/fixtures/authorized_keys/input_variants "/home/${TEST_USER}/.ssh/authorized_keys" run bash -c "sshcommand list $TEST_USER '' json" echo "output: $output" echo "status: $status" assert_equal \ "$(head -n1 tests/unit/fixtures/authorized_keys/sshcommand_list_expected_json_output)" \ "$(sshcommand list "$TEST_USER" "" json)" } @test "(core) sshcommand list (authorized_keys format variants)" { cp tests/unit/fixtures/authorized_keys/input_variants "/home/${TEST_USER}/.ssh/authorized_keys" run bash -c "sshcommand list $TEST_USER" echo "output: $output" echo "status: $status" assert_equal \ "$(cat tests/unit/fixtures/authorized_keys/sshcommand_list_expected_output)" \ "$(sshcommand list "$TEST_USER")" rm "/home/${TEST_USER}/.ssh/authorized_keys" } @test "(core) sshcommand list (json output)" { cp tests/unit/fixtures/authorized_keys/input_variants "/home/${TEST_USER}/.ssh/authorized_keys" run bash -c "sshcommand list $TEST_USER md5 json" echo "output: $output" echo "status: $status" assert_equal \ "$(cat tests/unit/fixtures/authorized_keys/sshcommand_list_expected_json_output_md5_filtered)" \ "$(sshcommand list "$TEST_USER" "md5" json)" rm "/home/${TEST_USER}/.ssh/authorized_keys" } @test "(core) sshcommand help" { run bash -c "sshcommand help | wc -l" echo "output: $output" echo "status: $status" [[ "$output" -ge 7 ]] } dokku-sshcommand-7f8b825/tests/unit/fixtures/000077500000000000000000000000001505763347300213375ustar00rootroot00000000000000dokku-sshcommand-7f8b825/tests/unit/fixtures/authorized_keys/000077500000000000000000000000001505763347300245505ustar00rootroot00000000000000dokku-sshcommand-7f8b825/tests/unit/fixtures/authorized_keys/input_variants000066400000000000000000000033721505763347300275460ustar00rootroot00000000000000command="FINGERPRINT=2a:f7:39:1c:63:80:c4:9e:a8:92:ec:e6:94:91:fa:c0 NAME=\"md5\" `cat /home/sshcommand_user/.sshcommand` $SSH_ORIGINAL_COMMAND",no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9y0nhYCAWn7PAf/jlOOSKNnnlKWQ1qkCLkrkHIzMy6hBjRyicFPZp3h+edkPIsUt0tZxXfxt/duJtFGXGS47n8aaWC4Dwu9V2l2U9kCbXtbvuV270+ayk4ax7imBBuMkUWUYgoxrzd13Z9VcSZYePMfZXqEwLN3+XXAZvK32nMfKZ9b0AX4jraDA3JMAXnBxWxoZk4ic+vlNzOZBjH7BS8XwackuqhjLddNPGjmo/YeBJL5Av32sV+tOvpPx4Zu4mTZXy8WQtx7r+Q+209Lt6eVKjO+FYR1gPcyh77KHeNGRvK0WcpnJAC3yU1wgUnpmNOcZ8F2FUpSdaxjX6JSwV md5 command="FINGERPRINT=SHA256:y/lCsvs2fCWjcYc0whZFYQ3UX/OO2qv+n6wE7D9dCeI NAME=\"sha256\" `cat /home/dokku/.sshcommand` $SSH_ORIGINAL_COMMAND",no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA1F98keUFBulWE04RO2+wv3bXASs3TeBXZwG7l4/DKAGc8Qx+U0wb8hZNRtPo2cZP2NRBnVRe6hYEHhr++ocDkPjWY1vggJu/nhubqVELVg+gblpC3rYJur6c4uoTUx/Ea1JzHZ9CKhQJRUj7t6KDfDYv8CM7mgS71QdL/YifTjDeIqXiaSRnb0Q2HkuKQQ5yxT0TLwN3chp4WfVqYcbWvnjUk23IfKAos/1e7F3DOMvO+gU8OYyV17iXVB2Bmb5YZodEYeomprL+yfeh5Qa8Kswo+fYRKJ+9SLYVAVNGxbpktUyv+DlLxuRD4AsGDWKuDqk5U3O6cK3CNQWYUGSVew== sha256 command="FINGERPRINT=9f:7d:fd:8e:48:86:c5:ed:41:82:f7:df:3c:2c:18:9e NAME=without-quotes `cat /home/dokku/.sshcommand` $SSH_ORIGINAL_COMMAND",no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAx6lOy3WkAieirh3UgZK0x2wwUkmaPeR+tJca4ZXGRYAh/7rqX3MHjIvgne8KDW2KGvPdy5BJpY3wkix9vRF7XU7nrl/dGnHWUT+8IcCKuAKiJQvVU1ZqYrvUXnRMeoRr1diHWoixo9BRxzu4GS4Gg+cFX3wH+qsJvMm5LollUUWy2RKWK2l3RFR6XQ3sUtvPe7FSSI+QYgst1HYQk50tigp1RYIFiLYXnvZrnAFQ05N4PjP15rTJRkOTatuDUzDB9lzEQjUq/Ew0zV8JEIn6Svp68UmeK+IWSMNsHj30k6aiZBHWYvKkrCgqAMZJdh/7nDNePqbUftdS2On9lwENpQ== dokku-sshcommand-7f8b825/tests/unit/fixtures/authorized_keys/sshcommand_list_expected_json_output000066400000000000000000000033521505763347300342170ustar00rootroot00000000000000[{ "fingerprint": "2a:f7:39:1c:63:80:c4:9e:a8:92:ec:e6:94:91:fa:c0", "name": "md5", "SSHCOMMAND_ALLOWED_KEYS": "no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding", "public-key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9y0nhYCAWn7PAf/jlOOSKNnnlKWQ1qkCLkrkHIzMy6hBjRyicFPZp3h+edkPIsUt0tZxXfxt/duJtFGXGS47n8aaWC4Dwu9V2l2U9kCbXtbvuV270+ayk4ax7imBBuMkUWUYgoxrzd13Z9VcSZYePMfZXqEwLN3+XXAZvK32nMfKZ9b0AX4jraDA3JMAXnBxWxoZk4ic+vlNzOZBjH7BS8XwackuqhjLddNPGjmo/YeBJL5Av32sV+tOvpPx4Zu4mTZXy8WQtx7r+Q+209Lt6eVKjO+FYR1gPcyh77KHeNGRvK0WcpnJAC3yU1wgUnpmNOcZ8F2FUpSdaxjX6JSwV md5" },{ "fingerprint": "SHA256:y/lCsvs2fCWjcYc0whZFYQ3UX/OO2qv+n6wE7D9dCeI", "name": "sha256", "SSHCOMMAND_ALLOWED_KEYS": "no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding", "public-key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEA1F98keUFBulWE04RO2+wv3bXASs3TeBXZwG7l4/DKAGc8Qx+U0wb8hZNRtPo2cZP2NRBnVRe6hYEHhr++ocDkPjWY1vggJu/nhubqVELVg+gblpC3rYJur6c4uoTUx/Ea1JzHZ9CKhQJRUj7t6KDfDYv8CM7mgS71QdL/YifTjDeIqXiaSRnb0Q2HkuKQQ5yxT0TLwN3chp4WfVqYcbWvnjUk23IfKAos/1e7F3DOMvO+gU8OYyV17iXVB2Bmb5YZodEYeomprL+yfeh5Qa8Kswo+fYRKJ+9SLYVAVNGxbpktUyv+DlLxuRD4AsGDWKuDqk5U3O6cK3CNQWYUGSVew== sha256" },{ "fingerprint": "9f:7d:fd:8e:48:86:c5:ed:41:82:f7:df:3c:2c:18:9e", "name": "without-quotes", "SSHCOMMAND_ALLOWED_KEYS": "no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding", "public-key": "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAx6lOy3WkAieirh3UgZK0x2wwUkmaPeR+tJca4ZXGRYAh/7rqX3MHjIvgne8KDW2KGvPdy5BJpY3wkix9vRF7XU7nrl/dGnHWUT+8IcCKuAKiJQvVU1ZqYrvUXnRMeoRr1diHWoixo9BRxzu4GS4Gg+cFX3wH+qsJvMm5LollUUWy2RKWK2l3RFR6XQ3sUtvPe7FSSI+QYgst1HYQk50tigp1RYIFiLYXnvZrnAFQ05N4PjP15rTJRkOTatuDUzDB9lzEQjUq/Ew0zV8JEIn6Svp68UmeK+IWSMNsHj30k6aiZBHWYvKkrCgqAMZJdh/7nDNePqbUftdS2On9lwENpQ==" }] sshcommand_list_expected_json_output_md5_filtered000066400000000000000000000011011505763347300365510ustar00rootroot00000000000000dokku-sshcommand-7f8b825/tests/unit/fixtures/authorized_keys[{"fingerprint":"2a:f7:39:1c:63:80:c4:9e:a8:92:ec:e6:94:91:fa:c0","name":"md5","SSHCOMMAND_ALLOWED_KEYS":"no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding","public-key":"ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9y0nhYCAWn7PAf/jlOOSKNnnlKWQ1qkCLkrkHIzMy6hBjRyicFPZp3h+edkPIsUt0tZxXfxt/duJtFGXGS47n8aaWC4Dwu9V2l2U9kCbXtbvuV270+ayk4ax7imBBuMkUWUYgoxrzd13Z9VcSZYePMfZXqEwLN3+XXAZvK32nMfKZ9b0AX4jraDA3JMAXnBxWxoZk4ic+vlNzOZBjH7BS8XwackuqhjLddNPGjmo/YeBJL5Av32sV+tOvpPx4Zu4mTZXy8WQtx7r+Q+209Lt6eVKjO+FYR1gPcyh77KHeNGRvK0WcpnJAC3yU1wgUnpmNOcZ8F2FUpSdaxjX6JSwV md5"}] dokku-sshcommand-7f8b825/tests/unit/fixtures/authorized_keys/sshcommand_list_expected_output000066400000000000000000000007341505763347300331670ustar00rootroot000000000000002a:f7:39:1c:63:80:c4:9e:a8:92:ec:e6:94:91:fa:c0 NAME="md5" SSHCOMMAND_ALLOWED_KEYS="no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding" SHA256:y/lCsvs2fCWjcYc0whZFYQ3UX/OO2qv+n6wE7D9dCeI NAME="sha256" SSHCOMMAND_ALLOWED_KEYS="no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding" 9f:7d:fd:8e:48:86:c5:ed:41:82:f7:df:3c:2c:18:9e NAME="without-quotes" SSHCOMMAND_ALLOWED_KEYS="no-agent-forwarding,no-user-rc,no-X11-forwarding,no-port-forwarding" dokku-sshcommand-7f8b825/tests/unit/fixtures/os/000077500000000000000000000000001505763347300217605ustar00rootroot00000000000000dokku-sshcommand-7f8b825/tests/unit/fixtures/os/alpine-32-os-release000066400000000000000000000002361505763347300254330ustar00rootroot00000000000000NAME="Alpine Linux" ID=alpine VERSION_ID=3.2.3 PRETTY_NAME="Alpine Linux v3.2" HOME_URL="http://alpinelinux.org" BUG_REPORT_URL="http://bugs.alpinelinux.org" dokku-sshcommand-7f8b825/tests/unit/fixtures/os/amzn-2-os-release000066400000000000000000000003241505763347300250430ustar00rootroot00000000000000NAME="Amazon Linux" VERSION="2" ID="amzn" ID_LIKE="centos rhel fedora" VERSION_ID="2" PRETTY_NAME="Amazon Linux 2" ANSI_COLOR="0;33" CPE_NAME="cpe:2.3:o:amazon:amazon_linux:2" HOME_URL="https://amazonlinux.com/" dokku-sshcommand-7f8b825/tests/unit/fixtures/os/arch-os-release000066400000000000000000000003231505763347300246530ustar00rootroot00000000000000NAME="Arch Linux" PRETTY_NAME="Arch Linux" ID=arch ID_LIKE=archlinux ANSI_COLOR="0;36" HOME_URL="https://www.archlinux.org/" SUPPORT_URL="https://bbs.archlinux.org/" BUG_REPORT_URL="https://bugs.archlinux.org/" dokku-sshcommand-7f8b825/tests/unit/fixtures/os/debian-jessie-os-release000066400000000000000000000003261505763347300264430ustar00rootroot00000000000000PRETTY_NAME="Debian GNU/Linux jessie/sid" NAME="Debian GNU/Linux" ID=debian ANSI_COLOR="1;31" HOME_URL="http://www.debian.org/" SUPPORT_URL="http://www.debian.org/support/" BUG_REPORT_URL="http://bugs.debian.org/" dokku-sshcommand-7f8b825/tests/unit/fixtures/os/ubuntu-1404-os-release000066400000000000000000000003711505763347300256510ustar00rootroot00000000000000NAME="Ubuntu" VERSION="14.04.3 LTS, Trusty Tahr" ID=ubuntu ID_LIKE=debian PRETTY_NAME="Ubuntu 14.04.3 LTS" VERSION_ID="14.04" HOME_URL="http://www.ubuntu.com/" SUPPORT_URL="http://help.ubuntu.com/" BUG_REPORT_URL="http://bugs.launchpad.net/ubuntu/" dokku-sshcommand-7f8b825/tests/unit/functions.bats000066400000000000000000000024251505763347300223540ustar00rootroot00000000000000#!/usr/bin/env bats load test_helper @test "(fn) print-os-id (custom path)" { load "/usr/local/bin/sshcommand" SSHCOMMAND_OSRELEASE=$BATS_TEST_DIRNAME/fixtures/os/ubuntu-1404-os-release run "fn-print-os-id" echo "output: $output" echo "status: $status" assert_output "ubuntu" assert_success SSHCOMMAND_OSRELEASE=$BATS_TEST_DIRNAME/fixtures/os/debian-jessie-os-release run "fn-print-os-id" echo "output: $output" echo "status: $status" assert_output "debian" assert_success SSHCOMMAND_OSRELEASE=$BATS_TEST_DIRNAME/fixtures/os/alpine-32-os-release run "fn-print-os-id" echo "output: $output" echo "status: $status" assert_output "alpine" assert_success SSHCOMMAND_OSRELEASE=$BATS_TEST_DIRNAME/fixtures/os/amzn-2-os-release run "fn-print-os-id" echo "output: $output" echo "status: $status" assert_output "amzn" assert_success SSHCOMMAND_OSRELEASE=$BATS_TEST_DIRNAME/fixtures/os/arch-os-release run "fn-print-os-id" echo "output: $output" echo "status: $status" assert_output "arch" assert_success } @test "(fn) print-os-id (invalid path)" { load "/usr/local/bin/sshcommand" SSHCOMMAND_OSRELEASE=/tmp/nonexistent-os-release run "fn-print-os-id" echo "output: $output" echo "status: $status" assert_output "unknown" assert_success } dokku-sshcommand-7f8b825/tests/unit/test_helper.bash000066400000000000000000000040701505763347300226440ustar00rootroot00000000000000#!/usr/bin/env bash # constants TEST_USER=sshcommand_user TEST_KEY_NAME=test_key TEST_KEY_DIR=/tmp/test_keys # test functions flunk() { { if [[ "$#" -eq 0 ]]; then cat - else echo "$*" fi } return 1 } # ShellCheck doesn't know about $status from Bats # shellcheck disable=SC2154 assert_success() { if [[ "$status" -ne 0 ]]; then flunk "command failed with exit status $status" elif [[ "$#" -gt 0 ]]; then assert_output "$1" fi } assert_failure() { if [[ "$status" -eq 0 ]]; then flunk "expected failed exit status" elif [[ "$#" -gt 0 ]]; then assert_output "$1" fi } assert_equal() { if [[ "$1" != "$2" ]]; then { echo "expected: $1" echo "actual: $2" } | flunk fi } # ShellCheck doesn't know about $output from Bats # shellcheck disable=SC2154 assert_output() { local expected if [[ $# -eq 0 ]]; then expected="$(cat -)" else expected="$1" fi assert_equal "$expected" "$output" } # ShellCheck doesn't know about $lines from Bats # shellcheck disable=SC2154 assert_line() { if [[ "$1" -ge 0 ]] 2>/dev/null; then assert_equal "$2" "${lines[$1]}" else local line for line in "${lines[@]}"; do [[ "$line" = "$1" ]] && return 0 done flunk "expected line \`$1'" fi } refute_line() { if [[ "$1" -ge 0 ]] 2>/dev/null; then local num_lines="${#lines[@]}" if [[ "$1" -lt "$num_lines" ]]; then flunk "output has $num_lines lines" fi else local line for line in "${lines[@]}"; do if [[ "$line" = "$1" ]]; then flunk "expected to not find line \`$line'" fi done fi } assert() { if ! "$*"; then flunk "failed: $*" fi } assert_exit_status() { assert_equal "$status" "$1" } # sshcommand helpers create_user() { sshcommand create $TEST_USER ls >/dev/null } delete_user() { userdel --remove $TEST_USER } create_test_key() { KEY_NAME="$1" KEY_NAME=${KEY_NAME:=$TEST_KEY_NAME} mkdir -p $TEST_KEY_DIR ssh-keygen -N '' -q -f "$TEST_KEY_DIR/$KEY_NAME" } delete_test_keys() { rm -rf $TEST_KEY_DIR }