cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
e2a8302bbca9804507e64d77feeecb04298260b5

Branches

Tags

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

Clone

HTTPS

Download ZIP

cfapi/tunnel.go

192lines · 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) DeleteTunnel(tunnelID uuid.UUID) error {
120 endpoint := r.baseEndpoints.accountLevel
121 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
122 resp, err := r.sendRequest("DELETE", endpoint, nil)
123 if err != nil {
124 return errors.Wrap(err, "REST request failed")
125 }
126 defer resp.Body.Close()
127
128 return r.statusCodeToError("delete tunnel", resp)
129}
130
131func (r *RESTClient) ListTunnels(filter *TunnelFilter) ([]*Tunnel, error) {
132 endpoint := r.baseEndpoints.accountLevel
133 endpoint.RawQuery = filter.encode()
134 resp, err := r.sendRequest("GET", endpoint, nil)
135 if err != nil {
136 return nil, errors.Wrap(err, "REST request failed")
137 }
138 defer resp.Body.Close()
139
140 if resp.StatusCode == http.StatusOK {
141 return parseListTunnels(resp.Body)
142 }
143
144 return nil, r.statusCodeToError("list tunnels", resp)
145}
146
147func parseListTunnels(body io.ReadCloser) ([]*Tunnel, error) {
148 var tunnels []*Tunnel
149 err := parseResponse(body, &tunnels)
150 return tunnels, err
151}
152
153func (r *RESTClient) ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error) {
154 endpoint := r.baseEndpoints.accountLevel
155 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
156 resp, err := r.sendRequest("GET", endpoint, nil)
157 if err != nil {
158 return nil, errors.Wrap(err, "REST request failed")
159 }
160 defer resp.Body.Close()
161
162 if resp.StatusCode == http.StatusOK {
163 return parseConnectionsDetails(resp.Body)
164 }
165
166 return nil, r.statusCodeToError("list connection details", resp)
167}
168
169func parseConnectionsDetails(reader io.Reader) ([]*ActiveClient, error) {
170 var clients []*ActiveClient
171 err := parseResponse(reader, &clients)
172 return clients, err
173}
174
175func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID, params *CleanupParams) error {
176 endpoint := r.baseEndpoints.accountLevel
177 endpoint.RawQuery = params.encode()
178 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
179 resp, err := r.sendRequest("DELETE", endpoint, nil)
180 if err != nil {
181 return errors.Wrap(err, "REST request failed")
182 }
183 defer resp.Body.Close()
184
185 return r.statusCodeToError("cleanup connections", resp)
186}
187
188func unmarshalTunnel(reader io.Reader) (*Tunnel, error) {
189 var tunnel Tunnel
190 err := parseResponse(reader, &tunnel)
191 return &tunnel, err
192}
193