cloudflare/cloudflared

Public

mirrored fromhttps://github.com/cloudflare/cloudflaredAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
4d95ab73f584a5a6439803648f013031ca1dd4ac

Branches

Tags

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

Clone

HTTPS

Download ZIP

cfapi/tunnel.go

255lines · 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 ManagementResource int
19
20const (
21 Logs ManagementResource = iota
22 Admin
23 HostDetails
24)
25
26func (r ManagementResource) String() string {
27 switch r {
28 case Logs:
29 return "logs"
30 case Admin:
31 return "admin"
32 case HostDetails:
33 return "host_details"
34 default:
35 return ""
36 }
37}
38
39type Tunnel struct {
40 ID uuid.UUID `json:"id"`
41 Name string `json:"name"`
42 CreatedAt time.Time `json:"created_at"`
43 DeletedAt time.Time `json:"deleted_at"`
44 Connections []Connection `json:"connections"`
45}
46
47type TunnelWithToken struct {
48 Tunnel
49 Token string `json:"token"`
50}
51
52type Connection struct {
53 ColoName string `json:"colo_name"`
54 ID uuid.UUID `json:"id"`
55 IsPendingReconnect bool `json:"is_pending_reconnect"`
56 OriginIP net.IP `json:"origin_ip"`
57 OpenedAt time.Time `json:"opened_at"`
58}
59
60type ActiveClient struct {
61 ID uuid.UUID `json:"id"`
62 Features []string `json:"features"`
63 Version string `json:"version"`
64 Arch string `json:"arch"`
65 RunAt time.Time `json:"run_at"`
66 Connections []Connection `json:"conns"`
67}
68
69type newTunnel struct {
70 Name string `json:"name"`
71 TunnelSecret []byte `json:"tunnel_secret"`
72}
73
74type CleanupParams struct {
75 queryParams url.Values
76}
77
78func NewCleanupParams() *CleanupParams {
79 return &CleanupParams{
80 queryParams: url.Values{},
81 }
82}
83
84func (cp *CleanupParams) ForClient(clientID uuid.UUID) {
85 cp.queryParams.Set("client_id", clientID.String())
86}
87
88func (cp CleanupParams) encode() string {
89 return cp.queryParams.Encode()
90}
91
92func (r *RESTClient) CreateTunnel(name string, tunnelSecret []byte) (*TunnelWithToken, error) {
93 if name == "" {
94 return nil, errors.New("tunnel name required")
95 }
96 if _, err := uuid.Parse(name); err == nil {
97 return nil, errors.New("you cannot use UUIDs as tunnel names")
98 }
99 body := &newTunnel{
100 Name: name,
101 TunnelSecret: tunnelSecret,
102 }
103
104 resp, err := r.sendRequest("POST", r.baseEndpoints.accountLevel, body)
105 if err != nil {
106 return nil, errors.Wrap(err, "REST request failed")
107 }
108 defer resp.Body.Close()
109
110 switch resp.StatusCode {
111 case http.StatusOK:
112 var tunnel TunnelWithToken
113 if serdeErr := parseResponse(resp.Body, &tunnel); serdeErr != nil {
114 return nil, serdeErr
115 }
116 return &tunnel, nil
117 case http.StatusConflict:
118 return nil, ErrTunnelNameConflict
119 }
120
121 return nil, r.statusCodeToError("create tunnel", resp)
122}
123
124func (r *RESTClient) GetTunnel(tunnelID uuid.UUID) (*Tunnel, error) {
125 endpoint := r.baseEndpoints.accountLevel
126 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
127 resp, err := r.sendRequest("GET", endpoint, nil)
128 if err != nil {
129 return nil, errors.Wrap(err, "REST request failed")
130 }
131 defer resp.Body.Close()
132
133 if resp.StatusCode == http.StatusOK {
134 return unmarshalTunnel(resp.Body)
135 }
136
137 return nil, r.statusCodeToError("get tunnel", resp)
138}
139
140func (r *RESTClient) GetTunnelToken(tunnelID uuid.UUID) (token string, err error) {
141 endpoint := r.baseEndpoints.accountLevel
142 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/token", tunnelID))
143 resp, err := r.sendRequest("GET", endpoint, nil)
144 if err != nil {
145 return "", errors.Wrap(err, "REST request failed")
146 }
147 defer resp.Body.Close()
148
149 if resp.StatusCode == http.StatusOK {
150 err = parseResponse(resp.Body, &token)
151 return token, err
152 }
153
154 return "", r.statusCodeToError("get tunnel token", resp)
155}
156
157// managementEndpointPath returns the path segment for a management resource endpoint
158func managementEndpointPath(tunnelID uuid.UUID, res ManagementResource) string {
159 return fmt.Sprintf("%v/management/%s", tunnelID, res.String())
160}
161
162func (r *RESTClient) GetManagementToken(tunnelID uuid.UUID, res ManagementResource) (token string, err error) {
163 endpoint := r.baseEndpoints.accountLevel
164 endpoint.Path = path.Join(endpoint.Path, managementEndpointPath(tunnelID, res))
165
166 resp, err := r.sendRequest("POST", endpoint, nil)
167 if err != nil {
168 return "", errors.Wrap(err, "REST request failed")
169 }
170 defer resp.Body.Close()
171
172 if resp.StatusCode == http.StatusOK {
173 err = parseResponse(resp.Body, &token)
174 return token, err
175 }
176
177 return "", r.statusCodeToError("get tunnel token", resp)
178}
179
180func (r *RESTClient) DeleteTunnel(tunnelID uuid.UUID, cascade bool) error {
181 endpoint := r.baseEndpoints.accountLevel
182 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
183 // Cascade will delete all tunnel dependencies (connections, routes, etc.) that
184 // are linked to the deleted tunnel.
185 if cascade {
186 endpoint.RawQuery = "cascade=true"
187 }
188 resp, err := r.sendRequest("DELETE", endpoint, nil)
189 if err != nil {
190 return errors.Wrap(err, "REST request failed")
191 }
192 defer resp.Body.Close()
193
194 return r.statusCodeToError("delete tunnel", resp)
195}
196
197func (r *RESTClient) ListTunnels(filter *TunnelFilter) ([]*Tunnel, error) {
198 fetchFn := func(page int) (*http.Response, error) {
199 endpoint := r.baseEndpoints.accountLevel
200 filter.Page(page)
201 endpoint.RawQuery = filter.encode()
202 rsp, err := r.sendRequest("GET", endpoint, nil)
203 if err != nil {
204 return nil, errors.Wrap(err, "REST request failed")
205 }
206 if rsp.StatusCode != http.StatusOK {
207 rsp.Body.Close()
208 return nil, r.statusCodeToError("list tunnels", rsp)
209 }
210 return rsp, nil
211 }
212
213 return fetchExhaustively[Tunnel](fetchFn)
214}
215
216func (r *RESTClient) ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error) {
217 endpoint := r.baseEndpoints.accountLevel
218 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
219 resp, err := r.sendRequest("GET", endpoint, nil)
220 if err != nil {
221 return nil, errors.Wrap(err, "REST request failed")
222 }
223 defer resp.Body.Close()
224
225 if resp.StatusCode == http.StatusOK {
226 return parseConnectionsDetails(resp.Body)
227 }
228
229 return nil, r.statusCodeToError("list connection details", resp)
230}
231
232func parseConnectionsDetails(reader io.Reader) ([]*ActiveClient, error) {
233 var clients []*ActiveClient
234 err := parseResponse(reader, &clients)
235 return clients, err
236}
237
238func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID, params *CleanupParams) error {
239 endpoint := r.baseEndpoints.accountLevel
240 endpoint.RawQuery = params.encode()
241 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
242 resp, err := r.sendRequest("DELETE", endpoint, nil)
243 if err != nil {
244 return errors.Wrap(err, "REST request failed")
245 }
246 defer resp.Body.Close()
247
248 return r.statusCodeToError("cleanup connections", resp)
249}
250
251func unmarshalTunnel(reader io.Reader) (*Tunnel, error) {
252 var tunnel Tunnel
253 err := parseResponse(reader, &tunnel)
254 return &tunnel, err
255}
256