From 1a052969dcba3e65c1700c206c4d606df59156ec Mon Sep 17 00:00:00 2001 From: Jose Diaz-Gonzalez Date: Sat, 27 Nov 2021 14:20:47 -0500 Subject: [PATCH] feat: app filtering to commands listing or iterating over apps This increases the time to list apps from .05s to .2s~.3s on the simplest check, but the increased time is worth it for actually verifyng access to an app (should that need arise). We'll also want to add the same to app existence checks. --- docs/development/plugin-triggers.md | 41 +++++++++++++++++++++++++++++ plugins/20_events/user-auth-app | 1 + plugins/apps/apps.go | 2 +- plugins/apps/functions.go | 37 ++++++++++++++++++++++++++ plugins/common/subprocess.go | 17 ++++++++++++ 5 files changed, 97 insertions(+), 1 deletion(-) create mode 120000 plugins/20_events/user-auth-app diff --git a/docs/development/plugin-triggers.md b/docs/development/plugin-triggers.md index c89069338..9014822f7 100644 --- a/docs/development/plugin-triggers.md +++ b/docs/development/plugin-triggers.md @@ -2367,3 +2367,44 @@ shift 2 [[ "$SSH_NAME" == "default" && $1 == plugin:* ]] && exit 1 exit 0 ``` + +### `user-auth-app` + +This is a special plugin trigger that is executed when listing apps or checking if an app exists. All Dokku commands should check if an app exists at least once before interacting with them so as not to circumvent the check. + +Note that the trigger should exit `0`, and each non-empty line on stdout is captured as a valid app name. + +The `SSH_USER` is the original ssh user. If you are running remote commands, this user will typically be `dokku`, and as such should not be trusted when checking permissions. If you are connected via ssh as a different user who then invokes `dokku`, the value of this variable will be that user's name (`root`, `myuser`, etc.). + +The `SSH_NAME` is the `NAME` variable set via the `sshcommand acl-add` command. For reference, the following command can be run as the root user to specify a specific `NAME` for a given ssh key: + +```shell +sshcommand acl-add dokku NAME < $PATH_TO_SSH_KEY +``` + +Note that the `NAME` value is set at the first ssh key match. If an ssh key is set in the `/home/dokku/.ssh/authorized_keys` multiple times, the first match will decide the value. + +- Description: Allows you to deny access to a Dokku app by either ssh user or associated ssh-command NAME user. +- Invoked by: `dokku` +- Arguments: `$SSH_USER $SSH_NAME $DOKKU_COMMAND` +- Example: + +```shell +#!/usr/bin/env bash +# hide any apps with the prefix "admin" +# if the logged in user (SSH_USER) or SSH_NAME is not `root` + +main() { + declare SSH_USER="$1" SSH_NAME="$2" ARGS=("${@:3}") + + for arg in "${ARGS[@]}"; do + if [[ "$arg" == admin-* ]] && [[ "$SSH_USER" != "root" ]] && [[ "$SSH_NAME" != "root" ]]; then + continue + fi + + echo "${arg}" + done +} + +main "$@" +``` diff --git a/plugins/20_events/user-auth-app b/plugins/20_events/user-auth-app new file mode 120000 index 000000000..5178a749f --- /dev/null +++ b/plugins/20_events/user-auth-app @@ -0,0 +1 @@ +hook \ No newline at end of file diff --git a/plugins/apps/apps.go b/plugins/apps/apps.go index ae4bf246e..d125c3a6e 100644 --- a/plugins/apps/apps.go +++ b/plugins/apps/apps.go @@ -46,5 +46,5 @@ func DokkuApps() ([]string, error) { return apps, fmt.Errorf("You haven't deployed any applications yet") } - return apps, nil + return filterApps(apps) } diff --git a/plugins/apps/functions.go b/plugins/apps/functions.go index d19b8b370..7d1221a30 100644 --- a/plugins/apps/functions.go +++ b/plugins/apps/functions.go @@ -154,3 +154,40 @@ func maybeCreateApp(appName string) error { return createApp(appName) }) } + +func filterApps(apps []string) ([]string, error) { + if !common.PlugnTriggerExists("user-auth-app") { + return apps, nil + } + + sshUser := os.Getenv("SSH_USER") + if sshUser == "" { + sshUser = os.Getenv("USER") + } + + sshName := os.Getenv("SSH_NAME") + if sshName == "" { + sshName = "default" + } + + args := append([]string{sshUser, sshName}, apps...) + b, _ := common.PlugnTriggerOutput("user-auth-app", args...) + filteredApps := strings.Split(strings.TrimSpace(string(b[:])), "\n") + filteredApps = removeEmptyEntries(filteredApps) + + if len(filteredApps) == 0 { + return filteredApps, fmt.Errorf("You haven't deployed any applications yet") + } + + return filteredApps, nil +} + +func removeEmptyEntries(s []string) []string { + var r []string + for _, str := range s { + if str != "" { + r = append(r, str) + } + } + return r +} diff --git a/plugins/common/subprocess.go b/plugins/common/subprocess.go index 2f970474d..f90f7fc41 100644 --- a/plugins/common/subprocess.go +++ b/plugins/common/subprocess.go @@ -5,6 +5,7 @@ import ( "io/ioutil" "os" "os/exec" + "path/filepath" "strings" "github.com/codeskyblue/go-sh" @@ -128,3 +129,19 @@ func PlugnTriggerSetup(triggerName string, args ...string) *sh.Session { } return sh.Command("plugn", shellArgs...) } + +// PlugnTriggerExists returns whether a plugin trigger exists (ignoring the existence of any within the 20_events plugin) +func PlugnTriggerExists(triggerName string) bool { + pluginPath := MustGetEnv("PLUGIN_ENABLED_PATH") + glob := filepath.Join(pluginPath, "*", triggerName) + exists := false + files, _ := filepath.Glob(glob) + for _, file := range files { + plugin := strings.Trim(strings.TrimPrefix(strings.TrimSuffix(file, "/user-auth-app"), pluginPath), "/") + if plugin != "20_events" { + exists = true + break + } + } + return exists +}