/
ssh.go
178 lines (149 loc) · 4.28 KB
/
ssh.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
package ssh
import (
"errors"
"fmt"
"io"
"os"
"strings"
"github.com/pkg/sftp"
"golang.org/x/crypto/ssh"
)
// VaultSSHClient represents a Vault server and the operations available on it over an SSH Connection. For operations
// involving the Vault API, see the vault/api/VaultAPIClient interface instead
type VaultSSHClient interface {
// CheckOSArch returns the os type and architecture
CheckOSArch() (string, string, error)
// WriteFile writes a file to the SSH server, overwriting what's already there
WriteFile(sourceFile io.Reader, hostDestination string) error
// FileExists checks whether a file exists on a server over SSH
FileExists(filepath string) (bool, error)
// AddIPCLockCapabilityToFile attempts to call setcap over SSH to add IPC_LOCK capability to an executable. Requires
// sudo privileges
AddIPCLockCapabilityToFile(filename string) error
// IsIPCLockCapabilityOnFile calls getcap over SSH to check whether an executable has IPC_LOCK capability
IsIPCLockCapabilityOnFile(filename string) (bool, error)
// Close closes the underlying SSH connection
Close() error
}
type sshClient struct {
Client *ssh.Client
}
func NewClient(address, username, password string) (VaultSSHClient, error) {
config := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{ssh.Password(password)},
HostKeyCallback: ssh.InsecureIgnoreHostKey(),
}
conn, err := ssh.Dial("tcp", address, config)
if err != nil {
return nil, err
}
return &sshClient{conn}, nil
}
func (c *sshClient) CheckOSArch() (string, string, error) {
session, err := c.Client.NewSession()
if err != nil {
return "", "", err
}
defer session.Close()
output, err := session.Output("echo $(uname -s && uname -m)")
if err != nil {
return "", "", err
}
outputSplit := strings.Split(string(output), " ")
osType := strings.TrimSpace(outputSplit[0])
arch := strings.TrimSpace(outputSplit[1])
return osType, arch, nil
}
func (c *sshClient) WriteFile(sourceFile io.Reader, hostDestination string) error {
sftpClient, closeFunc, err := newSFTPClient(c.Client)
if err != nil {
return err
}
defer closeFunc()
// Delete file if it exists already, otherwise create a new file
dstFile, err := sftpClient.OpenFile(hostDestination, os.O_WRONLY|os.O_CREATE|os.O_TRUNC)
if err != nil {
if errors.Is(err, os.ErrPermission) {
return ErrNoPermissions
} else if errors.Is(err, os.ErrNotExist) {
return ErrNotFound
} else if strings.Contains(err.Error(), "SSH_FX_FAILURE") {
// FIXME: can this error occur for any other reasons?
return ErrFileBusy
}
return err
}
defer dstFile.Close()
_, err = io.Copy(dstFile, sourceFile)
if err != nil {
return err
}
err = makeFileExecutable(dstFile)
if err != nil {
return err
}
return nil
}
func (c *sshClient) FileExists(filepath string) (bool, error) {
sftpClient, closeFunc, err := newSFTPClient(c.Client)
if err != nil {
return false, err
}
defer closeFunc()
_, err = sftpClient.Stat(filepath)
if err != nil {
if os.IsNotExist(err) {
return false, nil
}
return false, err
}
return true, nil
}
func newSFTPClient(conn *ssh.Client) (*sftp.Client, func(), error) {
sftpClient, err := sftp.NewClient(conn)
if err != nil {
return nil, nil, err
}
closeConns := func() {
sftpClient.Close()
}
return sftpClient, closeConns, nil
}
func makeFileExecutable(file *sftp.File) error {
err := file.Chmod(0775)
if err != nil {
return err
}
return nil
}
func (c *sshClient) AddIPCLockCapabilityToFile(filename string) error {
session, err := c.Client.NewSession()
if err != nil {
return err
}
defer session.Close()
// TODO: is sudo there?
err = session.Run(fmt.Sprintf("sudo setcap cap_ipc_lock=ep %s", filename))
if err != nil {
return err
}
return nil
}
func (c *sshClient) IsIPCLockCapabilityOnFile(filename string) (bool, error) {
session, err := c.Client.NewSession()
if err != nil {
return false, err
}
defer session.Close()
output, err := session.Output(fmt.Sprintf("getcap %s", filename))
if err != nil {
return false, err
}
// Plugin currently is not "capability-aware" so the effective flag must be set for the capability, in addition to
// it being in the permitted set
return strings.Contains(string(output), "cap_ipc_lock+ep"), nil
}
func (c *sshClient) Close() error {
return c.Client.Close()
}