cloudflare/cloudflared

Public

mirrored from https://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2023.2.2

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

cfapi/tunnel.go

209lines · modecode

1package cfapi
2
3import (
4 "fmt"
5 "io"
6 "net"
7 "net/http"
8 "net/url"
9 "path"
10 "time"
11
12 "github.com/google/uuid"
13 "github.com/pkg/errors"
14)
15
16var ErrTunnelNameConflict = errors.New("tunnel with name already exists")
17
18type Tunnel struct {
19 ID uuid.UUID `json:"id"`
20 Name string `json:"name"`
21 CreatedAt time.Time `json:"created_at"`
22 DeletedAt time.Time `json:"deleted_at"`
23 Connections []Connection `json:"connections"`
24}
25
26type TunnelWithToken struct {
27 Tunnel
28 Token string `json:"token"`
29}
30
31type Connection struct {
32 ColoName string `json:"colo_name"`
33 ID uuid.UUID `json:"id"`
34 IsPendingReconnect bool `json:"is_pending_reconnect"`
35 OriginIP net.IP `json:"origin_ip"`
36 OpenedAt time.Time `json:"opened_at"`
37}
38
39type ActiveClient struct {
40 ID uuid.UUID `json:"id"`
41 Features []string `json:"features"`
42 Version string `json:"version"`
43 Arch string `json:"arch"`
44 RunAt time.Time `json:"run_at"`
45 Connections []Connection `json:"conns"`
46}
47
48type newTunnel struct {
49 Name string `json:"name"`
50 TunnelSecret []byte `json:"tunnel_secret"`
51}
52
53type CleanupParams struct {
54 queryParams url.Values
55}
56
57func NewCleanupParams() *CleanupParams {
58 return &CleanupParams{
59 queryParams: url.Values{},
60 }
61}
62
63func (cp *CleanupParams) ForClient(clientID uuid.UUID) {
64 cp.queryParams.Set("client_id", clientID.String())
65}
66
67func (cp CleanupParams) encode() string {
68 return cp.queryParams.Encode()
69}
70
71func (r *RESTClient) CreateTunnel(name string, tunnelSecret []byte) (*TunnelWithToken, error) {
72 if name == "" {
73 return nil, errors.New("tunnel name required")
74 }
75 if _, err := uuid.Parse(name); err == nil {
76 return nil, errors.New("you cannot use UUIDs as tunnel names")
77 }
78 body := &newTunnel{
79 Name: name,
80 TunnelSecret: tunnelSecret,
81 }
82
83 resp, err := r.sendRequest("POST", r.baseEndpoints.accountLevel, body)
84 if err != nil {
85 return nil, errors.Wrap(err, "REST request failed")
86 }
87 defer resp.Body.Close()
88
89 switch resp.StatusCode {
90 case http.StatusOK:
91 var tunnel TunnelWithToken
92 if serdeErr := parseResponse(resp.Body, &tunnel); err != nil {
93 return nil, serdeErr
94 }
95 return &tunnel, nil
96 case http.StatusConflict:
97 return nil, ErrTunnelNameConflict
98 }
99
100 return nil, r.statusCodeToError("create tunnel", resp)
101}
102
103func (r *RESTClient) GetTunnel(tunnelID uuid.UUID) (*Tunnel, error) {
104 endpoint := r.baseEndpoints.accountLevel
105 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
106 resp, err := r.sendRequest("GET", endpoint, nil)
107 if err != nil {
108 return nil, errors.Wrap(err, "REST request failed")
109 }
110 defer resp.Body.Close()
111
112 if resp.StatusCode == http.StatusOK {
113 return unmarshalTunnel(resp.Body)
114 }
115
116 return nil, r.statusCodeToError("get tunnel", resp)
117}
118
119func (r *RESTClient) GetTunnelToken(tunnelID uuid.UUID) (token string, err error) {
120 endpoint := r.baseEndpoints.accountLevel
121 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/token", tunnelID))
122 resp, err := r.sendRequest("GET", endpoint, nil)
123 if err != nil {
124 return "", errors.Wrap(err, "REST request failed")
125 }
126 defer resp.Body.Close()
127
128 if resp.StatusCode == http.StatusOK {
129 err = parseResponse(resp.Body, &token)
130 return token, err
131 }
132
133 return "", r.statusCodeToError("get tunnel token", resp)
134}
135
136func (r *RESTClient) DeleteTunnel(tunnelID uuid.UUID) error {
137 endpoint := r.baseEndpoints.accountLevel
138 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
139 resp, err := r.sendRequest("DELETE", endpoint, nil)
140 if err != nil {
141 return errors.Wrap(err, "REST request failed")
142 }
143 defer resp.Body.Close()
144
145 return r.statusCodeToError("delete tunnel", resp)
146}
147
148func (r *RESTClient) ListTunnels(filter *TunnelFilter) ([]*Tunnel, error) {
149 endpoint := r.baseEndpoints.accountLevel
150 endpoint.RawQuery = filter.encode()
151 resp, err := r.sendRequest("GET", endpoint, nil)
152 if err != nil {
153 return nil, errors.Wrap(err, "REST request failed")
154 }
155 defer resp.Body.Close()
156
157 if resp.StatusCode == http.StatusOK {
158 return parseListTunnels(resp.Body)
159 }
160
161 return nil, r.statusCodeToError("list tunnels", resp)
162}
163
164func parseListTunnels(body io.ReadCloser) ([]*Tunnel, error) {
165 var tunnels []*Tunnel
166 err := parseResponse(body, &tunnels)
167 return tunnels, err
168}
169
170func (r *RESTClient) ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error) {
171 endpoint := r.baseEndpoints.accountLevel
172 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
173 resp, err := r.sendRequest("GET", endpoint, nil)
174 if err != nil {
175 return nil, errors.Wrap(err, "REST request failed")
176 }
177 defer resp.Body.Close()
178
179 if resp.StatusCode == http.StatusOK {
180 return parseConnectionsDetails(resp.Body)
181 }
182
183 return nil, r.statusCodeToError("list connection details", resp)
184}
185
186func parseConnectionsDetails(reader io.Reader) ([]*ActiveClient, error) {
187 var clients []*ActiveClient
188 err := parseResponse(reader, &clients)
189 return clients, err
190}
191
192func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID, params *CleanupParams) error {
193 endpoint := r.baseEndpoints.accountLevel
194 endpoint.RawQuery = params.encode()
195 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
196 resp, err := r.sendRequest("DELETE", endpoint, nil)
197 if err != nil {
198 return errors.Wrap(err, "REST request failed")
199 }
200 defer resp.Body.Close()
201
202 return r.statusCodeToError("cleanup connections", resp)
203}
204
205func unmarshalTunnel(reader io.Reader) (*Tunnel, error) {
206 var tunnel Tunnel
207 err := parseResponse(reader, &tunnel)
208 return &tunnel, err
209}
210