cloudflare/cloudflared

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
2021.12.4

Branches

Tags

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

Clone

HTTPS

Download ZIP

cfapi/tunnel.go

183lines · 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 Connection struct {
27 ColoName string `json:"colo_name"`
28 ID uuid.UUID `json:"id"`
29 IsPendingReconnect bool `json:"is_pending_reconnect"`
30 OriginIP net.IP `json:"origin_ip"`
31 OpenedAt time.Time `json:"opened_at"`
32}
33
34type ActiveClient struct {
35 ID uuid.UUID `json:"id"`
36 Features []string `json:"features"`
37 Version string `json:"version"`
38 Arch string `json:"arch"`
39 RunAt time.Time `json:"run_at"`
40 Connections []Connection `json:"conns"`
41}
42
43type newTunnel struct {
44 Name string `json:"name"`
45 TunnelSecret []byte `json:"tunnel_secret"`
46}
47
48type CleanupParams struct {
49 queryParams url.Values
50}
51
52func NewCleanupParams() *CleanupParams {
53 return &CleanupParams{
54 queryParams: url.Values{},
55 }
56}
57
58func (cp *CleanupParams) ForClient(clientID uuid.UUID) {
59 cp.queryParams.Set("client_id", clientID.String())
60}
61
62func (cp CleanupParams) encode() string {
63 return cp.queryParams.Encode()
64}
65
66func (r *RESTClient) CreateTunnel(name string, tunnelSecret []byte) (*Tunnel, error) {
67 if name == "" {
68 return nil, errors.New("tunnel name required")
69 }
70 if _, err := uuid.Parse(name); err == nil {
71 return nil, errors.New("you cannot use UUIDs as tunnel names")
72 }
73 body := &newTunnel{
74 Name: name,
75 TunnelSecret: tunnelSecret,
76 }
77
78 resp, err := r.sendRequest("POST", r.baseEndpoints.accountLevel, body)
79 if err != nil {
80 return nil, errors.Wrap(err, "REST request failed")
81 }
82 defer resp.Body.Close()
83
84 switch resp.StatusCode {
85 case http.StatusOK:
86 return unmarshalTunnel(resp.Body)
87 case http.StatusConflict:
88 return nil, ErrTunnelNameConflict
89 }
90
91 return nil, r.statusCodeToError("create tunnel", resp)
92}
93
94func (r *RESTClient) GetTunnel(tunnelID uuid.UUID) (*Tunnel, error) {
95 endpoint := r.baseEndpoints.accountLevel
96 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
97 resp, err := r.sendRequest("GET", endpoint, nil)
98 if err != nil {
99 return nil, errors.Wrap(err, "REST request failed")
100 }
101 defer resp.Body.Close()
102
103 if resp.StatusCode == http.StatusOK {
104 return unmarshalTunnel(resp.Body)
105 }
106
107 return nil, r.statusCodeToError("get tunnel", resp)
108}
109
110func (r *RESTClient) DeleteTunnel(tunnelID uuid.UUID) error {
111 endpoint := r.baseEndpoints.accountLevel
112 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
113 resp, err := r.sendRequest("DELETE", endpoint, nil)
114 if err != nil {
115 return errors.Wrap(err, "REST request failed")
116 }
117 defer resp.Body.Close()
118
119 return r.statusCodeToError("delete tunnel", resp)
120}
121
122func (r *RESTClient) ListTunnels(filter *TunnelFilter) ([]*Tunnel, error) {
123 endpoint := r.baseEndpoints.accountLevel
124 endpoint.RawQuery = filter.encode()
125 resp, err := r.sendRequest("GET", endpoint, nil)
126 if err != nil {
127 return nil, errors.Wrap(err, "REST request failed")
128 }
129 defer resp.Body.Close()
130
131 if resp.StatusCode == http.StatusOK {
132 return parseListTunnels(resp.Body)
133 }
134
135 return nil, r.statusCodeToError("list tunnels", resp)
136}
137
138func parseListTunnels(body io.ReadCloser) ([]*Tunnel, error) {
139 var tunnels []*Tunnel
140 err := parseResponse(body, &tunnels)
141 return tunnels, err
142}
143
144func (r *RESTClient) ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error) {
145 endpoint := r.baseEndpoints.accountLevel
146 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
147 resp, err := r.sendRequest("GET", endpoint, nil)
148 if err != nil {
149 return nil, errors.Wrap(err, "REST request failed")
150 }
151 defer resp.Body.Close()
152
153 if resp.StatusCode == http.StatusOK {
154 return parseConnectionsDetails(resp.Body)
155 }
156
157 return nil, r.statusCodeToError("list connection details", resp)
158}
159
160func parseConnectionsDetails(reader io.Reader) ([]*ActiveClient, error) {
161 var clients []*ActiveClient
162 err := parseResponse(reader, &clients)
163 return clients, err
164}
165
166func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID, params *CleanupParams) error {
167 endpoint := r.baseEndpoints.accountLevel
168 endpoint.RawQuery = params.encode()
169 endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
170 resp, err := r.sendRequest("DELETE", endpoint, nil)
171 if err != nil {
172 return errors.Wrap(err, "REST request failed")
173 }
174 defer resp.Body.Close()
175
176 return r.statusCodeToError("cleanup connections", resp)
177}
178
179func unmarshalTunnel(reader io.Reader) (*Tunnel, error) {
180 var tunnel Tunnel
181 err := parseResponse(reader, &tunnel)
182 return &tunnel, err
183}
184