2015-06-17 16:39:49 +00:00
package command
import (
"fmt"
2015-06-18 00:33:03 +00:00
"io/ioutil"
2015-06-24 22:13:12 +00:00
"net"
2015-06-18 00:33:03 +00:00
"os"
"os/exec"
2015-07-29 18:21:36 +00:00
"os/user"
2015-06-17 16:39:49 +00:00
"strings"
2015-07-02 23:00:14 +00:00
2015-07-23 21:20:28 +00:00
"github.com/hashicorp/vault/builtin/logical/ssh"
2015-06-17 16:39:49 +00:00
)
2015-07-27 20:42:03 +00:00
// SSHCommand is a Command that establishes a SSH connection
// with target by generating a dynamic key
2015-07-01 15:58:49 +00:00
type SSHCommand struct {
2015-06-17 16:39:49 +00:00
Meta
}
2015-07-01 15:58:49 +00:00
func ( c * SSHCommand ) Run ( args [ ] string ) int {
2015-08-12 17:30:50 +00:00
var role , port , mountPoint string
2015-07-29 18:21:36 +00:00
var noExec bool
2015-07-23 21:20:28 +00:00
var sshCmdArgs [ ] string
var sshDynamicKeyFileName string
2015-06-30 02:00:08 +00:00
flags := c . Meta . FlagSet ( "ssh" , FlagSetDefault )
2015-06-26 18:08:03 +00:00
flags . StringVar ( & role , "role" , "" , "" )
2015-07-06 20:56:45 +00:00
flags . StringVar ( & port , "port" , "22" , "" )
2015-08-12 17:30:50 +00:00
flags . StringVar ( & mountPoint , "mount-point" , "ssh" , "" )
2015-07-29 18:21:36 +00:00
flags . BoolVar ( & noExec , "no-exec" , false , "" )
2015-06-17 16:39:49 +00:00
flags . Usage = func ( ) { c . Ui . Error ( c . Help ( ) ) }
if err := flags . Parse ( args ) ; err != nil {
return 1
}
2015-06-26 18:08:03 +00:00
args = flags . Args ( )
if len ( args ) < 1 {
c . Ui . Error ( "ssh expects at least one argument" )
return 2
}
2015-06-30 02:00:08 +00:00
2015-06-17 16:39:49 +00:00
client , err := c . Client ( )
if err != nil {
c . Ui . Error ( fmt . Sprintf ( "Error initializing client: %s" , err ) )
return 2
}
2015-07-27 20:42:03 +00:00
2015-06-24 22:13:12 +00:00
input := strings . Split ( args [ 0 ] , "@" )
2015-07-29 18:21:36 +00:00
var username string
var ipAddr string
if len ( input ) == 1 {
u , err := user . Current ( )
if err != nil {
2015-08-06 19:50:12 +00:00
c . Ui . Error ( fmt . Sprintf ( "Error fetching username: %s" , err ) )
2015-07-29 18:21:36 +00:00
}
username = u . Username
ipAddr = input [ 0 ]
} else if len ( input ) == 2 {
username = input [ 0 ]
ipAddr = input [ 1 ]
} else {
2015-07-27 20:42:03 +00:00
c . Ui . Error ( fmt . Sprintf ( "Invalid parameter: %s" , args [ 0 ] ) )
return 2
}
2015-07-29 18:21:36 +00:00
ip , err := net . ResolveIPAddr ( "ip" , ipAddr )
2015-06-30 02:00:08 +00:00
if err != nil {
c . Ui . Error ( fmt . Sprintf ( "Error resolving IP Address: %s" , err ) )
return 2
}
if role == "" {
2015-08-12 17:30:50 +00:00
role , err = c . defaultRole ( mountPoint , ip . String ( ) )
2015-06-30 02:00:08 +00:00
if err != nil {
2015-08-06 19:50:12 +00:00
c . Ui . Error ( fmt . Sprintf ( "Error setting default role: %s" , err ) )
2015-06-30 02:00:08 +00:00
return 1
}
2015-08-06 21:00:50 +00:00
c . Ui . Output ( fmt . Sprintf ( "Vault SSH: Role: %s" , role ) )
2015-06-30 02:00:08 +00:00
}
2015-06-24 22:13:12 +00:00
data := map [ string ] interface { } {
"username" : username ,
2015-06-30 02:00:08 +00:00
"ip" : ip . String ( ) ,
2015-06-24 22:13:12 +00:00
}
2015-07-27 20:42:03 +00:00
2015-08-12 17:30:50 +00:00
keySecret , err := client . SSHWithMountPoint ( mountPoint ) . Credential ( role , data )
2015-06-17 16:39:49 +00:00
if err != nil {
2015-06-30 02:00:08 +00:00
c . Ui . Error ( fmt . Sprintf ( "Error getting key for SSH session:%s" , err ) )
2015-06-17 16:39:49 +00:00
return 2
}
2015-06-30 02:00:08 +00:00
2015-07-29 18:21:36 +00:00
if noExec {
c . Ui . Output ( fmt . Sprintf ( "IP:%s\nUsername: %s\nKey:%s\n" , ip . String ( ) , username , keySecret . Data [ "key" ] ) )
return 0
}
2015-07-23 21:20:28 +00:00
if keySecret . Data [ "key_type" ] . ( string ) == ssh . KeyTypeDynamic {
sshDynamicKey := string ( keySecret . Data [ "key" ] . ( string ) )
if len ( sshDynamicKey ) == 0 {
c . Ui . Error ( fmt . Sprintf ( "Invalid key" ) )
return 2
}
sshDynamicKeyFileName = fmt . Sprintf ( "vault_temp_file_%s_%s" , username , ip . String ( ) )
err = ioutil . WriteFile ( sshDynamicKeyFileName , [ ] byte ( sshDynamicKey ) , 0600 )
sshCmdArgs = append ( sshCmdArgs , [ ] string { "-i" , sshDynamicKeyFileName } ... )
2015-07-02 23:00:14 +00:00
2015-07-23 21:20:28 +00:00
} else if keySecret . Data [ "key_type" ] . ( string ) == ssh . KeyTypeOTP {
2015-08-06 21:00:50 +00:00
sshpassPath , err := exec . LookPath ( "sshpass" )
if err == nil {
sshCmdArgs = append ( sshCmdArgs , [ ] string { "-p" , string ( keySecret . Data [ "key" ] . ( string ) ) , "ssh" , "-p" , port } ... )
sshCmdArgs = append ( sshCmdArgs , args ... )
sshCmd := exec . Command ( sshpassPath , sshCmdArgs ... )
sshCmd . Stdin = os . Stdin
sshCmd . Stdout = os . Stdout
err = sshCmd . Run ( )
if err != nil {
c . Ui . Error ( fmt . Sprintf ( "Error while running ssh command:%s" , err ) )
}
return 0
}
c . Ui . Output ( fmt . Sprintf ( "OTP for the session is %s\n[Note: Install 'sshpass' to automate typing in OTP]\n" , string ( keySecret . Data [ "key" ] . ( string ) ) ) )
2015-07-23 21:20:28 +00:00
} else {
2015-07-27 20:42:03 +00:00
c . Ui . Error ( "Error creating key" )
2015-07-23 21:20:28 +00:00
}
sshCmdArgs = append ( sshCmdArgs , [ ] string { "-p" , port } ... )
2015-07-10 15:56:14 +00:00
sshCmdArgs = append ( sshCmdArgs , args ... )
sshCmd := exec . Command ( "ssh" , sshCmdArgs ... )
sshCmd . Stdin = os . Stdin
sshCmd . Stdout = os . Stdout
err = sshCmd . Run ( )
2015-06-18 00:33:03 +00:00
if err != nil {
2015-07-02 23:00:14 +00:00
c . Ui . Error ( fmt . Sprintf ( "Error while running ssh command:%s" , err ) )
2015-06-18 00:33:03 +00:00
}
2015-07-23 21:20:28 +00:00
if keySecret . Data [ "key_type" ] . ( string ) == ssh . KeyTypeDynamic {
err = os . Remove ( sshDynamicKeyFileName )
if err != nil {
2015-07-29 18:21:36 +00:00
c . Ui . Error ( fmt . Sprintf ( "Error deleting key file: %s" , err ) )
2015-07-23 21:20:28 +00:00
}
2015-07-06 15:05:02 +00:00
}
2015-07-29 18:21:36 +00:00
err = client . Sys ( ) . Revoke ( keySecret . LeaseID )
2015-07-06 15:05:02 +00:00
if err != nil {
2015-07-29 18:21:36 +00:00
c . Ui . Error ( fmt . Sprintf ( "Error revoking the key: %s" , err ) )
2015-06-18 00:33:03 +00:00
}
2015-07-02 23:00:14 +00:00
2015-06-17 16:39:49 +00:00
return 0
}
2015-07-29 18:21:36 +00:00
// If user did not provide the role with which SSH connection has
// to be established and if there is only one role associated with
// the IP, it is used by default.
2015-08-12 17:30:50 +00:00
func ( c * SSHCommand ) defaultRole ( mountPoint , ip string ) ( string , error ) {
2015-07-02 23:00:14 +00:00
data := map [ string ] interface { } {
"ip" : ip ,
}
2015-07-29 18:21:36 +00:00
client , err := c . Client ( )
if err != nil {
return "" , err
}
2015-08-12 17:30:50 +00:00
secret , err := client . Logical ( ) . Write ( mountPoint + "/lookup" , data )
2015-07-02 23:00:14 +00:00
if err != nil {
2015-08-06 19:50:12 +00:00
return "" , fmt . Errorf ( "Error finding roles for IP %s: %s" , ip , err )
2015-07-02 23:00:14 +00:00
}
if secret == nil {
2015-08-06 19:50:12 +00:00
return "" , fmt . Errorf ( "Error finding roles for IP %s: %s" , ip , err )
2015-07-02 23:00:14 +00:00
}
if secret . Data [ "roles" ] == nil {
2015-08-06 19:50:12 +00:00
return "" , fmt . Errorf ( "No matching roles found for IP %s" , ip )
2015-07-02 23:00:14 +00:00
}
if len ( secret . Data [ "roles" ] . ( [ ] interface { } ) ) == 1 {
return secret . Data [ "roles" ] . ( [ ] interface { } ) [ 0 ] . ( string ) , nil
} else {
2015-07-29 18:21:36 +00:00
var roleNames string
for _ , item := range secret . Data [ "roles" ] . ( [ ] interface { } ) {
roleNames += item . ( string ) + ", "
}
roleNames = strings . TrimRight ( roleNames , ", " )
2015-08-06 19:50:12 +00:00
return "" , fmt . Errorf ( "IP %s has multiple roles.\nSelect a role using '-role' option.\nPossible roles: [%s]\nNote that all roles may not be permitted, based on ACLs." , ip , roleNames )
2015-07-02 23:00:14 +00:00
}
2015-06-24 22:13:12 +00:00
}
2015-07-01 15:58:49 +00:00
func ( c * SSHCommand ) Synopsis ( ) string {
2015-06-17 16:39:49 +00:00
return "Initiate a SSH session"
}
2015-07-01 15:58:49 +00:00
func ( c * SSHCommand ) Help ( ) string {
2015-06-17 16:39:49 +00:00
helpText := `
2015-07-02 21:23:09 +00:00
Usage : vault ssh [ options ] username @ ip
Establishes an SSH connection with the target machine .
2015-07-27 20:42:03 +00:00
This command generates a key and uses it to establish an SSH
connection with the target machine . This operation requires
2015-07-02 21:23:09 +00:00
that SSH backend is mounted and at least one ' role ' be registed
with vault at priori .
2015-07-29 18:21:36 +00:00
For setting up SSH backends with one - time - passwords , installation
of agent in target machines is required .
See [ https : //github.com/hashicorp/vault-ssh-agent]
2015-07-02 21:23:09 +00:00
General Options :
` + generalOptionsUsage() + `
SSH Options :
2015-07-29 18:21:36 +00:00
- role Role to be used to create the key .
2015-07-02 21:23:09 +00:00
Each IP is associated with a role . To see the associated
roles with IP , use "lookup" endpoint . If you are certain that
there is only one role associated with the IP , you can
skip mentioning the role . It will be chosen by default .
2015-07-29 18:21:36 +00:00
If there are no roles associated with the IP , register
2015-07-02 21:23:09 +00:00
the CIDR block of that IP using the "roles/" endpoint .
2015-07-10 15:56:14 +00:00
- port Port number to use for SSH connection . This defaults to port 22.
2015-07-29 18:21:36 +00:00
- no - exec Shows the credentials but does not establish connection .
2015-08-12 17:30:50 +00:00
- mount - point Mount point of SSH backend . If the backend is mounted at
2015-07-29 18:21:36 +00:00
' ssh ' , which is the default as well , this parameter can
be skipped .
2015-07-02 21:23:09 +00:00
`
2015-06-17 16:39:49 +00:00
return strings . TrimSpace ( helpText )
}