cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2023.7.3

Branches

Tags

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

Clone

HTTPS

Download ZIP

cfapi/tunnel.go

235lines · 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 managementRequest struct {
54 Resources []string `json:"resources"`
55}
56
57type CleanupParams struct {
58 queryParams url.Values
59}
60
61func NewCleanupParams() *CleanupParams {
62 return &CleanupParams{
63 queryParams: url.Values{},
64 }
65}
66
67func (cp *CleanupParams) ForClient(clientID uuid.UUID) {
68 cp.queryParams.Set("client_id", clientID.String())
69}
70
71func (cp CleanupParams) encode() string {
72 return cp.queryParams.Encode()
73}
74
75func (r *RESTClient) CreateTunnel(name string, tunnelSecret []byte) (*TunnelWithToken, error) {
76 if name == "" {
77 return nil, errors.New("tunnel name required")
78 }
79 if _, err := uuid.Parse(name); err == nil {
80 return nil, errors.New("you cannot use UUIDs as tunnel names")
81 }
82 body := &newTunnel{
83 Name: name,
84 TunnelSecret: tunnelSecret,
85 }
86
87 resp, err := r.sendRequest("POST", r.baseEndpoints.accountLevel, body)
88 if err != nil {
89 return nil, errors.Wrap(err, "REST request failed")
90 }
91 defer resp.Body.Close()
92
93 switch resp.StatusCode {
94 case http.StatusOK:
95 var tunnel TunnelWithToken
96 if serdeErr := parseResponse(resp.Body, &tunnel); err != nil {
97 return nil, serdeErr
98 }
99 return &tunnel, nil
100 case http.StatusConflict:
101 return nil, ErrTunnelNameConflict
102 }
103
104 return nil, r.statusCodeToError("create tunnel", resp)
105}
106
107func (r *RESTClient) GetTunnel(tunnelID uuid.UUID) (*Tunnel, error) {
108 endpoint := r.baseEndpoints.accountLevel
109 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
110 resp, err := r.sendRequest("GET", endpoint, nil)
111 if err != nil {
112 return nil, errors.Wrap(err, "REST request failed")
113 }
114 defer resp.Body.Close()
115
116 if resp.StatusCode == http.StatusOK {
117 return unmarshalTunnel(resp.Body)
118 }
119
120 return nil, r.statusCodeToError("get tunnel", resp)
121}
122
123func (r *RESTClient) GetTunnelToken(tunnelID uuid.UUID) (token string, err error) {
124 endpoint := r.baseEndpoints.accountLevel
125 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/token", tunnelID))
126 resp, err := r.sendRequest("GET", endpoint, nil)
127 if err != nil {
128 return "", errors.Wrap(err, "REST request failed")
129 }
130 defer resp.Body.Close()
131
132 if resp.StatusCode == http.StatusOK {
133 err = parseResponse(resp.Body, &token)
134 return token, err
135 }
136
137 return "", r.statusCodeToError("get tunnel token", resp)
138}
139
140func (r *RESTClient) GetManagementToken(tunnelID uuid.UUID) (token string, err error) {
141 endpoint := r.baseEndpoints.accountLevel
142 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/management", tunnelID))
143
144 body := &managementRequest{
145 Resources: []string{"logs"},
146 }
147
148 resp, err := r.sendRequest("POST", endpoint, body)
149 if err != nil {
150 return "", errors.Wrap(err, "REST request failed")
151 }
152 defer resp.Body.Close()
153
154 if resp.StatusCode == http.StatusOK {
155 err = parseResponse(resp.Body, &token)
156 return token, err
157 }
158
159 return "", r.statusCodeToError("get tunnel token", resp)
160}
161
162func (r *RESTClient) DeleteTunnel(tunnelID uuid.UUID) error {
163 endpoint := r.baseEndpoints.accountLevel
164 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
165 resp, err := r.sendRequest("DELETE", endpoint, nil)
166 if err != nil {
167 return errors.Wrap(err, "REST request failed")
168 }
169 defer resp.Body.Close()
170
171 return r.statusCodeToError("delete tunnel", resp)
172}
173
174func (r *RESTClient) ListTunnels(filter *TunnelFilter) ([]*Tunnel, error) {
175 endpoint := r.baseEndpoints.accountLevel
176 endpoint.RawQuery = filter.encode()
177 resp, err := r.sendRequest("GET", endpoint, nil)
178 if err != nil {
179 return nil, errors.Wrap(err, "REST request failed")
180 }
181 defer resp.Body.Close()
182
183 if resp.StatusCode == http.StatusOK {
184 return parseListTunnels(resp.Body)
185 }
186
187 return nil, r.statusCodeToError("list tunnels", resp)
188}
189
190func parseListTunnels(body io.ReadCloser) ([]*Tunnel, error) {
191 var tunnels []*Tunnel
192 err := parseResponse(body, &tunnels)
193 return tunnels, err
194}
195
196func (r *RESTClient) ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error) {
197 endpoint := r.baseEndpoints.accountLevel
198 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
199 resp, err := r.sendRequest("GET", endpoint, nil)
200 if err != nil {
201 return nil, errors.Wrap(err, "REST request failed")
202 }
203 defer resp.Body.Close()
204
205 if resp.StatusCode == http.StatusOK {
206 return parseConnectionsDetails(resp.Body)
207 }
208
209 return nil, r.statusCodeToError("list connection details", resp)
210}
211
212func parseConnectionsDetails(reader io.Reader) ([]*ActiveClient, error) {
213 var clients []*ActiveClient
214 err := parseResponse(reader, &clients)
215 return clients, err
216}
217
218func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID, params *CleanupParams) error {
219 endpoint := r.baseEndpoints.accountLevel
220 endpoint.RawQuery = params.encode()
221 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
222 resp, err := r.sendRequest("DELETE", endpoint, nil)
223 if err != nil {
224 return errors.Wrap(err, "REST request failed")
225 }
226 defer resp.Body.Close()
227
228 return r.statusCodeToError("cleanup connections", resp)
229}
230
231func unmarshalTunnel(reader io.Reader) (*Tunnel, error) {
232 var tunnel Tunnel
233 err := parseResponse(reader, &tunnel)
234 return &tunnel, err
235}
236