Project: Teleport (Provisioning / Security & Compliance)
File: 0061-tsh-aliases.md

authors: Roman Tkachenko (roman@goteleport.com) state: implemented Proposes a way for users to define custom tsh commands. Defining custom commands will allow users to extend `tsh` CLI to fit their use-case without having to modify Teleport's source code. Examples of what users might want to do include: Define shorthands for the existing `tsh` commands. Tweak the default behavior of existing `tsh` commands, for example to have `tsh login` always default to a particular auth connector or username. Define new subcommands combining multiple actions in the same command, for example logging into a cluster, a database and connecting to the database. The simplest analogue that comes to mind is the bash alias: ```bash alias ll="ls -l" ``` Bash aliases are quite simplistic as they only allow to define new commands that expand to the strings they alias and don't provide a way to refer to the command's arguments. Git aliases are defined in the repo-specific `.gitconfig` (or `.git/config`) or global `/etc/gitconfig` file, and are much closer conceptually to what we want to accomplish as they allow to define custom `git` subcommands: ```ini [alias] co = checkout ls = log --oneline ``` Git aliases also support calling non-`git` commands (using `!` notation) and refer to the command's arguments (via `$@`, `$1`, etc.) which allows to build powerful new commands. For example, the following alias will make `git top` to show 15 most recently updated local branches: ```ini [alias] top = "!bash -c 'git branch --sort=-committerdate | head -15'" ``` Users will define aliases in their `tsh` configuration file which is kept in `$TELEPORT_HOME/config/config.yaml` using the following syntax: ```yaml aliases: "<alias>": "<command>" ``` The `<alias>` can only be a top-level subcommand. In other words, users can define a new `tsh mycommand` alias but cannot define `tsh my command` command. A few notes regarding the `<command>`: Unlike `git` aliases explored above, proposal is to always require the binary name instead of defaulting to `tsh` and requiring special syntax otherwise. The `<command>` will be fed to `exec.Command` for execution. To chain multiple commands, use pipes, etc. users will explicitly use `bash -c`. The `<command>` will require `$@`, `$1`, etc. to reference the arguments provided to the alias command. Starting with a very simple example, suppose you're tired of typing `tsh login` every morning and want to alias it to something shorter

`tsh l`: ```yaml aliases: "l": "tsh login $@" ``` As a more practical example, the following alias will make `tsh login` default to the specific auth connector and username: ```yaml aliases: "login": "tsh login --auth=local --user=alice $@" ``` Similarly, as discussed in the search-based access requests RFD, some users may want `tsh ssh` to default to the mode in which it will auto-request access to the node upon encountering an access denied error: ```yaml aliases: "ssh": "tsh ssh -P $@" ``` Note: command arguments will be resolved prior to invoking the alias. So in the example above, when a user runs `tsh ssh root@node1`, the alias command executed will be `tsh ssh -P root@node1`. Each alias invocation will set a few environment variables that can be consumed within the alias. To prevent infinite recursion, `TSH_ALIAS` variable will be set indicating the command is invoked as an alias. If it's detected, `tsh` won't attempt to expand the same command again. For example, `TSH_ALIAS=login`. This will prevent other `login` commands from being expanded but will allow using aliases in other aliases (see below for an example). Each alias will also set `TSH` environment variable to the path of the `tsh` binary that invoked the alias and `SHELL` environment variable to the default shell of the user invoking the command. An alias can also define a custom subcommand that combines multiple commands. The following alias will connect to a node within a specific leaf cluster using `tsh connect leaf ubuntu@node-1` command: ```yaml aliases: "connect": "bash -c '$TSH login $1 && $TSH ssh $2'" ``` The following alias will list nodes in all clusters: ```yaml aliases: "lsall": "bash -c 'for cluster in $($TSH clusters | tail -3 | head -2 | cut -d \' \' -f1); $TSH ls --cluster=$cluster; done'" ``` Multiple aliases can be defined in the config and can reference each other: ```yaml aliases: "login": "$TSH login --auth=local --user=alice $@" "ssh": "$TSH ssh -P $@" "connect": "bash -c '$TSH login $1 && $TSH ssh $2'" ``` In this example, `tsh login` and `tsh ssh` will use the aliases when invoked as a part of the "connect" command. Currently tsh config is read from `$TELEPORT_HOME/config/config.yaml` which by default is user-specific since `$TELEPORT_HOME` defaults to `~/.tsh`. As a part of this feature, introduce global tsh config `/etc/tsh.yaml`. Settings from the global config will merge with the user-local config, with the local config taking precedence. For easier troubleshooting of aliases, make sure that tsh logs the actual command being executed when invoking an alias. When we have `bash`/`zsh` auto-completion, it should take aliased commands into account as well. For reference, `git` autocompletion supports it.