A transparent wrapper that adds support for regex, aliases, gateways, dynamic hostnames, graphviz, json output, yaml configuration, and more to SSH.
lib-ssh wraps assh as a ProxyCommand; it means that it works seamlessly with:
- ssh
 - scp
 - rsync
 - git
 - Desktop applications depending on 
lib-sshorssh(i.e., Tower, Atom.io, SSH Tunnel Manager) 
For specific examples, see 3rd Party Integration
- regex support
 - aliases 
gate->gate.domain.tld - gateways -> transparent ssh connection chaining
 - includes: split configuration in multiple files, note that OpenSSH as of v7.3 has native support for this
 - local command execution: finally the reverse of RemoteCommand
 - templates: equivalent to host but you can't connect directly to a template, perfect for inheritance
 - inheritance: make hosts inherits from host hosts or templates
 - variable expansion: resolve variables from the environment
 - smart proxycommand: RAW tcp connection when possible with 
netcatandsocatas default fallbacks - rate limit: configure a per-host or global rate-limiting
 - JSON output
 - Graphviz: graphviz reprensentation of the hosts
 
assh can use the ProxyCommand with netcat feature of OpenSSH transparently and without the pain of using extended configuration.
Connect to hosta using hostb as a gateway.
flowchart 
    direction TB
    y[you]
    a[hosta]
    b[hostb]
    fw((firewall))
    style fw fill:#f00,color:#fff
    y ==x fw
    fw .-> a
    
    y --> b
    b --> a
    $ ssh hosta/hostb
user@hosta $Equivalent to ssh -o ProxyCommand="ssh hostb nc %h %p" hosta
Connect to hosta using hostb as a gateway using hostc as a gateway.
flowchart 
    direction TB
    y[you]
    a[hosta]
    b[hostb]
    c[hostc]
    fw((firewall))
    style fw fill:#f00,color:#fff
    y ==x fw
    fw ..-> a
    
    y --> c
    c --> b
    b --> a
    $ ssh hosta/hostb/hostc
user@hosta $Equivalent to ssh -o ProxyCommand="ssh -o ProxyCommand='ssh hostc nc %h %p' hostb nc %h %p" hosta
You can define an equivalent of the "ProxyCommand with netcat" feature of OpenSSH, with a simpler syntax, more advanced workflows, and a unique fallback feature.
Let's consider the following assh.yml file
hosts:
  hosta:
    Hostname: 1.2.3.4
  hostb:
    Hostname: 5.6.7.8
    Gateways: hosta
  hostc:
    Hostname: 9.10.11.12
    Gateways: hostb
  hostd:
    Hostname: 13.14.15.16
    GatewayConnectTimeout: 2
    Gateways:
    - direct
    - hostassh hosta->ssh 1.2.3.4ssh hostb->ssh -o ProxyCommand="ssh hostb nc %h %p" hostassh hostc->ssh -o ProxyCommand="ssh -o ProxyCommand='ssh hostc nc %h %p' hostb nc %h %p" hostassh hostd->- assh will try to 
ssh 13.14.15.16 - then, fallback on 
ssh -o ProxyCommand="ssh hostd nc %h %p" hosta - this method allows you to have the best performances when it is possible, but ensure your commands will work if you are outside of your company for instance
 
- assh will try to 
 
- Automatically regenerates 
~/.ssh/configfile when needed - Inspect parent process to determine log level (if you use 
ssh -vv, assh will automatically run in debug mode) - Automatically creates 
ControlPathdirectories so you can use slashes in yourControlPathoption, can be enabled with theControlMasterMkdir: trueconfiguration in host or globally. 
BeforeConnect is called just before assh tries to connect to the remote SSH port.
Note: BeforeConnect will be called for each SSH connection; if you use multiple gateways, it will be called for each gateways until one succeed to connect.
Example of Golang template variables:
// Host: http://godoc.org/moul.io/assh/pkg/config/#Host
{{.Host.Name}}                                  //  localhost
{{.Host.HostName}}                              //  127.0.0.1
{{.Host.Port}}                                  //  22
{{.Host.User}}                                  //  moul
{{.Host.Prototype}}                             //  [email protected]:22
{{.Host}}                                       //  {"HostName":"localhost","Port":22","User":"moul","ControlPersist":"yes",...}
{{printf "%s:%s" .Host.HostName .Host.Port}}    //  localhost:22OnConnect is called as soon as assh is connected to the remote SSH port.
Note: OnConnect is not aware of the authentication process and will always be raised.
Example of Golang template variables:
// Host: http://godoc.org/moul.io/assh/pkg/config/#Host
{{.Host.Name}}                                  //  localhost
{{.Host.HostName}}                              //  127.0.0.1
{{.Host.Port}}                                  //  22
{{.Host.User}}                                  //  moul
{{.Host.Prototype}}                             //  [email protected]:22
{{.Host}}                                       //  {"HostName":"localhost","Port":22","User":"moul","ControlPersist":"yes",...}
{{printf "%s:%s" .Host.HostName .Host.Port}}    //  localhost:22
// Stats: http://godoc.org/moul.io/assh/pkg/commands/#ConnectionStats
{{.Stats.ConnectedAt}}                           //  2016-07-20 11:19:23.467900594 +0200 CESTOnConnectError is called when assh fails to open a new TCP connection.
Example of Golang template variables:
// Host: http://godoc.org/moul.io/assh/pkg/config/#Host
{{.Host.Name}}                                  //  localhost
{{.Host.HostName}}                              //  127.0.0.1
{{.Host.Port}}                                  //  22
{{.Host.User}}                                  //  moul
{{.Host.Prototype}}                             //  [email protected]:22
{{.Host}}                                       //  {"HostName":"localhost","Port":22","User":"moul","ControlPersist":"yes",...}
{{printf "%s:%s" .Host.HostName .Host.Port}}    //  localhost:22
// Error
{{.Error}}                                      //  dial tcp: lookup localhost: no such hostOnDisconnect is called as the assh socket is closed.
warning: if you don't see a notification when closing an SSH connection, then you probably have ControlMaster configured; OnDisconnect is not linked to the ssh program but to its socket which may stay alive even after exiting the ssh program.
Example of Golang template variables:
// Host: http://godoc.org/moul.io/assh/pkg/config/#Host
{{.Host.Name}}                                  //  localhost
{{.Host.HostName}}                              //  127.0.0.1
{{.Host.Port}}                                  //  22
{{.Host.User}}                                  //  moul
{{.Host.Prototype}}                             //  [email protected]:22
{{.Host}}                                       //  {"HostName":"localhost","Port":22","User":"moul","ControlPersist":"yes",...}
{{printf "%s:%s" .Host.HostName .Host.Port}}    //  localhost:22
// Stats: http://godoc.org/moul.io/assh/pkg/commands/#ConnectionStats
{{.Stats.ConnectedAt}}                           //  2016-07-20 11:19:23.467900594 +0200 CEST
{{.Stats.WrittenBytes}}                          //  3613
{{.Stats.WrittenBytesHuman}}                     //  3.6kb
{{.Stats.DisconnectAt}}                          //  2016-07-20 11:19:29,520515792 +0200 CEST
{{.Stats.ConnectionDuration}}                    //  6.052615198s
{{.Stats.ConnectionDurationHuman}}               //  6s
{{.Stats.AverageSpeed}}                          //  596.933bps
{{.Stats.AverageSpeedHuman}}                     //  3.4kb/sBeforeConfigWrite is called just before assh rewrite the ~/.ssh/config file.
Example of Golang template variables:
{{.SSHConfigPath}}                               // ~/.ssh/configExec driver uses Golang's template system to execute a shell command
Usage: exec <binary> [args...]
defaults:
  Hooks:
    OnConnect: exec echo '{{.Host}}' | jq .
# executes: `echo '{"HostName":"localhost","Port":"22","User":"moul","ControlPersist":"yes",...}' | jq .
# which results in printing a pretty JSON of the host
# {
#   "HostName": "localhost",
#   "Port": "22",
#   "User": "moul",
#   "ControlPersist": "yes",
#   ...
# }defaults:
  Hooks:
    OnConnect: exec echo 'New SSH connection to {{.Host.Prototype}}.' | mail -s "SSH connection journal" [email protected]
# send an email with the connection prototypedefaults:
  Hooks:
    BeforeConfigWrite: exec cp {{.SSHConfigPath}} {{.SSHConfigPath}}.backup
# make a copy of ~/.ssh/config before being rewrittendefaults:
  Hooks:
    AfterConfigWrite: 'exec echo "# date: `date`" >> {{.SSHConfigPath}}'
# Append a comment with the compilation date to the generated ~/.ssh/config filedefaults:
  Hooks:
  AfterConfigWrite: 'exec cat /path/to/my/provider/generated/.ssh/config >> {{.SSHConfigPath}}'
# Append another .ssh/config file to the generated .ssh/config fileThe exec commands are blocking, a new driver for background tasks is planned. For now, you can run a job in background like this:
defaults:
  Hooks:
    OnConnect:
    - exec sleep 60 &
# execute the `sleep 60` command in background (non-blocking)
# if you quit your ssh connection, the process will continue in background.Write driver uses Golang's template system to write out data to stdout
Usage: write <line:string...>
defaults:
  Hooks:
    OnConnect:
    - write New SSH connection to {{.Host.Prototype}}.
# writes: "New SSH connection to [email protected]:22." on the terminal on connectiondefaults:
  Hooks:
    OnDisconnect:
    - "write SSH connection to {{.Host.Name}} closed, {{ .Stats.WrittenBytes }} bytes written in {{ .Stats.ConnectionDuration }} ({{ .Stats.AverageSpeed }})"
# writes: SSH connection to localhost closed, 40 bytes written.Notify driver uses Golang's template system to open Desktop notifications.
- Mac OS X: Built-in support
 - Linux: Depends on gnotifier
 - Windows: Not supported
 - BSD: Not supported
 
Usage: notify <line:string...>
defaults:
  Hooks:
    OnConnect: notify New SSH connection to {{.Host.Prototype}}.defaults:
  Hooks:
    OnDisconnect:
    - "notify SSH connection to {{.Host.Name}} closed, {{ .Stats.WrittenBytes }} bytes written in {{ .Stats.ConnectionDuration }} ({{ .Stats.AverageSpeed }})"assh now manages the ~/.ssh/config file, take care to keep a backup your ~/.ssh/config file.
~/.ssh/assh.yml is a YAML file containing:
- a 
hostsdictionary containing multiple HOST definitions - a 
defaultssection containing global flags - and an 
includessection containing path to other configuration files 
hosts:
  homer:
    # ssh homer ->  ssh 1.2.3.4 -p 2222 -u robert
    Hostname: 1.2.3.4
    User: robert
    Port: 2222
  bart:
    # ssh bart ->   ssh 5.6.7.8 -u bart           <- direct access
    #            or ssh 5.6.7.8/homer -u bart     <- using homer as a gateway
    Hostname: 5.6.7.8
    User: bart
    Gateways:
    - direct                   # tries a direct access first
    - homer                    # fallback on homer gateway
  maggie:
    # ssh maggie ->   ssh 5.6.7.8 -u maggie       <- direct access
    #              or ssh 5.6.7.8/homer -u maggie   <- using homer as a gateway
    User: maggie
    Inherits: bart             # inherits rules from "bart"
  bart-access:
    # ssh bart-access ->  ssh home.simpson.springfield.us -u bart
    Inherits:
    - bart-template
    - simpson-template
  lisa-access:
    # ssh lisa-access ->  ssh home.simpson.springfield.us -u lisa
    Inherits:
    - lisa-template
    - simpson-template
  marvin:
    # ssh marvin    -> ssh marvin    -p 23
    # ssh sad-robot -> ssh sad-robot -p 23
    # ssh bighead   -> ssh bighead   -p 23
    # aliases inherit everything from marvin, except hostname
    Port: 23
    Aliases:
    - sad-robot
    - bighead
  dolphin:
    # ssh dolphin   -> ssh dolphin -p 24
    # ssh ecco      -> ssh dolphin -p 24
    # same as above, but with fixed hostname
    Port: 24
    Hostname: dolphin
    Aliases: ecco
    RateLimit: 10M # 10Mbytes/second rate limiting
  schooltemplate:
    User: student
    IdentityFile: ~/.ssh/school-rsa
    ForwardX11: yes
  schoolgw:
    # ssh school ->   ssh gw.school.com -l student -o ForwardX11=no -i ~/.ssh/school-rsa
    Hostname: gw.school.com
    ForwardX11: no
    Inherits: schooltemplate
  "expanded-host[0-7]*":
    # ssh somehost2042 ->       ssh somehost2042.some.zone
    Hostname: "%h.some.zone"
  vm-*.school.com:
    # ssh vm-42.school.com ->   ssh vm-42.school.com/gw.school.com -l student -o ForwardX11=yes -i ~/.ssh/school-rsa
    Gateways: schoolgw
    Inherits: schooltemplate
    # do not automatically create `ControlPath` -> may result in error
    ControlMasterMkdir: true
  "*.shortcut1":
    ResolveCommand: /bin/sh -c "echo %h | sed s/.shortcut1/.my-long-domain-name.com/"
  "*.shortcut2":
    ResolveCommand: /bin/sh -c "echo $(echo %h | sed s/.shortcut2//).my-other-long-domain-name.com"
  "*.scw":
    # ssh toto.scw -> 1. dynamically resolves the IP address
    #                 2. ssh {resolved ip address} -u root -p 22 -o UserKnownHostsFile=null -o StrictHostKeyChecking=no
    # requires github.com/scaleway/scaleway-cli
    ResolveCommand: /bin/sh -c "scw inspect -f {{.PublicAddress.IP}} server:$(echo %h | sed s/.scw//)"
    User: root
    Port: 22
    UserKnownHostsFile: /dev/null
    StrictHostKeyChecking: no
  my-env-host:
    User: user-$USER
    Hostname: ${HOSTNAME}${HOSTNAME_SUFFIX}
templates:
  # Templates are similar to Hosts; you can inherit from them
  # but you cannot ssh to a template
  bart-template:
    User: bart
  lisa-template:
    User: lisa
  simpson-template:
    Host: home.simpson.springfield.us
defaults:
  # Defaults are applied to each hosts
  ControlMaster: auto
  ControlPath: ~/tmp/.ssh/cm/%h-%p-%r.sock
  ControlPersist: yes
  Port: 22
  User: bob
  Hooks:
    # Automatically backup ~/.ssh/config
    BeforeConfigWrite:
      - 'exec set -x; cp {{.SSHConfigPath}} {{.SSHConfigPath}}.bkp'
    AfterConfigWrite:
      # Concat another `ssh_config` file with the one just generated by `assh`
      - 'exec cat ~/.ssh/my-heroku-generated-config >> {{.SSHConfigPath}}'
      # Alert me with a Desktop notification
      - notify "{{.SSHConfigPath}} has been rewritten"
    OnConnect:
      # Log internal information to a file
      - exec printf '{{.}}' | jq . >> ~/.ssh/last_connected_host.txt
      # Alert me with a Desktop notification
      - notify New SSH connection to {{.Host.Prototype}} at {{.Stats.ConnectedAt}}
      # Write the host prototype to the terminal stderr
      - write New SSH connection to {{.Host.Prototype}}
    OnDisconnect:
      # write on terminal and in a Desktop notification some statistics about the finished connection
      - "write  SSH connection to {{.Host.HostName}} closed, {{.Stats.WrittenBytes }} bytes written in {{.Stats.ConnectionDuration}} ({{.Stats.AverageSpeed}}bps)"
      - "notify SSH connection to {{.Host.HostName}} closed, {{.Stats.WrittenBytes }} bytes written in {{.Stats.ConnectionDuration}} ({{.Stats.AverageSpeed}}bps)"
includes:
- ~/.ssh/assh.d/*.yml
- /etc/assh.yml
- $ENV_VAR/blah-blah-*/*.yml
ASSHBinaryPath: ~/bin/assh  # optionally set the path of asshFor further inspiration, these assh.yml files on public GitHub projects can educate you on how people are using assh
assh usage
NAME:
   assh - advanced ssh config
USAGE:
   assh [global options] command [command options] [arguments...]
VERSION:
2.8.0 (HEAD)
AUTHOR(S):
   Manfred Touron <https://github.com/moul/assh>
COMMANDS:
   ping          Send packets to the SSH server and display statistics
   info          Display system-wide information
   config        Manage ssh and assh configuration
   sockets       Manage control sockets
   help, h       Shows a list of commands or help for one command
GLOBAL OPTIONS:
  --config value, -c value       Location of config file (default: "~/.ssh/assh.yml") [$ASSH_CONFIG]
  --debug, -D                    Enable debug mode [$ASSH_DEBUG]
  --verbose, -V                  Enable verbose mode
  --help, -h                     show help
  --version, -v                  print the version
Rewrites and replaces the existing ~/.ssh/config file.
This action is automatically done by assh when detecting configuration changes. Running this command is useful to set up assh or repair the configuration file.
$ assh config build > ~/.ssh/configList hosts and options.
$ assh config list
Listing entries
    *.scw -> root@[hostname_not_specified]:22
        StrictHostKeyChecking=no [custom options]
        UserKnownHostsFile=/dev/null [custom options]
    *.shortcut1 -> bob@[hostname_not_specified]:22
    *.shortcut2 -> bob@[hostname_not_specified]:22
    bart -> [email protected]:22
    bart-access -> bob@[hostname_not_specified]:22
    dolphin -> bob@dolphin:24
    expanded-host[0-7]* -> bob@%h.some.zone:22
    homer -> [email protected]:2222
    lisa-access -> bob@[hostname_not_specified]:22
    maggie -> maggie@[hostname_not_specified]:22
    marvin -> bob@[hostname_not_specified]:23
    my-env-host -> user-moul@[hostname_not_specified]:22
    schoolgw -> [email protected]:22
        ForwardX11=no [custom options]
    schooltemplate -> student@[hostname_not_specified]:22
        ForwardX11=yes [custom options]
        IdentityFile=~/.ssh/school-rsa [custom options]
    vm-*.school.com -> bob@[hostname_not_specified]:22
    (*) General options:
        ControlMaster: auto
        ControlPath: ~/tmp/.ssh/cm/%h-%p-%r.sock
        ControlPersist: yes
        Port: 22
        User: bobGenerate a graphviz graph of the hosts
$ assh config graphviz | dot -Tpng > assh-hosts.pngSearch for <keyword> in hosts and host options.
$ assh config search bart
Listing results for bart:
    bart -> [email protected]:22
    bart-access -> moul@[hostname_not_specified]:22Display system-wide information.
$ assh info
Debug mode (client): false
CLI Path: /path/to/assh
Go version: go1.6.2
OS/Arch: darwin/amd64
RC files:
- ~/.ssh/assh.yml
- ~/.ssh/assh.d/hosts.yml
- ~/.ssh/assh.d/moul.yml
- ~/.ssh/assh.d/test.yml
Statistics:
- 299 hosts
- 2 templates
- 4 included filesList active control sockets.
$ assh sockets list
4 active control sockets in "~/.ssh/cm/":
- bart/homer/lisa-22-root.sock (14 minutes)
- bart/homer-22-root.sock (14 minutes)
- bart-22-root.sock (14 minutes)
- marge-22-bart.sock (1 hour)Close active control sockets.
$ assh sockets flush
Closed 4 control sockets.Create a master control sockets.
$ assh sockets masterSend packets to the SSH server and display stats.
$ assh ping -c 4 localhost
PING localhost (127.0.0.1) PORT 22 (ssh) PROTO tcp
Connected to 127.0.0.1: seq=0 time=321µs protocol=tcp port=22
Connected to 127.0.0.1: seq=1 time=501µs protocol=tcp port=22
Connected to 127.0.0.1: seq=2 time=550µs protocol=tcp port=22
Connected to 127.0.0.1: seq=3 time=641µs protocol=tcp port=22
--- localhost assh ping statistics ---
4 packets transmitted, 4 packets received, 0.00% packet loss
round-trip min/avg/max = 321µs/503.25µs/641µsGet the latest version using GO (recommended way):
go install moul.io/assh/v2@latestnote: tested with Go1.7 or above
Get the latest released version using homebrew (Mac OS X):
brew install asshBuild the latest version
brew install assh --HEADGet a released version on: https://github.com/moul/assh/releases
Install with asdf-vm:
asdf plugin add assh
asdf install assh latest
asdf global assh latestTo improve experience when using advanced pattern matching, add the following at the end of your .bashrc / .zshrc / config.fish:
alias ssh="assh wrapper ssh --"This step is not mandatory but highly recommended.
Note: ssh does not understand advanced patterns;
To bypass this limitation, assh maintains a list of known hosts and regenerate the ~/.ssh/config with all those expanded known hosts.
Without the wrapper, the ~/.ssh/config risks to be outdated when connecting to a new host for the first time and you will need to launch the command again.
With the wrapper, ssh will always be called with an updated ~/.ssh/config file.
- Backup your old 
~/.ssh/config:cp ~/.ssh/config ~/.ssh/config.backup - Create a new 
~/.ssh/assh.ymlfile - Run 
assh config build > ~/.ssh/configto validate the syntax of your~/.ssh/assh.ymlfile and automatically build your~/.ssh/configfile - You are ready!
 
assh contains an experimental web application hosted on heroku: https://assh.herokuapp.com/
Convert an assh.yml file to ssh_config format:
$ http --form POST https://assh-dev.herokuapp.com/assh-to-ssh assh_config=@~/.ssh/assh.d/test.yml | jq -r .ssh_config
# This file was automatically generated by assh v2.8.0
# on 2018-07-03 21:06:56 +0000 UTC, based on ~/.ssh/assh.yml
#
# more info: https://github.com/moul/assh
# host-based configuration
Host *.scw
  Port 22
  StrictHostKeyChecking no
  User root
  UserKnownHostsFile /dev/null
  # ResolveCommand: /bin/sh -c "scw inspect -f {{.PublicAddress.IP}} server:$(echo %h | sed s/.scw//)"
Host lalala
  Port 22
  User moul
  # HostName: 127.0.0.1
Host toto[0-5]toto
  User samantha
# global configuration
Host *
  ProxyCommand assh connect --port=%p %hExperimental: assh may run in Docker, however you will have limitations:
- The 
asshcontainers does not have any binaries exceptassh, you can't useProxyCommand,ResolveCommand... - Docker may run on another host, 
ssh localhostwill ssh to Docker host 
docker run -it --rm -v ~/.ssh:/.ssh moul/assh --helpassh in Docker is slower and has more limitations, but it may be useful for testing or if you plan to use a Docker host as a remote Gateway
- v1 (2009-2015) - The original implementation. It worked quite well, but was a lot slower, less portable, harder to install for the user and harder to work on to develop new features and fix bugs
 
assh uses the built-in netcat mode of OpenSSH (shipped with OpenSSH 5.4) by default.
If your ssh client doesn't support this feature, you can configure a custom ProxyCommand configuration, i.e.,
hosts:
  myserver:
    host: 1.2.3.4
    gateways: mygateway
    # configure a custom proxycommand
    proxycommand: /bin/nc %h %p
  mygateway:
    host: 5.6.7.8You can configure this rule globally:
defaults:
  proxycommand: nc %h %pAlso, be sure to have netcat installed on your system, or use an alternative proxy binary, i.e., socat.
assh resolves hostnames using the system built-in resolver, depending on the OS, you can enable new features and/or change modules order.
- Linux - nsswitch documentation
 - Linux - mDNS support (nss-mdns)
 - Mac OS X - 
/etc/resolv.confdocumentation 
Starting with OpenSSH v6.7 the socket name can be shortened by configuring %C for the name expansion.
defaults:
  ControlPath: ~/tmp/.ssh/cm/%C.sock%C is a unique identifier based on a hash of the tuple of (local host, remote user, hostname, port).
Each time you call ssh, assh will check if the generated ~/.ssh/config file is outdated.
By default, it will transparently regenerate the configuration file if needed.
You can disable this behavior by generating the configuration file like this:
assh config build --no-automatic-rewriteIn your ansible.cfg under ssh_connection, make sure you have the following, changing the path to your assh:
[ssh_connection]
ansible_ssh_executable = '/usr/local/bin/assh wrapper ssh'
- ansible-dotfiles-assh: Ansible - Configure SSH with ASSH
 - appflow: Multitenant environment automation
 
This project exists thanks to all the people who contribute. [Contribute].
Become a financial contributor and help us sustain our community. [Contribute]
Support this project with your organization. Your logo will show up here with a link to your website. [Contribute]
© 2009-2021 Manfred Touron - MIT License



