Skip to content

Commit 677ba26

Browse files
committed
added support for proxying to multiple destinations within a single tunnel: Prometheus, ClickHouse, and Pyroscope
1 parent b0a1539 commit 677ba26

File tree

6 files changed

+175
-136
lines changed

6 files changed

+175
-136
lines changed

Dockerfile

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
FROM golang:1.17-stretch AS builder
1+
FROM golang:1.19-bullseye AS builder
22

3-
WORKDIR /go/src/promtun
3+
WORKDIR /go/src/coroot-connect
44

55
COPY go.mod .
66
COPY go.sum .
@@ -13,5 +13,5 @@ RUN CGO_ENABLED=0 go install -mod=readonly -ldflags "-X main.version=$VERSION" .
1313

1414
FROM scratch
1515
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
16-
COPY --from=builder /go/bin/promtun /promtun
17-
CMD ["/promtun"]
16+
COPY --from=builder /go/bin/coroot-connect /coroot-connect
17+
CMD ["/coroot-connect"]

README.md

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,42 +1,13 @@
1-
# Promtum
1+
# Coroot-connect
22

3-
Promtun is a tiny tool to establish a tunnel between a Prometheus server and Coroot cloud.
4-
5-
![](./schema.svg)
3+
Coroot-connect is a tool that establishes secure tunnels between the Coroot cloud and customers' clusters.
64

75
## Run
86

97
### Docker
108

11-
```bash
12-
docker run --detach --name coroot-promtun \
13-
-e PROMETHEUS_ADDRESS=<INTERNAL_PROMETHEUS_HOST_AND_PORT> \
14-
-e PROJECT_TOKEN=<COROOT_PROJECT_TOKEN> \
15-
ghcr.io/coroot/promtun
16-
```
9+
TDB
1710

1811
### Kubernetes
1912

20-
```yaml
21-
apiVersion: apps/v1
22-
kind: Deployment
23-
metadata:
24-
name: promtun
25-
namespace: coroot
26-
spec:
27-
selector:
28-
matchLabels: {app: promtun}
29-
replicas: 1
30-
template:
31-
metadata:
32-
labels: {app: promtun}
33-
spec:
34-
containers:
35-
- name: promtun
36-
image: ghcr.io/coroot/promtun
37-
env:
38-
- name: PROMETHEUS_ADDRESS
39-
value: <INTERNAL_PROMETHEUS_HOST_AND_PORT>
40-
- name: PROJECT_TOKEN
41-
value: <COROOT_PROJECT_TOKEN>
42-
```
13+
TBD

promtun.go renamed to connect.go

Lines changed: 81 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -26,24 +26,24 @@ var (
2626
backoffFactor = 2.
2727
backoffMin = 5 * time.Second
2828
backoffMax = time.Minute
29-
streamTimeout = time.Minute
29+
streamTimeout = 5 * time.Minute
3030
)
3131

3232
type Tunnel struct {
33-
promAddr string
34-
address string
35-
projectToken string
36-
serverName string
37-
cancelFn context.CancelFunc
38-
gwConn net.Conn
33+
address string
34+
serverName string
35+
token string
36+
config []byte
37+
cancelFn context.CancelFunc
38+
gwConn net.Conn
3939
}
4040

41-
func NewTunnel(address, serverName, promAddr, projectToken string) *Tunnel {
41+
func NewTunnel(address, serverName string, token string, config []byte) *Tunnel {
4242
t := &Tunnel{
43-
address: address,
44-
promAddr: promAddr,
45-
projectToken: projectToken,
46-
serverName: serverName,
43+
address: address,
44+
serverName: serverName,
45+
token: token,
46+
config: config,
4747
}
4848
var ctx context.Context
4949
ctx, t.cancelFn = context.WithCancel(context.Background())
@@ -59,15 +59,15 @@ func (t *Tunnel) keepConnected(ctx context.Context) {
5959
case <-ctx.Done():
6060
return
6161
default:
62-
t.gwConn, err = connect(t.address, t.serverName, t.projectToken)
62+
t.gwConn, err = connect(t.address, t.serverName, t.token, t.config)
6363
if err != nil {
6464
d := b.Duration()
6565
klog.Errorf("%s, reconnecting to %s in %.0fs", err, t.address, d.Seconds())
6666
time.Sleep(d)
6767
continue
6868
}
6969
b.Reset()
70-
proxy(ctx, t.gwConn, t.promAddr)
70+
proxy(ctx, t.gwConn)
7171
_ = t.gwConn.Close()
7272
}
7373
}
@@ -81,30 +81,40 @@ func (t *Tunnel) Close() {
8181
}
8282

8383
func main() {
84-
klog.Infof("version: %s", version)
8584
resolverUrl := os.Getenv("RESOLVER_URL")
8685
if resolverUrl == "" {
8786
resolverUrl = "https://gw.coroot.com/promtun/resolve"
8887
}
89-
promAddr := mustEnv("PROMETHEUS_ADDRESS")
90-
projectToken := mustEnv("PROJECT_TOKEN")
88+
token := mustEnv("PROJECT_TOKEN")
89+
if len(token) != 36 {
90+
klog.Exitln("invalid project token")
91+
}
92+
configPath := mustEnv("CONFIG_PATH")
9193

92-
u, err := url.Parse(resolverUrl)
94+
data, err := os.ReadFile(configPath)
9395
if err != nil {
94-
klog.Exitf("invalid RESOLVER_URL %s: %s", resolverUrl, err)
96+
klog.Exitln("failed to read config:", err)
9597
}
96-
serverName := u.Hostname()
98+
config := []byte(os.ExpandEnv(string(data)))
9799

98-
if err := pingProm(promAddr); err != nil {
99-
klog.Exitf("failed to ping prometheus: %s", err)
100+
klog.Infof("version: %s", version)
101+
102+
loop(token, resolverUrl, config)
103+
}
104+
105+
func loop(token, resolverUrl string, config []byte) {
106+
u, err := url.Parse(resolverUrl)
107+
if err != nil {
108+
klog.Exitf("invalid resolver URL %s: %s", resolverUrl, err)
100109
}
110+
tlsServerName := u.Hostname()
101111

102112
tunnels := map[string]*Tunnel{}
103113

104114
b := backoff.Backoff{Factor: backoffFactor, Min: backoffMin, Max: backoffMax}
105115
for {
106116
klog.Infof("updating gateways endpoints from %s", resolverUrl)
107-
endpoints, err := getEndpoints(resolverUrl, projectToken)
117+
endpoints, err := getEndpoints(resolverUrl, token)
108118
if err != nil {
109119
d := b.Duration()
110120
klog.Errorf("failed to get gateway endpoints: %s, retry in %.0fs", err, d.Seconds())
@@ -117,13 +127,13 @@ func main() {
117127
for _, e := range endpoints {
118128
fresh[e] = true
119129
if _, ok := tunnels[e]; !ok {
120-
klog.Infof("starting tunnel to %s", e)
121-
tunnels[e] = NewTunnel(e, serverName, promAddr, projectToken)
130+
klog.Infof("starting a tunnel to %s", e)
131+
tunnels[e] = NewTunnel(e, tlsServerName, token, config)
122132
}
123133
}
124134
for e, t := range tunnels {
125135
if !fresh[e] {
126-
klog.Infof("closing tunnel to %s", e)
136+
klog.Infof("closing tunnel with %s", e)
127137
t.Close()
128138
delete(tunnels, e)
129139
}
@@ -132,9 +142,9 @@ func main() {
132142
}
133143
}
134144

135-
func getEndpoints(resolverUrl, projectToken string) ([]string, error) {
145+
func getEndpoints(resolverUrl, token string) ([]string, error) {
136146
req, _ := http.NewRequest("GET", resolverUrl, nil)
137-
req.Header.Set("X-Token", projectToken)
147+
req.Header.Set("X-Token", token)
138148
resp, err := http.DefaultClient.Do(req)
139149
if err != nil {
140150
return nil, err
@@ -150,29 +160,44 @@ func getEndpoints(resolverUrl, projectToken string) ([]string, error) {
150160
return strings.Split(strings.TrimSpace(string(payload)), ";"), nil
151161
}
152162

153-
func connect(gwAddr, serverName, projectToken string) (net.Conn, error) {
163+
type Header struct {
164+
Token [36]byte
165+
Version [16]byte
166+
ConfigSize uint32
167+
}
168+
169+
func connect(gwAddr, serverName, token string, config []byte) (net.Conn, error) {
170+
h := Header{}
171+
copy(h.Token[:], token)
172+
copy(h.Version[:], version)
173+
h.ConfigSize = uint32(len(config))
174+
154175
klog.Infof("connecting to %s (%s)", gwAddr, serverName)
155176
deadline := time.Now().Add(timeout)
156177
dialer := &net.Dialer{Deadline: deadline}
157-
tlsCfg := &tls.Config{InsecureSkipVerify: tlsSkipVerify, ServerName: serverName}
178+
tlsCfg := &tls.Config{ServerName: serverName, InsecureSkipVerify: tlsSkipVerify}
158179
gwConn, err := tls.DialWithDialer(dialer, "tcp", gwAddr, tlsCfg)
159180
if err != nil {
160-
return nil, fmt.Errorf("failed to establish connection to %s: %s", gwAddr, err)
181+
return nil, fmt.Errorf("failed to establish a connection to %s: %s", gwAddr, err)
161182
}
162183
klog.Infof("connected to gateway %s", gwAddr)
163184

164185
_ = gwConn.SetDeadline(deadline)
165-
if _, err := gwConn.Write([]byte(projectToken)); err != nil {
186+
if err = binary.Write(gwConn, binary.LittleEndian, h); err != nil {
187+
_ = gwConn.Close()
188+
return nil, fmt.Errorf("failed to send config to %s: %s", gwAddr, err)
189+
}
190+
if _, err = gwConn.Write(config); err != nil {
166191
_ = gwConn.Close()
167-
return nil, fmt.Errorf("failed to send project token to %s: %s", gwAddr, err)
192+
return nil, fmt.Errorf("failed to send config to %s: %s", gwAddr, err)
168193
}
169194
var resp uint16
170195
if err := binary.Read(gwConn, binary.LittleEndian, &resp); err != nil {
171196
_ = gwConn.Close()
172-
return nil, fmt.Errorf("failed to read gateway response from %s: %s", gwAddr, err)
197+
return nil, fmt.Errorf("failed to read the response from %s: %s", gwAddr, err)
173198
}
174199
_ = gwConn.SetDeadline(time.Time{})
175-
klog.Infof("got from gateway %s: %d", gwAddr, resp)
200+
klog.Infof(`got "%d" from the gateway %s`, resp, gwAddr)
176201

177202
if resp != 200 {
178203
_ = gwConn.Close()
@@ -182,7 +207,7 @@ func connect(gwAddr, serverName, projectToken string) (net.Conn, error) {
182207
return gwConn, nil
183208
}
184209

185-
func proxy(ctx context.Context, gwConn net.Conn, promAddr string) {
210+
func proxy(ctx context.Context, gwConn net.Conn) {
186211
cfg := yamux.DefaultConfig()
187212
cfg.KeepAliveInterval = time.Second
188213
cfg.LogOutput = ioutil.Discard
@@ -199,30 +224,41 @@ func proxy(ctx context.Context, gwConn net.Conn, promAddr string) {
199224
default:
200225
gwStream, err := session.Accept()
201226
if err != nil {
202-
klog.Errorf("failed to accept stream: %s", err)
227+
klog.Errorf("failed to accept a stream: %s", err)
203228
return
204229
}
205230
go func(c net.Conn) {
206231
defer c.Close()
207232
deadline := time.Now().Add(streamTimeout)
208233
if err := c.SetDeadline(deadline); err != nil {
209-
klog.Errorf("failed to set deadline to stream: %s", err)
234+
klog.Errorf("failed to set a deadline for the stream: %s", err)
210235
return
211236
}
212-
promConn, err := net.DialTimeout("tcp", promAddr, timeout)
237+
var dstLen uint16
238+
if err := binary.Read(c, binary.LittleEndian, &dstLen); err != nil {
239+
klog.Errorf("failed to read the destination size: %s", err)
240+
return
241+
}
242+
dest := make([]byte, int(dstLen))
243+
if _, err := io.ReadFull(c, dest); err != nil {
244+
klog.Errorf("failed to read the destination address: %s", err)
245+
return
246+
}
247+
destAddress := string(dest)
248+
destConn, err := net.DialTimeout("tcp", destAddress, timeout)
213249
if err != nil {
214-
klog.Errorf("failed to establish prometheus connection: %s", err)
250+
klog.Errorf("failed to establish a connection to %s: %s", destAddress, err)
215251
return
216252
}
217-
defer promConn.Close()
218-
if err = promConn.SetDeadline(deadline); err != nil {
219-
klog.Errorf("failed to set deadline to prometheus connection: %s", err)
253+
defer destConn.Close()
254+
if err = destConn.SetDeadline(deadline); err != nil {
255+
klog.Errorf("failed to set a deadline for the dest connection: %s", err)
220256
return
221257
}
222258
go func() {
223-
io.Copy(c, promConn)
259+
io.Copy(c, destConn)
224260
}()
225-
io.Copy(promConn, c)
261+
io.Copy(destConn, c)
226262
}(gwStream)
227263
}
228264
}
@@ -235,12 +271,3 @@ func mustEnv(key string) string {
235271
}
236272
return value
237273
}
238-
239-
func pingProm(addr string) error {
240-
c, err := net.DialTimeout("tcp", addr, timeout)
241-
if err != nil {
242-
return err
243-
}
244-
_ = c.Close()
245-
return nil
246-
}

0 commit comments

Comments
 (0)