├── .editorconfig ├── .gitignore ├── .travis.yml ├── ISSUE_TEMPLATE.md ├── LICENSE.txt ├── Makefile ├── README.md ├── commands ├── common-functions ├── config ├── help-functions ├── internal-functions ├── plugin.toml ├── pre-build ├── pre-delete ├── pre-receive-app ├── subcommands ├── add ├── add-service ├── allowed ├── allowed-services ├── list ├── list-service ├── remove ├── remove-service └── report ├── tests ├── bin │ ├── at-least-version │ ├── docker │ ├── id │ └── lsb_release ├── commands.bats ├── hook_pre_build.bats ├── hook_user_auth.bats ├── setup.sh └── test_helper.bash └── user-auth /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | insert_final_newline = true 5 | indent_style = space 6 | indent_size = 2 7 | 8 | [Makefile] 9 | insert_final_newline = true 10 | indent_style = tab 11 | indent_size = 4 12 | 13 | [*.mk] 14 | insert_final_newline = true 15 | indent_style = tab 16 | indent_size = 4 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/dokku 2 | tests/fixtures 3 | tests/bin/plugn 4 | tests/bin/readlink 5 | .vagrant 6 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | dist: trusty 3 | language: bash 4 | env: 5 | - DOKKU_VERSION=master DOKKU_SYSTEM_GROUP=travis DOKKU_SYSTEM_USER=travis 6 | - DOKKU_VERSION=v0.7.0 DOKKU_SYSTEM_GROUP=travis DOKKU_SYSTEM_USER=travis 7 | - DOKKU_VERSION=v0.6.0 DOKKU_SYSTEM_GROUP=travis DOKKU_SYSTEM_USER=travis 8 | - DOKKU_VERSION=v0.5.0 DOKKU_SYSTEM_GROUP=travis DOKKU_SYSTEM_USER=travis 9 | - DOKKU_VERSION=v0.4.0 DOKKU_SYSTEM_GROUP=travis DOKKU_SYSTEM_USER=travis 10 | script: make test 11 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description of problem 4 | 5 | ### How reproducible 6 | 7 | ### Steps to Reproduce 8 | 9 | 1. 10 | 2. 11 | 3. 12 | 13 | #### Actual Results 14 | 15 | #### Expected Results 16 | 17 | ## Environment Information 18 | 19 | ### `dokku report` output 20 | 21 | > This is required! Issues missing this information may be closed. 22 | 23 | ### `dokku acl:report` output 24 | 25 | > This is required! Issues missing this information may be closed. 26 | 27 | ### `ls -lah ~dokku/.dokkurc/` output 28 | 29 | > This is required! Issues missing this information may be closed. 30 | 31 | ### How (deb/make/rpm) and where (AWS, VirtualBox, physical, etc.) was Dokku installed?: 32 | 33 | ### Additional information 34 | 35 | - Output of failing Dokku commands after running `dokku trace on` 36 | (BEWARE: `trace on` will print environment variables for some commands, be sure you're not exposing any sensitive information when posting issues. You may replace these values with XXXXXXX): 37 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Maciej Łebkowski 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | shellcheck: 2 | ifeq ($(shell shellcheck > /dev/null 2>&1 ; echo $$?),127) 3 | ifeq ($(shell uname),Darwin) 4 | brew install shellcheck 5 | else 6 | sudo add-apt-repository 'deb http://archive.ubuntu.com/ubuntu trusty-backports main restricted universe multiverse' 7 | sudo apt-get update -qq && sudo apt-get install -qq -y shellcheck 8 | endif 9 | endif 10 | 11 | bats: 12 | ifeq ($(shell bats > /dev/null 2>&1 ; echo $$?),127) 13 | ifeq ($(shell uname),Darwin) 14 | git clone https://github.com/sstephenson/bats.git /tmp/bats 15 | cd /tmp/bats && sudo ./install.sh /usr/local 16 | rm -rf /tmp/bats 17 | else 18 | sudo add-apt-repository ppa:duggan/bats --yes 19 | sudo apt-get update -qq && sudo apt-get install -qq -y bats 20 | endif 21 | endif 22 | 23 | readlink: 24 | ifeq ($(shell uname),Darwin) 25 | ifeq ($(shell greadlink > /dev/null 2>&1 ; echo $$?),127) 26 | brew install coreutils 27 | endif 28 | ln -nfs `which greadlink` tests/bin/readlink 29 | endif 30 | 31 | ci-dependencies: shellcheck bats readlink 32 | 33 | lint: 34 | # these are disabled due to their expansive existence in the codebase. we should clean it up though 35 | # SC1090: Can't follow non-constant source. Use a directive to specify location. 36 | # SC2034: VAR appears unused - https://github.com/koalaman/shellcheck/wiki/SC2034 37 | @echo linting... 38 | @$(QUIET) find ./ -maxdepth 1 -not -path '*/\.*' | xargs file | egrep "shell|bash" | awk '{ print $$1 }' | sed 's/://g' | xargs shellcheck -e SC1090,SC2034 39 | 40 | unit-tests: 41 | @echo running unit tests... 42 | @$(QUIET) bats tests 43 | 44 | setup: 45 | bash tests/setup.sh 46 | $(MAKE) ci-dependencies 47 | 48 | test: setup lint unit-tests 49 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dokku-acl [![Build Status](https://img.shields.io/travis/dokku-community/dokku-acl.svg?branch=master "Build Status")](https://travis-ci.org/dokku-community/dokku-acl) 2 | 3 | *Access Control List management for Dokku.* 4 | 5 | This plugin adds the ability to restrict dokku commands and push privileges 6 | for apps to certain users, with the goal of allowing secure multi-tenant dokku 7 | hosting. (See below for notes and limitations.) 8 | 9 | ## requirements 10 | 11 | - dokku 0.32.0+ 12 | - docker 1.8.x 13 | 14 | - An older version of this plugin works with dokku 0.3.x; the last version 15 | known to work is tagged as `for-dokku-0.3.x`. 16 | 17 | ## installation 18 | 19 | ```shell 20 | dokku plugin:install https://github.com/dokku-community/dokku-acl.git acl 21 | ``` 22 | 23 | ## commands 24 | 25 | ```shell 26 | acl:add Allow to access 27 | acl:allowed List apps the user has access to 28 | acl:list Show list of users with access to 29 | acl:remove Revoke 's access to 30 | 31 | acl:add-service Allow to access of type 32 | acl:allowed-service List services of type that the user has access to 33 | acl:list-service Show list of users with access to of type 34 | acl:remove-service Revoke 's access to of type 35 | ``` 36 | 37 | ## usage 38 | 39 | There are no restrictions to pushing at first. After you create an 40 | app, use `dokku acl:add your-app your-user` to restrict access to that 41 | user. After an allowed user list is created for app, no other users 42 | will be able to push. 43 | 44 | To remove the restrictions, remove all users from the ACL. 45 | 46 | You cannot modify the ACL list by ssh (`ssh target-host dokku acl:add …`); you have to do it using a local command. 47 | 48 | ### defining users 49 | 50 | Every user has their entry in `~dokku/.ssh/authorized_keys`. Use 51 | `$NAME` environment variable to define the username. If you add the user 52 | using `dokku ssh-keys:add`, this will be done automatically for you. 53 | 54 | ### configuring command line usage 55 | 56 | By default, certain dokku commands (e.g. `app:destroy`) won't work when run 57 | from the command line on the server, if `DOKKU_SUPER_USER` is set, even when 58 | run as `root` or `dokku`. To avoid confusion, we recommend allowing command 59 | line access by defining `DOKKU_ACL_ALLOW_COMMAND_LINE` in 60 | `~dokku/.dokkurc/acl`: 61 | 62 | ```shell 63 | export DOKKU_ACL_ALLOW_COMMAND_LINE=1 64 | ``` 65 | 66 | (The default behaviour exists to prevent security issues for users who were 67 | depending on the legacy behaviour. We recommend that all users set the 68 | variable above.) 69 | 70 | ### default behavior 71 | 72 | By default every user can push to repositories and even create new ones. You can change that by creating an admin 73 | user by defining `$DOKKU_SUPER_USER` env in `~dokku/.dokkurc/acl`: 74 | 75 | ```shell 76 | export DOKKU_SUPER_USER=puck 77 | ``` 78 | 79 | If defined, this user is always allowed to push, and no other users are allowed to push to apps with empty ACLs. 80 | 81 | ### command restrictions 82 | 83 | By default, all users can run all dokku commands. To restrict the commands 84 | available to non-admin users, whitelist the desired commands in 85 | `~dokku/.dokkurc/acl`. The following lists of commands can be defined: 86 | * Commands in `$DOKKU_ACL_USER_COMMANDS` can be run by any user at any time 87 | * Commands in `$DOKKU_ACL_PER_APP_COMMANDS` can be run on an app by any user 88 | with permission to manage that app. 89 | * Commands in `$DOKKU_ACL_PER_SERVICE_COMMANDS` can be run on any service by 90 | any user with permission to manage that service. 91 | * Commands in `$DOKKU_ACL_LINK_COMMANDS` can be run by any user with permission 92 | to manage both the service and the app being linked. 93 | 94 | See the section on secure multi-tenancy for examples. 95 | 96 | ### read restrictions 97 | 98 | By default, users can read (`git pull`, `git clone`, `git archive`) 99 | from repositories, even when they aren't in the ACL. To prevent this, 100 | add a per-app command restriction for `git-upload-pack` and 101 | `git-upload-archive`. 102 | 103 | ### secure multi-tenancy 104 | 105 | Dokku already provides good isolation functionality between apps: apps are 106 | run in independent Docker containers, and all builds occur in Docker 107 | containers too. This plugin aims to address the "missing link" needed for 108 | secure multi-tenancy with Dokku: restricting access to apps and management 109 | commands. 110 | 111 | **Note that this plugin has not been extensively audited for security**, and 112 | to our knowledge, neither has Dokku. Serious deficiencies may exist, and users 113 | of this plugin are strongly advised 114 | to perform their own security audit. If you encounter any issues or limitations 115 | with this plugin, please log them as GitHub issues and we'll try and address 116 | them. As usual with open source software there is **no warranty**. (Please 117 | see LICENSE.txt for details.) 118 | 119 | With that in mind, here are some recommendations on creating a secure 120 | multi-tenancy setup with Dokku and this plugin: 121 | 122 | 1. Keep up to date with Dokku releases and with security updates for all 123 | software on your servers, including Docker. 124 | 125 | 2. Restrict shell access to the server. Users should only be able to interact 126 | with the machine via apps, and via restricted ssh. (If you manage users using 127 | `dokku ssh-keys`, this will be done for you.) 128 | 129 | 3. Set a `DOKKU_SUPER_USER`. This prevents pushing to apps with no ACL. To do 130 | this, add a line like the following to `~dokku/.dokkurc/acl`: 131 | 132 | ```shell 133 | export DOKKU_SUPER_USER=super_user_name 134 | ``` 135 | 136 | 4. Restrict user commands to the minimum set needed by your users, and be sure 137 | the commands you allow meet your security requirements. The authors of this 138 | plugin currently recommend allowing `help` and `version`. To do this, add 139 | the following line to `~dokku/.dokkurc/acl`: 140 | 141 | ```shell 142 | export DOKKU_ACL_USER_COMMANDS="help version" 143 | ``` 144 | 145 | 5. Similarly, restrict per-app commands. The authors of this plugin 146 | currently recommend allowing `logs`, `urls`, `ps:rebuild`, 147 | `ps:restart`, `ps:stop`, `ps:start`, `git-upload-pack`, `git-upload-archive`, 148 | `git-receive-pack`, `git-hook`. 149 | To do this, add the following line to `~dokku/.dokkurc/acl`: 150 | 151 | ```shell 152 | export DOKKU_ACL_PER_APP_COMMANDS="logs urls ps:rebuild ps:restart ps:stop ps:start git-upload-pack git-upload-archive git-receive-pack git-hook" 153 | ``` 154 | 155 | This will also prevent users from reading from app repos when they aren't in 156 | the ACL, which is desireable for security. While apps _should_ be configured 157 | using the environment, app developers often include secrets in their repos, 158 | especially with closed source projects. 159 | -------------------------------------------------------------------------------- /commands: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" 3 | [[ " help $PLUGIN_COMMAND_PREFIX:help $PLUGIN_COMMAND_PREFIX $PLUGIN_COMMAND_PREFIX:default " == *" $1 "* ]] || [[ "$1" == "$PLUGIN_COMMAND_PREFIX:"* ]] || exit "$DOKKU_NOT_IMPLEMENTED_EXIT" 4 | source "$PLUGIN_BASE_PATH/common/functions" 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/help-functions" 9 | 10 | fn-help "$@" 11 | -------------------------------------------------------------------------------- /common-functions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_AVAILABLE_PATH/config/functions" 5 | 6 | is_implemented_command() { 7 | declare desc="return true if value ($1) is in list (all other arguments)" 8 | declare CMD="$1" 9 | CMD="$(echo "$CMD" | cut -d ':' -f2)" 10 | 11 | if [[ ${#PLUGIN_UNIMPLEMENTED_SUBCOMMANDS[@]} -eq 0 ]]; then 12 | return 0 13 | fi 14 | 15 | local e 16 | for e in "${PLUGIN_UNIMPLEMENTED_SUBCOMMANDS[@]}"; do 17 | [[ "$e" == "$CMD" ]] && return 1 18 | done 19 | return 0 20 | } 21 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export PLUGIN_UNIMPLEMENTED_SUBCOMMANDS=() 3 | export PLUGIN_COMMAND_PREFIX="acl" 4 | export PLUGIN_BASE_PATH="$PLUGIN_PATH" 5 | if [[ -n $DOKKU_API_VERSION ]]; then 6 | export PLUGIN_BASE_PATH="$PLUGIN_ENABLED_PATH" 7 | fi 8 | -------------------------------------------------------------------------------- /help-functions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/config" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/common-functions" 6 | # shellcheck disable=SC2155 7 | export SUBCOMMAND_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/subcommands" 8 | 9 | fn-help() { 10 | declare CMD="$1" 11 | local cmd EXIT_CODE 12 | 13 | if [[ "$CMD" == "help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:default" ]]; then 14 | fn-help-all "$@" 15 | exit 0 16 | fi 17 | 18 | pushd "$SUBCOMMAND_ROOT" >/dev/null 2>&1 19 | for cmd in *; do 20 | if [[ "$CMD" == "${PLUGIN_COMMAND_PREFIX}:$cmd" ]]; then 21 | "$SUBCOMMAND_ROOT/$cmd" "$@" 22 | EXIT_CODE="$?" 23 | exit "$EXIT_CODE" 24 | fi 25 | done 26 | popd >/dev/null 2>&1 27 | 28 | exit "$DOKKU_NOT_IMPLEMENTED_EXIT" 29 | } 30 | 31 | fn-help-all() { 32 | declare CMD="$1" SUBCOMMAND="$2" 33 | local CMD_OUTPUT BLUE BOLD FULL_OUTPUT NORMAL 34 | FULL_OUTPUT=true 35 | 36 | if [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:help" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX" ]] || [[ "$CMD" == "$PLUGIN_COMMAND_PREFIX:default" ]]; then 37 | BOLD="$(fn-help-fancy-tput bold)" 38 | NORMAL="$(fn-help-fancy-color "\033[m")" 39 | BLUE="$(fn-help-fancy-color "\033[0;34m")" 40 | CYAN="$(fn-help-fancy-color "\033[1;36m")" 41 | if [[ -n "$SUBCOMMAND" ]] && [[ "$SUBCOMMAND" != "--all" ]]; then 42 | fn-help-contents-subcommand "$SUBCOMMAND" "$FULL_OUTPUT" 43 | return "$?" 44 | fi 45 | 46 | echo -e "${BOLD}usage${NORMAL}: dokku ${PLUGIN_COMMAND_PREFIX}[:COMMAND]" 47 | echo '' 48 | echo -e "${BOLD}List your acls for an app.${NORMAL}" 49 | echo '' 50 | echo -e "${BLUE}Example:${NORMAL}" 51 | echo '' 52 | echo " \$ dokku $PLUGIN_COMMAND_PREFIX:list lolipop" 53 | echo '' 54 | fn-help-list-example | column -c5 -t -s, 55 | echo '' 56 | echo -e "dokku ${BOLD}${PLUGIN_COMMAND_PREFIX}${NORMAL} commands: (get help with ${CYAN}dokku ${PLUGIN_COMMAND_PREFIX}:help SUBCOMMAND${NORMAL})" 57 | echo '' 58 | fn-help-contents | sort | column -c2 -t -s, 59 | echo '' 60 | elif [[ $(ps -o command= $PPID) == *"--all"* ]]; then 61 | fn-help-contents 62 | else 63 | cat </dev/null 2>&1 73 | for cmd in *; do 74 | fn-help-contents-subcommand "$cmd" || true 75 | done 76 | } 77 | 78 | fn-help-contents-subcommand() { 79 | declare SUBCOMMAND="$1" FULL_OUTPUT="$2" 80 | # shellcheck disable=SC2155 81 | local TMPDIR=$(mktemp -d) 82 | local UNCLEAN_FILE="${TMPDIR}/cmd-unclean" CLEAN_FILE="${TMPDIR}/cmd-clean" 83 | local BOLD CMD_OUTPUT CYAN EXAMPLE LIGHT_GRAY NORMAL 84 | trap 'rm -rf "$TMPDIR" > /dev/null' RETURN INT TERM EXIT 85 | 86 | rm -rf "$UNCLEAN_FILE" "$CLEAN_FILE" 87 | cat "$SUBCOMMAND_ROOT/$SUBCOMMAND" >"$UNCLEAN_FILE" 88 | 89 | fn-help-subcommand-sanitize "$UNCLEAN_FILE" "$CLEAN_FILE" 90 | if ! is_implemented_command "$SUBCOMMAND"; then 91 | return 1 92 | fi 93 | 94 | args="$(fn-help-subcommand-args "$CLEAN_FILE" "$FULL_OUTPUT")" 95 | SUBCOMMAND=":$SUBCOMMAND" 96 | [[ "$SUBCOMMAND" == ":default" ]] && SUBCOMMAND="" 97 | cmd_line="$(echo -e "${SUBCOMMAND} ${args}" | sed -e 's/[[:space:]]*$//')" 98 | desc="$(grep desc "$CLEAN_FILE" | head -1)" 99 | eval "$desc" 100 | 101 | BLUE="$(fn-help-fancy-color "\033[0;34m")" 102 | BOLD="$(fn-help-fancy-tput bold)" 103 | CYAN="$(fn-help-fancy-color "\033[1;36m")" 104 | NORMAL="$(fn-help-fancy-color "\033[m")" 105 | LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" 106 | LIGHT_RED="$(fn-help-fancy-color "\033[1;31m")" 107 | CMD_OUTPUT="$(echo -e " ${PLUGIN_COMMAND_PREFIX}${cmd_line}, ${LIGHT_GRAY}${desc}${NORMAL}")" 108 | if [[ "$FULL_OUTPUT" != "true" ]]; then 109 | echo "$CMD_OUTPUT" 110 | return 0 111 | fi 112 | 113 | echo -e "${BOLD}usage:${NORMAL} dokku ${PLUGIN_COMMAND_PREFIX}${cmd_line}" 114 | echo '' 115 | echo -e "${BOLD}${desc}${NORMAL}" 116 | echo '' 117 | 118 | ARGS="$(fn-help-subcommand-list-args "$CLEAN_FILE")" 119 | if [[ -n "$ARGS" ]]; then 120 | echo -e "${CYAN}arguments:${NORMAL}" 121 | echo '' 122 | echo "$ARGS" | column -c2 -t -s, 123 | echo '' 124 | fi 125 | 126 | FLAGS="$(fn-help-subcommand-list-flags "$CLEAN_FILE")" 127 | if [[ -n "$FLAGS" ]]; then 128 | echo -e "${BLUE}flags:${NORMAL}" 129 | echo '' 130 | echo "$FLAGS" | column -c2 -t -s, 131 | echo '' 132 | fi 133 | 134 | EXAMPLE="$(fn-help-subcommand-example "$CLEAN_FILE")" 135 | if [[ -n "$EXAMPLE" ]]; then 136 | echo -e "${LIGHT_RED}examples:${NORMAL}" 137 | echo '' 138 | echo "$EXAMPLE" 139 | echo '' 140 | fi 141 | 142 | return 0 143 | } 144 | 145 | fn-help-fancy-tput() { 146 | declare desc="A wrapper around tput" 147 | 148 | if [[ -n "$DOKKU_NO_COLOR" ]] || [[ "$TERM" == "unknown" ]] || [[ "$TERM" == "dumb" ]]; then 149 | return 150 | fi 151 | 152 | tput "$@" 153 | } 154 | 155 | fn-help-fancy-color() { 156 | declare desc="A wrapper around colors" 157 | 158 | if [[ -n "$DOKKU_NO_COLOR" ]] || [[ "$TERM" == "unknown" ]] || [[ "$TERM" == "dumb" ]]; then 159 | return 160 | fi 161 | 162 | echo "$@" 163 | } 164 | 165 | fn-help-list-example() { 166 | # shellcheck disable=SC2034 167 | declare desc="return $PLUGIN_COMMAND_PREFIX plugin help content" 168 | cat <* ]] && line="\n ${BOLD}${line}${NORMAL}" 242 | # shellcheck disable=SC2001 243 | [[ "$line" == " "* ]] && line=" ${OTHER_GRAY}$(echo "$line" | sed -e 's/^[[:space:]]*//')${NORMAL}" 244 | echo -e "${NEWLINE}${line}" 245 | LAST_LINE="sentence" 246 | NEWLINE="\n" 247 | fi 248 | done 249 | } 250 | 251 | fn-help-subcommand-list-args() { 252 | declare FUNC_FILE="$1" 253 | local EXAMPLE LIGHT_GRAY NORMAL 254 | 255 | FLAGS=$(grep "#A" "$FUNC_FILE" | cut -d'A' -f2- | sed -e 's/^[[:space:]]*//' || true) 256 | if [[ -z "$FLAGS" ]]; then 257 | return 0 258 | fi 259 | 260 | NORMAL="$(fn-help-fancy-color "\033[m")" 261 | LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" 262 | 263 | _fn-help-apply-shell-expansion "$FLAGS" | while read -r line; do 264 | echo -e "$(echo "$line" | cut -d',' -f1),${LIGHT_GRAY}$(echo "$line" | cut -d',' -f2-)${NORMAL}" 265 | done 266 | } 267 | 268 | fn-help-subcommand-list-flags() { 269 | declare FUNC_FILE="$1" 270 | local EXAMPLE LIGHT_GRAY NORMAL 271 | 272 | FLAGS=$(grep "#F" "$FUNC_FILE" | cut -d'F' -f2- | sed -e 's/^[[:space:]]*//' || true) 273 | if [[ -z "$FLAGS" ]]; then 274 | return 0 275 | fi 276 | 277 | NORMAL="$(fn-help-fancy-color "\033[m")" 278 | LIGHT_GRAY="$(fn-help-fancy-color "\033[2;37m")" 279 | 280 | _fn-help-apply-shell-expansion "$FLAGS" | while read -r line; do 281 | echo -e "$(echo "$line" | cut -d',' -f1),${LIGHT_GRAY}$(echo "$line" | cut -d',' -f2-)${NORMAL}" 282 | done 283 | } 284 | 285 | fn-help-subcommand-sanitize() { 286 | declare FUNC_FILE="$1" OUTGOING_FUNC_FILE="$2" 287 | local FUNCTION_FOUND=false 288 | local IFS OIFS 289 | 290 | touch "$OUTGOING_FUNC_FILE" 291 | 292 | OIFS="$IFS" 293 | IFS=, 294 | while read -r p; do 295 | IFS="$OIFS" 296 | if [[ "$p" == *"-cmd \"\$@\""* ]] || [[ "$p" == "" ]]; then 297 | continue 298 | fi 299 | 300 | if [[ "$FUNCTION_FOUND" == true ]]; then 301 | echo "$p" >>"$OUTGOING_FUNC_FILE" 302 | continue 303 | fi 304 | 305 | if [[ "$p" == *"()"* ]]; then 306 | FUNCTION_FOUND=true 307 | echo "$p" >>"$OUTGOING_FUNC_FILE" 308 | continue 309 | fi 310 | done <"$FUNC_FILE" 311 | } 312 | 313 | _fn-help-apply-shell-expansion() { 314 | declare desc="Expand environment variables for a shell command" 315 | declare data="$1" 316 | declare delimiter="__apply_shell_expansion_delimiter__" 317 | declare command="cat <<$delimiter"$'\n'"$data"$'\n'"$delimiter" 318 | eval "$command" 319 | } 320 | -------------------------------------------------------------------------------- /internal-functions: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | 6 | cmd-acl-report-all() { 7 | declare desc="displays a acl report for one or more apps" 8 | local cmd="acl:report" 9 | local APP="$1" INFO_FLAG="$2" 10 | local INSTALLED_APPS 11 | 12 | if [[ -n "$APP" ]] && [[ "$APP" == --* ]]; then 13 | INFO_FLAG="$APP" 14 | APP="" 15 | fi 16 | 17 | if [[ -z "$APP" ]] && [[ -z "$INFO_FLAG" ]]; then 18 | INFO_FLAG="true" 19 | fi 20 | 21 | if [[ -z "$APP" ]]; then 22 | INSTALLED_APPS=$(dokku_apps) 23 | for app in $INSTALLED_APPS; do 24 | cmd-acl-report-single "$app" "$INFO_FLAG" | tee || true 25 | done 26 | else 27 | cmd-acl-report-single "$APP" "$INFO_FLAG" 28 | fi 29 | } 30 | 31 | cmd-acl-report-single() { 32 | declare APP="$1" INFO_FLAG="$2" 33 | if [[ "$INFO_FLAG" == "true" ]]; then 34 | INFO_FLAG="" 35 | fi 36 | verify_app_name "$APP" 37 | local flag_map=( 38 | "--acl-allowed-users: $(ls -1 "$DOKKU_ROOT/$APP/acl" >&2 2>/dev/null || true)" 39 | "--acl-global-allow-command-line: $DOKKU_ACL_ALLOW_COMMAND_LINE" 40 | "--acl-global-super-user: $DOKKU_SUPER_USER" 41 | "--acl-global-user-commands: $DOKKU_ACL_USER_COMMANDS" 42 | "--acl-global-per-app-commands: $DOKKU_ACL_PER_APP_COMMANDS" 43 | ) 44 | 45 | if [[ -z "$INFO_FLAG" ]]; then 46 | dokku_log_info2_quiet "${APP} acl information" 47 | for flag in "${flag_map[@]}"; do 48 | key="$(echo "${flag#--}" | cut -f1 -d' ' | tr - ' ')" 49 | dokku_log_verbose "$(printf "%-30s %-25s" "${key^}" "${flag#*: }")" 50 | done 51 | else 52 | local match=false 53 | local value_exists=false 54 | for flag in "${flag_map[@]}"; do 55 | valid_flags="${valid_flags} $(echo "$flag" | cut -d':' -f1)" 56 | if [[ "$flag" == "${INFO_FLAG}:"* ]]; then 57 | value=${flag#*: } 58 | size="${#value}" 59 | if [[ "$size" -ne 0 ]]; then 60 | echo "$value" && match=true && value_exists=true 61 | else 62 | match=true 63 | fi 64 | fi 65 | done 66 | [[ "$match" == "true" ]] || dokku_log_fail "Invalid flag passed, valid flags:${valid_flags}" 67 | [[ "$value_exists" == "true" ]] || dokku_log_fail "not deployed" 68 | fi 69 | } 70 | 71 | fn-acl-check-app() { 72 | declare APP="$1" 73 | 74 | verify_app_name "$APP" 75 | 76 | if [[ -n "${NAME:-}" ]]; then 77 | dokku_log_fail "You can only modify ACL using local dokku command on target host" 78 | fi 79 | } 80 | 81 | fn-acl-check-service() { 82 | declare SERVICE_TYPE="$1" SERVICE="$2" 83 | 84 | local SERVICE_PATH="$DOKKU_LIB_ROOT/services/$SERVICE_TYPE/$SERVICE" 85 | if ! [[ -d $SERVICE_PATH ]]; then 86 | dokku_log_fail "Service $SERVICE of type $SERVICE_TYPE does not exist" 87 | fi 88 | 89 | if [[ -n "${NAME:-}" ]]; then 90 | dokku_log_fail "You can only modify ACL using local dokku command on target host" 91 | fi 92 | } 93 | 94 | fn-check-app-acl() { 95 | declare desc="Checks if the current user has an ACL entry for the app" 96 | declare APP="$1" SSH_NAME="$2" 97 | local ACL_FILE="$DOKKU_ROOT/$APP/acl/$SSH_NAME" 98 | 99 | if ! (verify_app_name "$APP" 2>/dev/null); then 100 | dokku_log_fail "User $SSH_NAME does not have permissions to run $CMD on $APP, or $APP does not exist" 101 | fi 102 | 103 | [[ -f "$ACL_FILE" ]] && return 0 104 | 105 | dokku_log_fail "User $SSH_NAME does not have permissions to run $CMD on $APP, or $APP does not exist" 106 | } 107 | 108 | fn-check-service-acl() { 109 | declare desc="Checks if the current user has an ACL entry for the service" 110 | declare CMD="$1" SERVICE="$2" SSH_NAME="$3" 111 | 112 | local SERVICE_TYPE="${CMD%%:*}" 113 | local SERVICE_PATH="$DOKKU_LIB_ROOT/services/$SERVICE_TYPE/$SERVICE" 114 | local ACL_FILE="$SERVICE_PATH/acl/$SSH_NAME" 115 | 116 | if ! [[ -d $SERVICE_PATH ]]; then 117 | dokku_log_fail "User $SSH_NAME does not have permissions to run $CMD on $SERVICE, or $SERVICE does not exist" 118 | fi 119 | 120 | [[ -f "$ACL_FILE" ]] && return 0 121 | 122 | dokku_log_fail "User $SSH_NAME does not have permissions to run $CMD on $SERVICE, or $SERVICE does not exist" 123 | } 124 | 125 | fn-check-modify-app-acl() { 126 | declare desc="Checks if the current dokku user is allowed to modify the given app" 127 | declare APP="$1" 128 | 129 | local ACL="$DOKKU_ROOT/$APP/acl" 130 | local DOKKU_SUPER_USER="${DOKKU_SUPER_USER:-}" 131 | local DOKKU_ACL_ALLOW_COMMAND_LINE="${DOKKU_ACL_ALLOW_COMMAND_LINE:-}" 132 | 133 | if [[ -z "$NAME" ]]; then 134 | # Command line usage doesn't set $NAME. 135 | 136 | if [[ -z "$DOKKU_ACL_ALLOW_COMMAND_LINE" ]]; then 137 | # Preserve legacy behaviour by default for safety, even though it's weird. 138 | 139 | [[ -z "$DOKKU_SUPER_USER" ]] && exit 0 140 | 141 | dokku_log_fail "It appears that you're running this command from the command" \ 142 | "line. The \"dokku-acl\" plugin disables this by default for" \ 143 | "safety. Please check the \"dokku-acl\" documentation for how" \ 144 | "to enable command line usage." 145 | fi 146 | 147 | exit 0 148 | fi 149 | 150 | if [[ ! -d "$ACL" ]]; then 151 | if [[ -n "$DOKKU_SUPER_USER" ]] && [[ "$NAME" != "$DOKKU_SUPER_USER" ]]; then 152 | dokku_log_fail "Only $DOKKU_SUPER_USER can modify a repository if the ACL is empty" 153 | fi 154 | 155 | exit 0 # all good, there are no restrictions 156 | fi 157 | 158 | local ACL_FILE="$ACL/$NAME" 159 | 160 | if [[ ! -f "$ACL_FILE" ]] && [[ "$NAME" != "$DOKKU_SUPER_USER" ]]; then 161 | echo "User $NAME does not have permissions to modify this repository" >&2 162 | exit 2 163 | fi 164 | } 165 | 166 | fn-acl-is-super-user() { 167 | declare desc="check if the specified user is a super user" 168 | declare USERNAME="$1" 169 | 170 | if [[ "$USERNAME" == "$DOKKU_SUPER_USER" ]]; then 171 | return 172 | fi 173 | 174 | return 1 175 | } 176 | -------------------------------------------------------------------------------- /plugin.toml: -------------------------------------------------------------------------------- 1 | [plugin] 2 | description = "dokku plugin that can be used to restrict push privileges for app to certain users" 3 | version = "1.5.1" 4 | [plugin.config] 5 | -------------------------------------------------------------------------------- /pre-build: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | source "$(dirname "${BASH_SOURCE[0]}")/internal-functions" 6 | 7 | APP="$2" 8 | fn-check-modify-app-acl "$APP" 9 | -------------------------------------------------------------------------------- /pre-delete: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 5 | source "$(dirname "${BASH_SOURCE[0]}")/internal-functions" 6 | 7 | APP="$1" 8 | fn-check-modify-app-acl "$APP" 9 | -------------------------------------------------------------------------------- /pre-receive-app: -------------------------------------------------------------------------------- 1 | pre-delete -------------------------------------------------------------------------------- /subcommands/add: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | source "$PLUGIN_BASE_PATH/common/functions" 6 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/internal-functions" 7 | 8 | cmd-acl-add() { 9 | #E allow the admin user access to the lolipop app 10 | #E dokku $PLUGIN_COMMAND_PREFIX:add lolipop admin 11 | #A app, app to run command against 12 | #A user, a user to allow access 13 | declare desc="allow to push to 's repository" 14 | local cmd="$PLUGIN_COMMAND_PREFIX:add" argv=("$@") 15 | [[ ${argv[0]} == "$cmd" ]] && shift 1 16 | declare APP="$1" USER="$2" 17 | local ACL_PATH="$DOKKU_ROOT/$APP/acl" 18 | local ACL_FILE="$ACL_PATH/$USER" 19 | 20 | fn-acl-check-app "$APP" 21 | 22 | if [[ -z "$USER" ]]; then 23 | dokku_log_fail "Please specify a user name" 24 | fi 25 | 26 | [[ ! -d "$ACL_PATH" ]] && mkdir -p "$ACL_PATH" 27 | 28 | if [[ -f "$ACL_FILE" ]]; then 29 | echo "User already has permissions to push to this repository" >&2 30 | exit 2 31 | fi 32 | touch "$ACL_FILE" 33 | } 34 | 35 | cmd-acl-add "$@" 36 | -------------------------------------------------------------------------------- /subcommands/add-service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | source "$PLUGIN_BASE_PATH/common/functions" 6 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/internal-functions" 7 | 8 | cmd-acl-add-service() { 9 | #E allow the servuser user access to the birds Redis service 10 | #E dokku $PLUGIN_COMMAND_PREFIX:add-service redis birds servuser 11 | #A type, type of service 12 | #A service, service to run command against 13 | #A user, a user to allow access 14 | declare desc="allow to control of type " 15 | local cmd="$PLUGIN_COMMAND_PREFIX:add-service" argv=("$@") 16 | [[ ${argv[0]} == "$cmd" ]] && shift 1 17 | declare SERVICE_TYPE="$1" SERVICE="$2" USER="$3" 18 | 19 | fn-acl-check-service "$SERVICE_TYPE" "$SERVICE" 20 | 21 | local ACL_PATH="$DOKKU_LIB_ROOT/services/$SERVICE_TYPE/$SERVICE/acl" 22 | local ACL_FILE="$ACL_PATH/$USER" 23 | 24 | if [[ -z "$USER" ]]; then 25 | dokku_log_fail "Please specify a user name" 26 | fi 27 | 28 | [[ ! -d "$ACL_PATH" ]] && mkdir -p "$ACL_PATH" 29 | 30 | if [[ -f "$ACL_FILE" ]]; then 31 | echo "User already has permissions to push to this repository" >&2 32 | exit 2 33 | fi 34 | 35 | touch "$ACL_FILE" 36 | } 37 | 38 | cmd-acl-add-service "$@" 39 | -------------------------------------------------------------------------------- /subcommands/allowed: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | source "$PLUGIN_BASE_PATH/common/functions" 6 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/internal-functions" 7 | 8 | cmd-acl-allowed() { 9 | #E show apps of a given type that the user has access to 10 | #E dokku $PLUGIN_COMMAND_PREFIX:list redis birds 11 | #A username, user to check for access 12 | declare desc="list apps the user has access to" 13 | local cmd="$PLUGIN_COMMAND_PREFIX:allowed" argv=("$@") 14 | [[ ${argv[0]} == "$cmd" ]] && shift 1 15 | declare USERNAME="$1" 16 | 17 | if [[ -n "${NAME:-}" ]]; then 18 | dokku_log_fail "This command can only be run using the local dokku command on the target host" 19 | fi 20 | 21 | if [[ -z "$USERNAME" ]]; then 22 | dokku_log_fail "No username specified" 23 | fi 24 | 25 | if fn-acl-is-super-user "$USERNAME"; then 26 | dokku_apps 27 | return 28 | fi 29 | 30 | pushd "$DOKKU_ROOT" >/dev/null 31 | ls -d */acl/$USERNAME 2>/dev/null | cut -d '/' -f1 || true 32 | popd >/dev/null 33 | } 34 | 35 | cmd-acl-allowed "$@" 36 | -------------------------------------------------------------------------------- /subcommands/allowed-services: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | source "$PLUGIN_BASE_PATH/common/functions" 6 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/internal-functions" 7 | 8 | cmd-acl-allowed-services() { 9 | #E show services of a given type that the user has access to 10 | #E dokku $PLUGIN_COMMAND_PREFIX:list redis birds 11 | #A type, type of service 12 | #A username, user to check for access 13 | declare desc="list services of type that the user has access to" 14 | local cmd="$PLUGIN_COMMAND_PREFIX:allowed-services" argv=("$@") 15 | [[ ${argv[0]} == "$cmd" ]] && shift 1 16 | declare SERVICE_TYPE="$1" USERNAME="$2" 17 | 18 | if [[ -n "${NAME:-}" ]]; then 19 | dokku_log_fail "This command can only be run using the local dokku command on the target host" 20 | fi 21 | 22 | if [[ -z "$SERVICE_TYPE" ]]; then 23 | dokku_log_fail "No service type specified" 24 | fi 25 | 26 | if [[ -z "$USERNAME" ]]; then 27 | dokku_log_fail "No username specified" 28 | fi 29 | 30 | local PLUGIN_DATA_ROOT="$DOKKU_LIB_ROOT/services/$SERVICE_TYPE" 31 | if [[ ! -d "$PLUGIN_DATA_ROOT" ]]; then 32 | dokku_log_fail "$SERVICE_TYPE service plugin not detected" 33 | fi 34 | 35 | if fn-acl-is-super-user "$USERNAME"; then 36 | find "$PLUGIN_DATA_ROOT" -follow -maxdepth 1 -mindepth 1 -type d ! -name '.*' -printf "%f\n" 2>/dev/null | sort || true 37 | return 38 | fi 39 | 40 | pushd "$PLUGIN_DATA_ROOT" >/dev/null 41 | ls -d */acl/$USERNAME 2>/dev/null | cut -d '/' -f1 || true 42 | popd >/dev/null 43 | } 44 | 45 | cmd-acl-allowed-services "$@" 46 | -------------------------------------------------------------------------------- /subcommands/list: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | source "$PLUGIN_BASE_PATH/common/functions" 6 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/internal-functions" 7 | 8 | cmd-acl-list() { 9 | #E show users that have access to the lolipop app 10 | #E dokku $PLUGIN_COMMAND_PREFIX:list lolipop 11 | #A app, app to run command against 12 | declare desc="show list of users with access to " 13 | local cmd="$PLUGIN_COMMAND_PREFIX:list" argv=("$@") 14 | [[ ${argv[0]} == "$cmd" ]] && shift 1 15 | declare APP="$1" 16 | 17 | if [[ -z "$APP" ]]; then 18 | local INSTALLED_APPS=$(dokku_apps) 19 | for APP in $INSTALLED_APPS; do 20 | fn-acl-check-app "$APP" 21 | dokku_log_info2_quiet "${APP} acl list" 22 | ls -1 "$DOKKU_ROOT/$APP/acl" 2>/dev/null || true 23 | done 24 | else 25 | fn-acl-check-app "$APP" 26 | ls -1 "$DOKKU_ROOT/$APP/acl" 2>/dev/null || true 27 | fi 28 | } 29 | 30 | cmd-acl-list "$@" 31 | -------------------------------------------------------------------------------- /subcommands/list-service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | source "$PLUGIN_BASE_PATH/common/functions" 6 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/internal-functions" 7 | 8 | cmd-acl-list-service() { 9 | #E show users that have access to the birds redis service 10 | #E dokku $PLUGIN_COMMAND_PREFIX:list-service redis birds 11 | #A type, type of service 12 | #A service, service to run command against 13 | declare desc="show list of users with access to of type " 14 | local cmd="$PLUGIN_COMMAND_PREFIX:list-service" argv=("$@") 15 | [[ ${argv[0]} == "$cmd" ]] && shift 1 16 | declare SERVICE_TYPE="$1" SERVICE="$2" 17 | 18 | fn-acl-check-service "$SERVICE_TYPE" "$SERVICE" 19 | 20 | local ACL_PATH="$DOKKU_LIB_ROOT/services/$SERVICE_TYPE/$SERVICE/acl" 21 | ls -1 "$ACL_PATH" >&2 2>/dev/null || true 22 | } 23 | 24 | cmd-acl-list-service "$@" 25 | -------------------------------------------------------------------------------- /subcommands/remove: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | source "$PLUGIN_BASE_PATH/common/functions" 6 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/internal-functions" 7 | 8 | cmd-acl-remove() { 9 | #E remove access to the lolipop app from admin user access 10 | #E dokku $PLUGIN_COMMAND_PREFIX:remove lolipop admin 11 | #A app, app to run command against 12 | #A user, a user to allow access 13 | declare desc="revoke 's access to " 14 | local cmd="$PLUGIN_COMMAND_PREFIX:remove" argv=("$@") 15 | [[ ${argv[0]} == "$cmd" ]] && shift 1 16 | declare APP="$1" USER="$2" 17 | local ACL_PATH="$DOKKU_ROOT/$APP/acl" 18 | local ACL_FILE="$ACL_PATH/$USER" 19 | 20 | fn-acl-check-app "$APP" 21 | 22 | if [[ -z "$USER" ]]; then 23 | dokku_log_fail "Please specify a user name" 24 | fi 25 | 26 | [[ -f "$ACL_FILE" ]] && rm "$ACL_FILE"; 27 | if [[ -d "$ACL_PATH" ]] && [[ -z "$(ls "$ACL_PATH")" ]]; then 28 | rmdir "$ACL_PATH" 29 | else 30 | exit 0 31 | fi 32 | } 33 | 34 | cmd-acl-remove "$@" 35 | -------------------------------------------------------------------------------- /subcommands/remove-service: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | source "$PLUGIN_BASE_PATH/common/functions" 6 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/internal-functions" 7 | 8 | cmd-acl-remove-service() { 9 | #E revoke the servuser user's access to the birds Redis service 10 | #E dokku $PLUGIN_COMMAND_PREFIX:remove-service redis birds servuser 11 | #A type, type of service 12 | #A service, service to run command against 13 | #A user, a user to revoke access 14 | declare desc="revoke 's access to of type " 15 | local cmd="$PLUGIN_COMMAND_PREFIX:remove-service" argv=("$@") 16 | [[ ${argv[0]} == "$cmd" ]] && shift 1 17 | declare SERVICE_TYPE="$1" SERVICE="$2" USER="$3" 18 | 19 | fn-acl-check-service "$SERVICE_TYPE" "$SERVICE" 20 | 21 | local ACL_PATH="$DOKKU_LIB_ROOT/services/$SERVICE_TYPE/$SERVICE/acl" 22 | local ACL_FILE="$ACL_PATH/$USER" 23 | 24 | if [[ -z "$USER" ]]; then 25 | dokku_log_fail "Please specify a user name" 26 | fi 27 | 28 | [[ -f "$ACL_FILE" ]] && rm "$ACL_FILE"; 29 | if [[ -d "$ACL_PATH" ]] && [[ -z "$(ls "$ACL_PATH")" ]]; then 30 | rmdir "$ACL_PATH" 31 | else 32 | exit 0 33 | fi 34 | } 35 | 36 | cmd-acl-remove-service "$@" 37 | -------------------------------------------------------------------------------- /subcommands/report: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 3 | set -eo pipefail 4 | [[ $DOKKU_TRACE ]] && set -x 5 | source "$PLUGIN_BASE_PATH/common/functions" 6 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/internal-functions" 7 | 8 | cmd-acl-report() { 9 | #E displays a acl report for all apps 10 | #E dokku $PLUGIN_COMMAND_PREFIX:report 11 | #E displays a acl report for one app 12 | #E dokku $PLUGIN_COMMAND_PREFIX:report lolipop 13 | #A app, app to run command against 14 | declare desc="displays a acl report for one or more apps" 15 | local cmd="$PLUGIN_COMMAND_PREFIX:report" argv=("$@") 16 | [[ ${argv[0]} == "$cmd" ]] && shift 1 17 | declare APP="$1" 18 | 19 | cmd-acl-report-all "$@" 20 | } 21 | 22 | cmd-acl-report "$@" 23 | -------------------------------------------------------------------------------- /tests/bin/at-least-version: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -eo pipefail 3 | [[ $TRACE ]] && set -x 4 | 5 | semver-parse-into() { 6 | declare VERSION="$1" 7 | local RE='[^0-9]*\([0-9]*\)[.]\([0-9]*\)[.]\([0-9]*\)' 8 | local MAJOR="$(echo "$VERSION" | sed -e "s#$RE#\1#")" 9 | local MINOR="$(echo "$VERSION" | sed -e "s#$RE#\2#")" 10 | local PATCH="$(echo "$VERSION" | sed -e "s#$RE#\3#")" 11 | echo "${MAJOR} ${MINOR} ${PATCH}" 12 | } 13 | 14 | main() { 15 | declare MIN_VERSION="$1" CHECK_VERSION="$2" 16 | local IS_AT_LEAST_VERSION=false 17 | local MAJOR_MIN MINOR_MIN PATCH_MIN MAJOR_CHECK MINOR_CHECK PATCH_CHECK PARSED_MIN PARSED_CHECK 18 | 19 | PARSED_MIN="$(semver-parse-into "$MIN_VERSION")" 20 | PARSED_CHECK="$(semver-parse-into "$CHECK_VERSION")" 21 | MAJOR_MIN="$(echo "$PARSED_MIN" | cut -d' ' -f1)" 22 | MINOR_MIN="$(echo "$PARSED_MIN" | cut -d' ' -f2)" 23 | PATCH_MIN="$(echo "$PARSED_MIN" | cut -d' ' -f3)" 24 | MAJOR_CHECK="$(echo "$PARSED_CHECK" | cut -d' ' -f1)" 25 | MINOR_CHECK="$(echo "$PARSED_CHECK" | cut -d' ' -f2)" 26 | PATCH_CHECK="$(echo "$PARSED_CHECK" | cut -d' ' -f3)" 27 | 28 | if [[ "$MAJOR_CHECK" -gt "$MAJOR_MIN" ]]; then 29 | IS_AT_LEAST_VERSION=true 30 | elif [[ "$MAJOR_CHECK" -eq "$MAJOR_MIN" ]] && [[ "$MINOR_CHECK" -gt "$MINOR_MIN" ]]; then 31 | IS_AT_LEAST_VERSION=true 32 | elif [[ "$MAJOR_CHECK" -eq "$MAJOR_MIN" ]] && [[ "$MINOR_CHECK" -eq "$MINOR_MIN" ]] && [[ "$PATCH_CHECK" -ge "$PATCH_MIN" ]]; then 33 | IS_AT_LEAST_VERSION=true 34 | fi 35 | 36 | echo "$IS_AT_LEAST_VERSION" 37 | } 38 | 39 | main "$@" 40 | -------------------------------------------------------------------------------- /tests/bin/docker: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # shellcheck source=../../config 3 | # shellcheck disable=SC1091 4 | source "$(dirname "$0")/../../config" 5 | 6 | if [[ $ECHO_DOCKER_COMMAND == "true" ]]; then 7 | echo "$(basename "$0") $*" 8 | exit 0 9 | fi 10 | 11 | case "$1" in 12 | exec) 13 | echo "exec called with $@" 14 | ;; 15 | images) 16 | echo "REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE" 17 | echo "elasticsearch 2.3.5 89ed89918502 2 days ago 522.1 MB" 18 | echo "frodenas/couchdb 1.6 cb9a62e007eb 2 days ago 537 MB" 19 | echo "dokkupaas/docker-grafana-graphite 3.0.1 75dcd48a5eef 2 days ago 936.4 MB" 20 | echo "mariadb 10.1.16 f2485761e714 2 days ago 302.2 MB" 21 | echo "memcached 1.4.31 8a05b51f8876 2 days ago 132.4 MB" 22 | echo "mongo 3.2.9 12eadb136159 2 days ago 291.1 MB" 23 | echo "mysql 5.7.12 57d56ac47bed 2 days ago 321.3 MB" 24 | echo "nats 0.9.4 9216d5a4eec8 2 days ago 109.3 MB" 25 | echo "postgres 9.5.4 6412eb70175e 2 days ago 265.7 MB" 26 | echo "rabbitmq 3.6.5-management 327b803301e9 2 days ago 143.5 MB" 27 | echo "redis 3.2.3 9216d5a4eec8 2 days ago 109.3 MB" 28 | echo "rethinkdb 2.3.4 f27010a550ec 2 days ago 196.3 MB" 29 | echo "svendowideit/ambassador latest 0d2200edc53e 2 days ago 7.241 MB" 30 | ;; 31 | inspect) 32 | if [[ $@ == *"IPAddress"* ]]; then 33 | echo "172.17.0.34" 34 | exit 0 35 | fi 36 | 37 | if [[ $@ =~ \{\{.Config.Image\}\} ]]; then 38 | echo "$PLUGIN_IMAGE:$PLUGIN_IMAGE_VERSION" 39 | exit 0 40 | fi 41 | 42 | if [[ $@ =~ \{\{\.State\..*\}\} ]]; then 43 | if [[ $@ =~ \{\{\.State\.Running\}\} ]]; then 44 | echo "true" 45 | else 46 | echo "false" 47 | fi 48 | exit 0 49 | fi 50 | 51 | # running 52 | echo "true" 53 | ;; 54 | kill) 55 | echo "testid" 56 | ;; 57 | logs) 58 | echo "$PLUGIN_SERVICE $PLUGIN_IMAGE_VERSION" 59 | ;; 60 | ps) 61 | if [[ $@ == *"no-trunc"* ]]; then 62 | echo "1479bbd60ade8a92617d2aeb4935bd3ff3179bd0fd71c22c3102c421f4bc221f" 63 | exit 0 64 | else 65 | echo 'CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES' 66 | echo '4eeaae231d5e elasticsearch:2.3.5 "/docker-entrypoint." 11 seconds ago Up 10 seconds 9200/tcp, 9300/tcp dokku.elasticsearch.l' 67 | echo '2b20a383226d frodenas/couchdb:1.6 "/scripts/run.sh" 11 seconds ago Up 10 seconds 5984/tcp dokku.couchdb.l' 68 | echo '76a0e7154483 dokkupaas/docker-grafana-graphite:3.0.1 "/usr/bin/supervisor" 11 seconds ago Up 10 seconds 80/tcp, 2003/tcp, 8126/tcp, 8125/udp dokku.graphite.l' 69 | echo '94df08fe5550 mariadb:10.1.16 "/docker-entrypoint." 11 seconds ago Up 10 seconds 3306/tcp dokku.mariadb.l' 70 | echo 'ef27fec191ba memcached:1.4.31 "/entrypoint.sh memc" 11 seconds ago Up 10 seconds 11211/tcp dokku.memcached.l' 71 | echo 'c0f74fc90377 mongo:3.2.9 "/entrypoint.sh mong" 11 seconds ago Up 10 seconds 27017/tcp dokku.mongo.l' 72 | echo '0f33b1c86da9 mysql:5.7.12 "/entrypoint.sh mysq" 11 seconds ago Up 10 seconds 3306/tcp dokku.mysql.l' 73 | echo '9f10b6dc12d5 nats:0.9.4 "/entrypoint.sh redi" 11 seconds ago Up 10 seconds 4222/tcp dokku.nats.l' 74 | echo '7f899b723c08 postgres:9.5.4 "/docker-entrypoint." 11 seconds ago Up 10 seconds 5432/tcp dokku.postgres.l' 75 | echo '5e50a462661e rabbitmq:3.6.5-management "/docker-entrypoint." 11 seconds ago Up 10 seconds 5672/tcp, 15672/tcp dokku.rabbitmq.l' 76 | echo 'c39ca00fa3c6 redis:3.2.3 "/entrypoint.sh redi" 11 seconds ago Up 10 seconds 6379/tcp dokku.redis.l' 77 | echo 'dc98c2939a80 rethinkdb:2.3.4 "rethinkdb --bind al" 11 seconds ago Up 10 seconds 8080/tcp, 28015/tcp, 29015/tcp dokku.rethinkdb.l' 78 | fi 79 | ;; 80 | pull) 81 | exit 0 82 | ;; 83 | restart) 84 | echo "testid" 85 | ;; 86 | rm) 87 | echo "testid" 88 | ;; 89 | run) 90 | echo "testid" 91 | ;; 92 | start) 93 | echo "testid" 94 | ;; 95 | stop) 96 | echo "testid" 97 | ;; 98 | *) 99 | exit "$DOKKU_NOT_IMPLEMENTED_EXIT" 100 | ;; 101 | esac 102 | -------------------------------------------------------------------------------- /tests/bin/id: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | echo "dokku" 3 | -------------------------------------------------------------------------------- /tests/bin/lsb_release: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | if [[ "$(uname)" == "Darwin" ]]; then 3 | echo "Darwin" 4 | else 5 | echo "Ubuntu" 6 | fi 7 | -------------------------------------------------------------------------------- /tests/commands.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | load test_helper 3 | 4 | APP=acl-test-app 5 | APP_DIR="${DOKKU_ROOT:?}/$APP" 6 | HOOK="${DOKKU_ROOT:?}/plugins/${PLUGIN_COMMAND_PREFIX:?}/pre-build" 7 | 8 | setup() { 9 | dokku apps:create acl-test-app >&2 10 | TMP=$(mktemp -d) 11 | export DOKKU_LIB_ROOT="$TMP" 12 | export DOKKU_LIB_PATH="$TMP" 13 | SERVICE_PATH="$DOKKU_LIB_ROOT/services/redis/acl-test-service" 14 | mkdir -p "$SERVICE_PATH" 15 | } 16 | 17 | teardown() { 18 | sudo -u $DOKKU_SYSTEM_USER rm -rf "${APP_DIR:?}" 19 | rm -rf "$TMP" 20 | } 21 | 22 | @test "($PLUGIN_COMMAND_PREFIX:add) can add a user to an ACL" { 23 | run dokku acl:add $APP user1 24 | assert_success 25 | echo exit $? 26 | 27 | [ -e "${APP_DIR}/acl/user1" ] || flunk "ACL file not created" 28 | } 29 | 30 | @test "($PLUGIN_COMMAND_PREFIX:add) adding user that already exists fails" { 31 | dokku acl:add $APP user1 32 | run dokku acl:add $APP user1 33 | assert_failure "User already has permissions to push to this repository" 34 | 35 | [ -e "${APP_DIR}/acl/user1" ] || flunk "ACL file disappeared somehow" 36 | } 37 | 38 | @test "($PLUGIN_COMMAND_PREFIX:remove) can remove a user from an ACL" { 39 | sudo -u $DOKKU_SYSTEM_USER mkdir -p $APP_DIR/acl 40 | sudo -u $DOKKU_SYSTEM_USER touch $APP_DIR/acl/user1 41 | 42 | run dokku acl:remove $APP user1 43 | assert_success 44 | 45 | [ ! -e "${APP_DIR}/acl/user1" ] || flunk "ACL file still exists" 46 | } 47 | 48 | @test "($PLUGIN_COMMAND_PREFIX:list) can list users in an ACL" { 49 | dokku acl:add $APP user1 50 | 51 | run dokku acl:list $APP 52 | assert_success "user1" 53 | 54 | dokku acl:add $APP user2 55 | 56 | run dokku acl:list $APP 57 | assert_success 58 | assert_equal ${lines[0]} "user1" 59 | assert_equal ${lines[1]} "user2" 60 | } 61 | 62 | @test "($PLUGIN_COMMAND_PREFIX:add) can add a user to a service ACL" { 63 | run dokku acl:add-service redis acl-test-service user1 64 | assert_success 65 | echo exit $? 66 | 67 | [ -e "$SERVICE_PATH/acl/user1" ] || flunk "ACL file not created" 68 | } 69 | 70 | @test "($PLUGIN_COMMAND_PREFIX:remove) can remove a user from a service ACL" { 71 | sudo -u "$DOKKU_SYSTEM_USER" mkdir -p "$SERVICE_PATH/acl" 72 | sudo -u "$DOKKU_SYSTEM_USER" touch "$SERVICE_PATH/acl/user1" 73 | 74 | run dokku acl:remove-service redis acl-test-service user1 75 | assert_success 76 | 77 | [ ! -e "$SERVICE_PATH/acl/user1" ] || flunk "ACL file still exists" 78 | } 79 | 80 | @test "($PLUGIN_COMMAND_PREFIX:list) can list users in a service ACL" { 81 | dokku acl:add-service redis acl-test-service user1 82 | 83 | run dokku acl:list-service redis acl-test-service 84 | assert_success "user1" 85 | 86 | dokku acl:add-service redis acl-test-service user2 87 | 88 | run dokku acl:list-service redis acl-test-service 89 | assert_success 90 | assert_equal ${lines[0]} "user1" 91 | assert_equal ${lines[1]} "user2" 92 | } 93 | 94 | @test "($PLUGIN_COMMAND_PREFIX:default) help information is shown by default" { 95 | if [[ $DOKKU_VERSION = 'v0.4.0' ]] ; then 96 | skip "Default commands weren't implemented for v0.4.0" 97 | fi 98 | 99 | run dokku acl:help 100 | help_output="$output" 101 | assert_success 102 | 103 | run dokku acl 104 | assert_success "$help_output" 105 | } 106 | -------------------------------------------------------------------------------- /tests/hook_pre_build.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | load test_helper 3 | 4 | APP=acl-test-app 5 | APP_DIR="${DOKKU_ROOT:?}/$APP" 6 | HOOK="${DOKKU_ROOT:?}/plugins/${PLUGIN_COMMAND_PREFIX:?}/pre-build" 7 | 8 | setup() { 9 | dokku apps:create acl-test-app >&2 10 | } 11 | 12 | teardown() { 13 | sudo -u $DOKKU_SYSTEM_USER rm -rf "${APP_DIR:?}" 14 | } 15 | 16 | @test "($PLUGIN_COMMAND_PREFIX:hook-pre-build) allows write access by default" { 17 | NAME=user1 run $HOOK $APP 18 | assert_success 19 | 20 | NAME=admin run $HOOK $APP 21 | assert_success 22 | } 23 | 24 | @test "($PLUGIN_COMMAND_PREFIX:hook-pre-build) allows only configured users" { 25 | sudo -u $DOKKU_SYSTEM_USER mkdir -p $APP_DIR/acl 26 | sudo -u $DOKKU_SYSTEM_USER touch $APP_DIR/acl/user1 27 | 28 | NAME=user1 run $HOOK $APP 29 | assert_success 30 | 31 | NAME=user2 run $HOOK $APP 32 | assert_failure "User user2 does not have permissions to modify this repository" 33 | 34 | NAME=admin run $HOOK $APP 35 | assert_failure "User admin does not have permissions to modify this repository" 36 | } 37 | 38 | @test "($PLUGIN_COMMAND_PREFIX:hook-pre-build) allows only superuser when enabled and no configured users" { 39 | export DOKKU_SUPER_USER=admin 40 | 41 | NAME=user1 run $HOOK $APP 42 | assert_failure "Only admin can modify a repository if the ACL is empty" 43 | 44 | NAME=admin run $HOOK $APP 45 | assert_success 46 | } 47 | 48 | @test "($PLUGIN_COMMAND_PREFIX:hook-pre-build) allows configured users plus superuser when enabled" { 49 | export DOKKU_SUPER_USER=admin 50 | sudo -u $DOKKU_SYSTEM_USER mkdir -p $APP_DIR/acl 51 | sudo -u $DOKKU_SYSTEM_USER touch $APP_DIR/acl/user1 52 | 53 | NAME=user1 run $HOOK $APP 54 | assert_success 55 | 56 | NAME=user2 run $HOOK $APP 57 | assert_failure "User user2 does not have permissions to modify this repository" 58 | 59 | NAME=admin run $HOOK $APP 60 | assert_success 61 | } 62 | 63 | @test "($PLUGIN_COMMAND_PREFIX:hook-pre-build) implements legacy command line behaviour by default" { 64 | unset NAME 65 | 66 | # No app ACL, no DOKKU_SUPER_USER -> success 67 | SSH_NAME=default run $HOOK $APP 68 | assert_success 69 | 70 | run $HOOK $APP 71 | assert_success 72 | 73 | # No app ACL, DOKKU_SUPER_USER set -> failure 74 | export DOKKU_SUPER_USER=admin 75 | 76 | SSH_NAME=default run $HOOK $APP 77 | assert_failure "It appears that you're running this command from the command line. The \"dokku-acl\" plugin disables this by default for safety. Please check the \"dokku-acl\" documentation for how to enable command line usage." 78 | 79 | run $HOOK $APP 80 | assert_failure "It appears that you're running this command from the command line. The \"dokku-acl\" plugin disables this by default for safety. Please check the \"dokku-acl\" documentation for how to enable command line usage." 81 | 82 | # App ACL exists, no DOKKU_SUPER_USER -> success 83 | unset DOKKU_SUPER_USER 84 | sudo -u $DOKKU_SYSTEM_USER mkdir -p $APP_DIR/acl 85 | sudo -u $DOKKU_SYSTEM_USER touch $APP_DIR/acl/user1 86 | 87 | SSH_NAME=default run $HOOK $APP 88 | assert_success 89 | 90 | run $HOOK $APP 91 | assert_success 92 | 93 | # App ACL exists, DOKKU_SUPER_USER set -> failure 94 | export DOKKU_SUPER_USER=admin 95 | 96 | SSH_NAME=default run $HOOK $APP 97 | assert_failure "It appears that you're running this command from the command line. The \"dokku-acl\" plugin disables this by default for safety. Please check the \"dokku-acl\" documentation for how to enable command line usage." 98 | 99 | run $HOOK $APP 100 | assert_failure "It appears that you're running this command from the command line. The \"dokku-acl\" plugin disables this by default for safety. Please check the \"dokku-acl\" documentation for how to enable command line usage." 101 | } 102 | 103 | @test "($PLUGIN_COMMAND_PREFIX:hook-pre-build) allows command line usage when DOKKU_ACL_ALLOW_COMMAND_LINE set" { 104 | unset NAME 105 | export DOKKU_ACL_ALLOW_COMMAND_LINE=1 106 | 107 | # No app ACL, no DOKKU_SUPER_USER -> success 108 | run $HOOK $APP 109 | assert_success 110 | 111 | # No app ACL, DOKKU_SUPER_USER set -> success 112 | export DOKKU_SUPER_USER=admin 113 | 114 | run $HOOK $APP 115 | assert_success 116 | 117 | # App ACL exists, no DOKKU_SUPER_USER -> success 118 | unset DOKKU_SUPER_USER 119 | sudo -u $DOKKU_SYSTEM_USER mkdir -p $APP_DIR/acl 120 | sudo -u $DOKKU_SYSTEM_USER touch $APP_DIR/acl/user1 121 | 122 | run $HOOK $APP 123 | assert_success 124 | 125 | # App ACL exists, DOKKU_SUPER_USER set -> success 126 | run $HOOK $APP 127 | assert_success 128 | } 129 | -------------------------------------------------------------------------------- /tests/hook_user_auth.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | load test_helper 3 | 4 | APP=acl-test-app 5 | APP_DIR="${DOKKU_ROOT:?}/$APP" 6 | HOOK="${DOKKU_ROOT:?}/plugins/${PLUGIN_COMMAND_PREFIX:?}/user-auth" 7 | 8 | # Test a subset of easy to check commands 9 | ALLOWED_CMDS="apps:list certs:report help" 10 | RESTRICTED_CMDS="domains:report events ls ps:report" 11 | ALL_CMDS="$ALLOWED_CMDS $RESTRICTED_CMDS" 12 | 13 | # Slightly more complicated: these commands require an app and/or service name as an argument 14 | PER_APP_CMDS="config logs urls" 15 | PER_SERVICE_CMDS="redis:info redis:stop" 16 | LINK_CMDS="redis:link redis:unlink" 17 | 18 | setup() { 19 | dokku apps:create acl-test-app >&2 20 | TMP=$(mktemp -d) 21 | export DOKKU_LIB_ROOT="$TMP" 22 | } 23 | 24 | teardown() { 25 | sudo -u $DOKKU_SYSTEM_USER rm -rf "${APP_DIR:?}" 26 | rm -rf "$TMP" 27 | } 28 | 29 | @test "($PLUGIN_COMMAND_PREFIX:hook-user-auth) allows all commands by default" { 30 | for cmd in $ALL_CMDS; do 31 | run $HOOK dokku user1 $cmd 32 | assert_success 33 | done 34 | 35 | for cmd in $PER_APP_CMDS; do 36 | run $HOOK dokku user1 $cmd acl-test-app 37 | assert_success 38 | done 39 | } 40 | 41 | @test "($PLUGIN_COMMAND_PREFIX:hook-user-auth) allows only whitelisted commands" { 42 | export DOKKU_ACL_USER_COMMANDS="$ALLOWED_CMDS" 43 | 44 | for cmd in $ALLOWED_CMDS; do 45 | run $HOOK dokku user1 $cmd 46 | assert_success 47 | done 48 | 49 | for cmd in $RESTRICTED_CMDS; do 50 | run $HOOK dokku user1 $cmd 51 | assert_failure "User user1 does not have permissions to run $cmd" 52 | done 53 | } 54 | 55 | @test "($PLUGIN_COMMAND_PREFIX:hook-user-auth) allows per-app commands only for users in the app ACL" { 56 | export DOKKU_ACL_PER_APP_COMMANDS="$PER_APP_CMDS" 57 | sudo -u $DOKKU_SYSTEM_USER mkdir -p $APP_DIR/acl 58 | sudo -u $DOKKU_SYSTEM_USER touch $APP_DIR/acl/user1 59 | 60 | for cmd in $PER_APP_CMDS; do 61 | run $HOOK dokku user1 $cmd acl-test-app 62 | assert_success 63 | done 64 | 65 | for cmd in $PER_APP_CMDS; do 66 | run $HOOK dokku user2 $cmd acl-test-app 67 | assert_failure "User user2 does not have permissions to run $cmd on acl-test-app, or acl-test-app does not exist" 68 | done 69 | } 70 | 71 | @test "($PLUGIN_COMMAND_PREFIX:hook-user-auth) per-app commands do not reveal if an app exists or not" { 72 | export DOKKU_ACL_PER_APP_COMMANDS="$PER_APP_CMDS" 73 | 74 | for cmd in $PER_APP_CMDS; do 75 | run $HOOK dokku user2 $cmd nonexistent-app 76 | assert_failure "User user2 does not have permissions to run $cmd on nonexistent-app, or nonexistent-app does not exist" 77 | done 78 | } 79 | 80 | @test "($PLUGIN_COMMAND_PREFIX:hook-user-auth) per-app commands fail if no app" { 81 | export DOKKU_ACL_PER_APP_COMMANDS="$PER_APP_CMDS" 82 | 83 | for cmd in $PER_APP_CMDS; do 84 | run $HOOK dokku user2 $cmd 85 | assert_failure "An app name is required" 86 | done 87 | } 88 | 89 | @test "($PLUGIN_COMMAND_PREFIX:hook-user-auth) allows per-service commands only for users in the service ACL" { 90 | export DOKKU_ACL_PER_SERVICE_COMMANDS="$PER_SERVICE_CMDS" 91 | local SERVICE_DIR="$DOKKU_LIB_ROOT/services/redis/acl-test-service" 92 | sudo -u $DOKKU_SYSTEM_USER mkdir -p $SERVICE_DIR/acl 93 | sudo -u $DOKKU_SYSTEM_USER touch $SERVICE_DIR/acl/user1 94 | 95 | for cmd in $PER_SERVICE_CMDS; do 96 | run $HOOK dokku user1 $cmd acl-test-service 97 | assert_success 98 | done 99 | 100 | for cmd in $PER_SERVICE_CMDS; do 101 | run $HOOK dokku user2 $cmd acl-test-service 102 | assert_failure "User user2 does not have permissions to run $cmd on acl-test-service, or acl-test-service does not exist" 103 | done 104 | } 105 | 106 | @test "($PLUGIN_COMMAND_PREFIX:hook-user-auth) allows link commands only for users in the service AND app ACLs" { 107 | sudo -u $DOKKU_SYSTEM_USER mkdir -p $APP_DIR/acl 108 | sudo -u $DOKKU_SYSTEM_USER touch $APP_DIR/acl/user1 109 | sudo -u $DOKKU_SYSTEM_USER touch $APP_DIR/acl/user2 110 | 111 | export DOKKU_ACL_LINK_COMMANDS="$LINK_CMDS" 112 | local SERVICE_DIR="$DOKKU_LIB_ROOT/services/redis/acl-test-service" 113 | sudo -u $DOKKU_SYSTEM_USER mkdir -p $SERVICE_DIR/acl 114 | sudo -u $DOKKU_SYSTEM_USER touch $SERVICE_DIR/acl/user1 115 | sudo -u $DOKKU_SYSTEM_USER touch $SERVICE_DIR/acl/user3 116 | 117 | for cmd in $LINK_CMDS; do 118 | run $HOOK dokku user1 $cmd acl-test-service acl-test-app 119 | assert_success 120 | done 121 | 122 | for user in user2 user3 user4; do 123 | for cmd in $LINK_CMDS; do 124 | run $HOOK dokku $user $cmd acl-test-service acl-test-app 125 | 126 | [[ "$user" = user3 ]] && type=app || type=service 127 | 128 | assert_failure "User $user does not have permissions to run $cmd on acl-test-$type, or acl-test-$type does not exist" 129 | done 130 | done 131 | } 132 | 133 | @test "($PLUGIN_COMMAND_PREFIX:hook-user-auth) superuser and root can run any commands" { 134 | export DOKKU_ACL_USER_COMMANDS="$ALLOWED_CMDS" 135 | export DOKKU_ACL_PER_APP_COMMANDS="$PER_APP_CMDS" 136 | export DOKKU_ACL_PER_SERVICE_COMMANDS="$PER_SERVICE_CMDS" 137 | export DOKKU_SUPER_USER=admin 138 | 139 | for cmd in $ALL_CMDS; do 140 | run $HOOK dokku admin $cmd 141 | assert_success 142 | done 143 | 144 | for cmd in $ALL_CMDS; do 145 | run $HOOK root root $cmd 146 | assert_success 147 | done 148 | 149 | for cmd in $PER_APP_CMDS $PER_SERVICE_CMDS; do 150 | run $HOOK dokku admin $cmd acl-test-thing 151 | assert_success 152 | done 153 | 154 | for cmd in $PER_APP_CMDS $PER_SERVICE_CMDS; do 155 | run $HOOK root root $cmd acl-test-thing 156 | assert_success 157 | done 158 | 159 | for cmd in $LINK_CMDS; do 160 | run $HOOK dokku admin $cmd acl-test-service acl-test-app 161 | assert_success 162 | done 163 | 164 | for cmd in $LINK_CMDS; do 165 | run $HOOK root root $cmd acl-test-service acl-test-app 166 | assert_success 167 | done 168 | } 169 | -------------------------------------------------------------------------------- /tests/setup.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash -x 2 | set -eo pipefail 3 | [[ $DOKKU_TRACE ]] && set -x 4 | source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/test_helper.bash" 5 | 6 | BIN_STUBS="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/bin" 7 | 8 | if [[ ! -d $DOKKU_ROOT ]]; then 9 | git clone https://github.com/dokku/dokku.git $DOKKU_ROOT >/dev/null 10 | fi 11 | 12 | cd $DOKKU_ROOT 13 | echo "Dokku version $DOKKU_VERSION" 14 | git checkout $DOKKU_VERSION >/dev/null 15 | if grep go-build Makefile >/dev/null; then 16 | mv "$BIN_STUBS/docker" "$BIN_STUBS/docker-stub" 17 | make go-build 18 | mv "$BIN_STUBS/docker-stub" "$BIN_STUBS/docker" 19 | fi 20 | cd - 21 | 22 | test -f /etc/init.d/nginx || { 23 | sudo touch /etc/init.d/nginx 24 | sudo chmod +x /etc/init.d/nginx 25 | } 26 | 27 | source "$(dirname "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)")/config" 28 | rm -rf $DOKKU_ROOT/plugins/$PLUGIN_COMMAND_PREFIX 29 | mkdir -p $DOKKU_ROOT/plugins/$PLUGIN_COMMAND_PREFIX $DOKKU_ROOT/plugins/$PLUGIN_COMMAND_PREFIX/subcommands 30 | find ./ -maxdepth 1 -type f -exec cp '{}' $DOKKU_ROOT/plugins/$PLUGIN_COMMAND_PREFIX \; 31 | find ./subcommands -maxdepth 1 -type f -exec cp '{}' $DOKKU_ROOT/plugins/$PLUGIN_COMMAND_PREFIX/subcommands \; 32 | echo "$DOKKU_VERSION" >$DOKKU_ROOT/VERSION 33 | 34 | if [[ ! -f $BIN_STUBS/plugn ]]; then 35 | wget -O- "$PLUGN_URL" | tar xzf - -C "$BIN_STUBS" 36 | plugn init 37 | find "$DOKKU_ROOT/plugins" -mindepth 1 -maxdepth 1 -type d ! -name 'available' ! -name 'enabled' -exec ln -s {} "$DOKKU_ROOT/plugins/available" \; 38 | find "$DOKKU_ROOT/plugins" -mindepth 1 -maxdepth 1 -type d ! -name 'available' ! -name 'enabled' -exec ln -s {} "$DOKKU_ROOT/plugins/enabled" \; 39 | fi 40 | -------------------------------------------------------------------------------- /tests/test_helper.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | export DOKKU_QUIET_OUTPUT=1 3 | export DOKKU_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/dokku" 4 | export DOKKU_VERSION=${DOKKU_VERSION:-"master"} 5 | export PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/bin:$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/dokku:$PATH" 6 | export PLUGIN_COMMAND_PREFIX="acl" 7 | export PLUGIN_PATH="$DOKKU_ROOT/plugins" 8 | export PLUGIN_ENABLED_PATH="$PLUGIN_PATH" 9 | export PLUGIN_AVAILABLE_PATH="$PLUGIN_PATH" 10 | export PLUGIN_CORE_AVAILABLE_PATH="$PLUGIN_PATH" 11 | 12 | export DOKKU_LIB_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib-root" 13 | if [[ "$(uname)" == "Darwin" ]]; then 14 | export PLUGN_URL="https://github.com/dokku/plugn/releases/download/v0.3.0/plugn_0.3.0_darwin_x86_64.tgz" 15 | else 16 | export PLUGN_URL="https://github.com/dokku/plugn/releases/download/v0.3.0/plugn_0.3.0_linux_x86_64.tgz" 17 | fi 18 | 19 | flunk() { 20 | { 21 | if [ "$#" -eq 0 ]; then 22 | cat - 23 | else 24 | echo "$*" 25 | fi 26 | } 27 | return 1 28 | } 29 | 30 | assert_equal() { 31 | if [ "$1" != "$2" ]; then 32 | { 33 | echo "expected: $1" 34 | echo "actual: $2" 35 | } | flunk 36 | fi 37 | } 38 | 39 | assert_exit_status() { 40 | assert_equal "$status" "$1" 41 | } 42 | 43 | assert_success() { 44 | if [ "$status" -ne 0 ]; then 45 | flunk "command failed with exit status $status" 46 | elif [ "$#" -gt 0 ]; then 47 | assert_output "$1" 48 | fi 49 | } 50 | 51 | assert_failure() { 52 | if [[ "$status" -eq 0 ]]; then 53 | flunk "expected failed exit status" 54 | elif [[ "$#" -gt 0 ]]; then 55 | assert_output "$1" 56 | fi 57 | } 58 | 59 | assert_exists() { 60 | if [ ! -f "$1" ]; then 61 | flunk "expected file to exist: $1" 62 | fi 63 | } 64 | 65 | assert_contains() { 66 | if [[ "$1" != *"$2"* ]]; then 67 | flunk "expected $2 to be in: $1" 68 | fi 69 | } 70 | 71 | assert_output() { 72 | local expected 73 | if [ $# -eq 0 ]; then 74 | expected="$(cat -)" 75 | else 76 | expected="$1" 77 | fi 78 | assert_equal "$expected" "$output" 79 | } 80 | -------------------------------------------------------------------------------- /user-auth: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | # When enabled via the DOKKU_ACL_USER_COMMANDS variable, allow normal users 3 | # to run only these commands. 4 | 5 | set -eo pipefail 6 | [[ $DOKKU_TRACE ]] && set -x 7 | 8 | source "$PLUGIN_CORE_AVAILABLE_PATH/common/functions" 9 | source "$(dirname "${BASH_SOURCE[0]}")/internal-functions" 10 | 11 | DOKKU_SUPER_USER="${DOKKU_SUPER_USER:-}" 12 | DOKKU_ACL_USER_COMMANDS="${DOKKU_ACL_USER_COMMANDS:-}" 13 | DOKKU_ACL_PER_APP_COMMANDS="${DOKKU_ACL_PER_APP_COMMANDS:-}" 14 | DOKKU_ACL_PER_SERVICE_COMMANDS="${DOKKU_ACL_PER_SERVICE_COMMANDS:-}" 15 | DOKKU_ACL_LINK_COMMANDS="${DOKKU_ACL_LINK_COMMANDS:-}" 16 | 17 | SSH_USER=$1 18 | SSH_NAME=$2 19 | shift 2 20 | 21 | [[ -z "$DOKKU_ACL_USER_COMMANDS" && -z "$DOKKU_ACL_PER_APP_COMMANDS" && -z "$DOKKU_ACL_PER_SERVICE_COMMANDS" && -z "$DOKKU_ACL_LINK_COMMANDS" ]] && exit 0 22 | [[ "$SSH_USER" == "root" ]] && exit 0 23 | [[ -n "$DOKKU_SUPER_USER" ]] && [[ "$SSH_NAME" == "$DOKKU_SUPER_USER" ]] && exit 0 24 | 25 | CMD=$1 26 | 27 | for allowed in $DOKKU_ACL_USER_COMMANDS; do 28 | [[ "$CMD" == "$allowed" ]] && exit 0 29 | done 30 | 31 | for allowed in $DOKKU_ACL_PER_APP_COMMANDS; do 32 | if [[ "$CMD" == "$allowed" ]]; then 33 | if [[ -z "$2" ]]; then 34 | dokku_log_fail "An app name is required" 35 | fi 36 | # shellcheck disable=SC1003 37 | APP_NAME="$(echo "$2" | perl -pe 's/(?