-
Notifications
You must be signed in to change notification settings - Fork 0
/
main.go
333 lines (288 loc) · 11.7 KB
/
main.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
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
package main
import (
"crypto/tls"
"crypto/x509"
"dhes/emissary/daemon/cmd/utils"
"flag"
"fmt"
"io"
"log/slog"
"net"
"os"
"strconv"
"strings"
"sync"
"time"
)
var (
outboundService = flag.String("outbound", "", "Local service to proxy (e.g., localhost:5432)")
serviceName = flag.String("service-name", "", "Name of the service to register with Drawbridge")
)
var certificatesAndKeysFolderName = "put_certificates_and_key_from_drawbridge_here"
// Drawbridge Protocol
// Used by Drawbridge and Emissary in TCP connections to request and route to the desired
// Protected Service application.
const (
// Used by Emissary to tell Drawbridge which Protected Service it wants to connect to.
// e.g PS_CONN My Minecraft Server
ProtectedServiceConnection = "PS_CONN"
// Used to get list of service names for use by Emissary client to tell Drawbridge what Protected Service it wants to connect to.
ProtectedServicesList = "PS_LIST"
)
const (
OutboundConnectionCreate = "OB_CR8T"
OutboundConnection = "OB_CONN"
)
func main() {
flag.Parse()
fmt.Println("Emissary is starting...")
certificatesAndKeysFolderPath := utils.CreateEmissaryFileReadPath(certificatesAndKeysFolderName)
_, err := os.Stat(certificatesAndKeysFolderPath)
if os.IsNotExist(err) {
runOnboarding()
}
// A Drawbridge admin can create an "Emissary Bundle" which will contain certificate and drawbridge server info
// to remove the need for a user to configure Emissary manually.
emissaryBundle := getDrawbridgeAddress()
slog.Debug("Emissary is trying to read the Certificate file...")
if !utils.FileExists("./put_certificates_and_key_from_drawbridge_here/emissary-mtls-tcp.crt") {
message := fmt.Sprintf("The \"emissary-mtls-tcp.crt\" file is missing from the \"%s\" folder, which should be next to this program.\n", certificatesAndKeysFolderName)
message += "To generate this file, please request an Emissary Bundle from your Drawbridge admin.\n"
utils.PrintFinalError(message, nil)
}
slog.Debug("Emissary is trying to read the Key file...")
if !utils.FileExists("./put_certificates_and_key_from_drawbridge_here/emissary-mtls-tcp.key") {
message := fmt.Sprintf("The \"emissary-mtls-tcp.key\" file is missing from the \"%s\" folder, which should be next to this program.\n", certificatesAndKeysFolderName)
message += "To generate this file, please request an Emissary Bundle from your Drawbridge admin.\n"
utils.PrintFinalError(message, nil)
}
slog.Debug("Emissary is trying to read the CA Certificate file...")
if !utils.FileExists("./put_certificates_and_key_from_drawbridge_here/ca.crt") {
message := fmt.Sprintf("The \"ca.crt\" file is missing from the \"%s\" folder, which should be next to this program.\n", certificatesAndKeysFolderName)
message += "To generate this file, please request an Emissary Bundle from your Drawbridge admin.\n"
utils.PrintFinalError(message, nil)
}
slog.Debug("Emissary is trying to load Certificate and Key file for connecting to Drawbridge...")
// load tls configuration
mTLSCertificatePath := utils.CreateEmissaryFileReadPath("./put_certificates_and_key_from_drawbridge_here/emissary-mtls-tcp.crt")
mTLSKeyPath := utils.CreateEmissaryFileReadPath("./put_certificates_and_key_from_drawbridge_here/emissary-mtls-tcp.key")
cert, err := tls.LoadX509KeyPair(mTLSCertificatePath, mTLSKeyPath)
if err != nil {
utils.PrintFinalError("", err)
}
// Configure the client to trust TLS server certs issued by a CA.
certPool, err := x509.SystemCertPool()
if err != nil {
utils.PrintFinalError("", err)
}
drawbridgeCAPath := utils.CreateEmissaryFileReadPath("./put_certificates_and_key_from_drawbridge_here/ca.crt")
if caCertPEM, err := os.ReadFile(drawbridgeCAPath); err != nil {
utils.PrintFinalError("", err)
} else if ok := certPool.AppendCertsFromPEM(caCertPEM); !ok {
utils.PrintFinalError("invalid cert in CA PEM", nil)
}
tlsConfig := &tls.Config{
RootCAs: certPool,
Certificates: []tls.Certificate{cert},
}
var drawbridgeAddress string
if emissaryBundle == nil {
fmt.Println("Please enter your Drawbridge server URL or IP (e.g drawbridge.mysite.com:3100 or 50.162.50.224:3100):")
fmt.Println("Please note the default Drawbridge reverse proxy port is 3100.")
fmt.Print("Drawbridge server URL or IP: ")
fmt.Scan(&drawbridgeAddress)
fmt.Println()
} else {
fmt.Printf("Connecting to Drawbridge server from local Emissary Bundle at %s...\n\n", *emissaryBundle)
drawbridgeAddress = *emissaryBundle
}
if *outboundService != "" {
slog.Info("Attempting Outbound Mode...")
if *serviceName == "" {
utils.PrintFinalError("Service name must be provided when using outbound mode", nil)
}
runOutboundProxy(*outboundService, *serviceName, drawbridgeAddress, tlsConfig)
utils.PrintFinalError("Outbound Proxy Failed", fmt.Errorf("connection to Drawbridge has ceased"))
}
serviceNames := getProtectedServiceNames(drawbridgeAddress, tlsConfig)
runningProxies := make(map[string]net.Listener, 0)
// TODO
// dont run this print unless we were able to get at least one service from Drawbridge.
fmt.Println("The following Protected Services are available:")
port := 3200
for _, service := range serviceNames {
go setUpLocalSeviceProxies(service, runningProxies, drawbridgeAddress, tlsConfig, port)
if err != nil {
utils.PrintFinalError("error setting up local proxies to Drawbridge Protected Resources", err)
}
}
var exitCommand string
fmt.Scan(&exitCommand)
}
func runOutboundProxy(localService, serviceName, drawbridgeAddress string, tlsConfig *tls.Config) error {
for {
// Connect to Drawbridge
drawbridgeConn, err := establishConnection(drawbridgeAddress, tlsConfig)
if err != nil {
slog.Error("Failed to connect to Drawbridge, retrying in 5 seconds", "error", err)
time.Sleep(5 * time.Second)
continue
}
// Register the service with Drawbridge
registerMsg := fmt.Sprintf("%s %s", OutboundConnectionCreate, serviceName)
if _, err := drawbridgeConn.Write([]byte(registerMsg)); err != nil {
slog.Error("Failed to register service with Drawbridge", "error", err)
drawbridgeConn.Close()
continue
}
// Wait for acknowledgement
buf := make([]byte, 1024)
n, err := drawbridgeConn.Read(buf)
if err != nil || string(buf[:n]) != "ACK" {
slog.Error("Failed to receive acknowledgement from Drawbridge", "error", err)
drawbridgeConn.Close()
continue
}
slog.Info("Registered outbound service", "service", serviceName, "localAddress", localService)
// Handle the connection
handleOutboundConnection(drawbridgeConn, localService)
// Close the Drawbridge connection after handling
drawbridgeConn.Close()
}
}
func handleOutboundConnection(drawbridgeConn net.Conn, localService string) {
defer drawbridgeConn.Close()
// Connect to the local service
localConn, err := net.Dial("tcp", localService)
if err != nil {
slog.Error("Failed to connect to local service", "error", err)
return
}
defer localConn.Close()
proxyData(drawbridgeConn, localConn)
slog.Info("Connection closed")
}
func runOnboarding() {
fmt.Println("\n* * * * * * * * * * * *")
fmt.Println(" Welcome to Emissary!")
fmt.Println("* * * * * * * * * * * *")
fmt.Println("\nFIRST TIME SETUP INSTRUCTIONS:")
fmt.Println("If you're seeing this, you aren't using an Emissary Bundle or deleted your bundle and put_certificates_and_keys_here folder and files.")
utils.PrintFinalError("Reach out to your Drawbridge admin and ask for one :)", nil)
}
// We need to request the list of services from Drawbridge via a TCP call.
// It doesn't _have_ to be a TCP call, but we don't need to overhead of HTTP for this, I don't think.
// And at the end of the day we need to write to our connection to Drawbridge later with the name of the service we want to connect to.
func getProtectedServiceNames(drawbridgeAddress string, tlsConfig *tls.Config) []string {
conn, err := establishConnection(drawbridgeAddress, tlsConfig)
if err != nil {
slog.Error("Drawbridge Connection Failed - Retrying in 5 seconds")
fiveSecondsFromNow := time.Until(time.Now().Add(time.Second * 5))
time.AfterFunc(fiveSecondsFromNow, func() {
getProtectedServiceNames(drawbridgeAddress, tlsConfig)
})
return nil
}
defer conn.Close()
// Get list of Protected Services.
conn.Write([]byte(ProtectedServicesList))
// Read incoming data
buf := make([]byte, 2000)
_, err = conn.Read(buf)
if err != nil {
fmt.Println(err)
return nil
}
intermediateString := strings.Split(string(buf[:]), ":")
serviceNames := strings.Split(intermediateString[1], ",")
return serviceNames[:len(serviceNames)-1]
}
// Since we multiplex services over the only Drawbridge port, we need a way to tell Drawbridge which service we want to connect to.
// We can do this by exposing a port locally for each service seperately, and when we connect to each proxy, we can use the
// proxy port to ma to the service name, and request to connect to that service when we are talking to Drawbridge.
func setUpLocalSeviceProxies(protectedServiceString string, localServiceProxies map[string]net.Listener, drawbridgeAddress string, tlsConfig *tls.Config, port int) {
protectedServiceString = strings.TrimSpace(protectedServiceString)
// This is the id of the service in Drawbridge.
// It is used
portOffset, err := strconv.Atoi(protectedServiceString[1:3])
protectedServiceName := protectedServiceString[3:]
if err != nil {
utils.PrintFinalError("Error parsing protected service string: %w", err)
}
localServiceProxyPort := port + portOffset
hostAndPort := fmt.Sprintf("127.0.0.1:%d", localServiceProxyPort)
l, err := net.Listen("tcp", hostAndPort)
if err != nil {
utils.PrintFinalError("Emissary was unable to start the local proxy server", err)
}
fmt.Printf("• \"%s\" on localhost:%d\n", protectedServiceName, localServiceProxyPort)
// Save the proxy listener for use later.
localServiceProxies[protectedServiceString] = l
defer l.Close()
for {
clientConn, err := l.Accept()
if err != nil {
slog.Error("Reverse proxy TCP Accept failed", err)
continue
}
go handleConnection(clientConn, drawbridgeAddress, tlsConfig, protectedServiceString)
}
}
func proxyData(dst net.Conn, src net.Conn) {
defer dst.Close()
defer src.Close()
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
_, err := io.Copy(dst, src)
if err != nil {
slog.Error("Failed to copy src to dst", "error", err)
}
dst.Close()
}()
go func() {
defer wg.Done()
_, err := io.Copy(src, dst)
if err != nil {
slog.Error("Failed to copy dst to src", "error", err)
}
src.Close()
}()
wg.Wait()
}
func handleConnection(clientConn net.Conn, drawbridgeAddress string, tlsConfig *tls.Config, protectedServiceString string) {
defer clientConn.Close()
drawbridgeConn, err := establishConnection(drawbridgeAddress, tlsConfig)
if err != nil {
slog.Error("Failed to connect to Drawbridge", "error", err)
return
}
defer drawbridgeConn.Close()
// Tell Drawbridge the name of the Protected Service we want to connect to.
protectedServiceConnectionMessage := fmt.Sprintf("%s %s", ProtectedServiceConnection, protectedServiceString)
_, err = drawbridgeConn.Write([]byte(protectedServiceConnectionMessage))
if err != nil {
slog.Error("Error sending Protected Service connection message", "error", err)
return
}
proxyData(drawbridgeConn, clientConn)
}
func establishConnection(drawbridgeAddress string, tlsConfig *tls.Config) (net.Conn, error) {
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: 15 * time.Second}, "tcp", drawbridgeAddress, tlsConfig)
if err != nil {
slog.Error("Failed connecting to Drawbridge mTLS TCP server", err)
return nil, err
}
slog.Info("Connected to Drawbridge!")
return conn, nil
}
func getDrawbridgeAddress() *string {
bundleBytes := utils.ReadFile("./bundle/drawbridge.txt")
if bundleBytes != nil {
bundleData := strings.TrimSpace(string(*bundleBytes))
return &bundleData
} else {
return nil
}
}