-
Notifications
You must be signed in to change notification settings - Fork 40
/
proxy_service.go
169 lines (141 loc) · 4.55 KB
/
proxy_service.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
package ssh
import (
"fmt"
"io/ioutil"
"net"
"os"
validation "github.com/go-ozzo/ozzo-validation"
"golang.org/x/crypto/ssh"
"github.com/cyberark/secretless-broker/internal"
"github.com/cyberark/secretless-broker/pkg/secretless/log"
"github.com/cyberark/secretless-broker/pkg/secretless/plugin/connector/tcp"
)
// An SSH ProxyService accepts SSH connections and MITMs them.
//
// NOTE: This MITM approach to SSH is experimental. The ssh-agent approach is
// better validated and probably better all-around.
type proxyService struct {
connector tcp.Connector
done bool
listener net.Listener
logger log.Logger
retrieveCredentials internal.CredentialsRetriever
}
// NewProxyService constructs a new instance of a SSH ProxyService. The
// constructor takes a Listener, Logger and CredentialResolver.
// A SSH ProxyService is able to Connect with Credentials then subsequently stream
// bytes between client and target service
func NewProxyService(
listener net.Listener,
logger log.Logger,
retrieveCredentials internal.CredentialsRetriever,
) (internal.Service, error) {
errors := validation.Errors{}
if retrieveCredentials == nil {
errors["retrieveCredentials"] = fmt.Errorf("retrieveCredentials cannot be nil")
}
if logger == nil {
errors["logger"] = fmt.Errorf("logger cannot be nil")
}
if listener == nil {
errors["listener"] = fmt.Errorf("listener cannot be nil")
}
if err := errors.Filter(); err != nil {
return nil, err
}
return &proxyService{
retrieveCredentials: retrieveCredentials,
listener: listener,
logger: logger,
done: false,
}, nil
}
func (proxy *proxyService) handleConnections(channels <-chan ssh.NewChannel) error {
var connector = ServiceConnector{
channels: channels,
logger: proxy.logger,
}
backendCredentials, err := proxy.retrieveCredentials()
defer internal.ZeroizeCredentials(backendCredentials)
if err != nil {
return fmt.Errorf("failed on retrieve credentials: %s", err)
}
return connector.Connect(backendCredentials)
}
// Start initiates the net.Listener to listen for incoming connections
// Listen accepts SSH connections and MITMs them using a ServiceConnector.
func (proxy *proxyService) Start() error {
logger := proxy.logger
logger.Infof("Starting service")
if proxy.done {
return fmt.Errorf("cannot call Start on stopped ProxyService")
}
go func() {
expectedHostKeyPath := "/tmp/id_rsa"
// Generate a host key if one isn't present in ./tmp/id_rsa
// TODO: Be able to use secretless.yml-provided host key
if _, err := os.Stat(expectedHostKeyPath); err != nil {
logger.Debugf("Could not find pre-existing host key at %s. Generating...", expectedHostKeyPath)
if err := _GenerateSSHKeys(expectedHostKeyPath); err != nil {
logger.Panicf("Failed to create host key: ", err)
}
}
serverConfig := &ssh.ServerConfig{
NoClientAuth: true,
PasswordCallback: func(c ssh.ConnMetadata, pass []byte) (*ssh.Permissions, error) {
return nil, nil
},
PublicKeyCallback: func(c ssh.ConnMetadata, pubKey ssh.PublicKey) (*ssh.Permissions, error) {
return nil, nil
},
}
privateBytes, err := ioutil.ReadFile(expectedHostKeyPath)
if err != nil {
logger.Panicf("Failed to load private key: ", err)
}
private, err := ssh.ParsePrivateKey(privateBytes)
if err != nil {
logger.Panicf("Failed to parse private key: ", err)
}
serverConfig.AddHostKey(private)
// TODO: is it possible to use the duplex func to stream ?
for !proxy.done {
nConn, err := proxy.listener.Accept()
if err != nil {
logger.Debugf("Failed on accept connection: %s", err)
return
}
// https://godoc.org/golang.org/x/crypto/ssh#NewServerConn
conn, chans, reqs, err := ssh.NewServerConn(nConn, serverConfig)
if err != nil {
logger.Debugf("Failed to handshake: %s", err)
continue
}
logger.Debugf(
"New connection accepted for user %s from %s",
conn.User(),
conn.RemoteAddr(),
)
// The incoming Request channel must be serviced.
go func() {
for req := range reqs {
logger.Debugf("Global SSH request : %v", req)
}
}()
go func() {
if err := proxy.handleConnections(chans); err != nil {
logger.Errorf("Failed on handle connection: %s", err)
return
}
logger.Infof("Connection closed on %v", conn.LocalAddr())
}()
}
}()
return nil
}
// Stop terminates proxyService by closing the listening net.Listener
func (proxy *proxyService) Stop() error {
proxy.logger.Infof("Stopping service")
proxy.done = true
return proxy.listener.Close()
}