mirror of
https://github.com/cloudflare/cloudflared.git
synced 2025-05-11 21:26:35 +00:00

cloudflared tail will now fetch the management token from by making a request to the Cloudflare API using the cert.pem (acquired from cloudflared login). Refactored some of the credentials code into it's own package as to allow for easier use between subcommands outside of `cloudflared tunnel`.
236 lines
6.1 KiB
Go
236 lines
6.1 KiB
Go
package cfapi
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/url"
|
|
"path"
|
|
"time"
|
|
|
|
"github.com/google/uuid"
|
|
"github.com/pkg/errors"
|
|
)
|
|
|
|
var ErrTunnelNameConflict = errors.New("tunnel with name already exists")
|
|
|
|
type Tunnel struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Name string `json:"name"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
DeletedAt time.Time `json:"deleted_at"`
|
|
Connections []Connection `json:"connections"`
|
|
}
|
|
|
|
type TunnelWithToken struct {
|
|
Tunnel
|
|
Token string `json:"token"`
|
|
}
|
|
|
|
type Connection struct {
|
|
ColoName string `json:"colo_name"`
|
|
ID uuid.UUID `json:"id"`
|
|
IsPendingReconnect bool `json:"is_pending_reconnect"`
|
|
OriginIP net.IP `json:"origin_ip"`
|
|
OpenedAt time.Time `json:"opened_at"`
|
|
}
|
|
|
|
type ActiveClient struct {
|
|
ID uuid.UUID `json:"id"`
|
|
Features []string `json:"features"`
|
|
Version string `json:"version"`
|
|
Arch string `json:"arch"`
|
|
RunAt time.Time `json:"run_at"`
|
|
Connections []Connection `json:"conns"`
|
|
}
|
|
|
|
type newTunnel struct {
|
|
Name string `json:"name"`
|
|
TunnelSecret []byte `json:"tunnel_secret"`
|
|
}
|
|
|
|
type managementRequest struct {
|
|
Resources []string `json:"resources"`
|
|
}
|
|
|
|
type CleanupParams struct {
|
|
queryParams url.Values
|
|
}
|
|
|
|
func NewCleanupParams() *CleanupParams {
|
|
return &CleanupParams{
|
|
queryParams: url.Values{},
|
|
}
|
|
}
|
|
|
|
func (cp *CleanupParams) ForClient(clientID uuid.UUID) {
|
|
cp.queryParams.Set("client_id", clientID.String())
|
|
}
|
|
|
|
func (cp CleanupParams) encode() string {
|
|
return cp.queryParams.Encode()
|
|
}
|
|
|
|
func (r *RESTClient) CreateTunnel(name string, tunnelSecret []byte) (*TunnelWithToken, error) {
|
|
if name == "" {
|
|
return nil, errors.New("tunnel name required")
|
|
}
|
|
if _, err := uuid.Parse(name); err == nil {
|
|
return nil, errors.New("you cannot use UUIDs as tunnel names")
|
|
}
|
|
body := &newTunnel{
|
|
Name: name,
|
|
TunnelSecret: tunnelSecret,
|
|
}
|
|
|
|
resp, err := r.sendRequest("POST", r.baseEndpoints.accountLevel, body)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "REST request failed")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
switch resp.StatusCode {
|
|
case http.StatusOK:
|
|
var tunnel TunnelWithToken
|
|
if serdeErr := parseResponse(resp.Body, &tunnel); err != nil {
|
|
return nil, serdeErr
|
|
}
|
|
return &tunnel, nil
|
|
case http.StatusConflict:
|
|
return nil, ErrTunnelNameConflict
|
|
}
|
|
|
|
return nil, r.statusCodeToError("create tunnel", resp)
|
|
}
|
|
|
|
func (r *RESTClient) GetTunnel(tunnelID uuid.UUID) (*Tunnel, error) {
|
|
endpoint := r.baseEndpoints.accountLevel
|
|
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
|
|
resp, err := r.sendRequest("GET", endpoint, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "REST request failed")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusOK {
|
|
return unmarshalTunnel(resp.Body)
|
|
}
|
|
|
|
return nil, r.statusCodeToError("get tunnel", resp)
|
|
}
|
|
|
|
func (r *RESTClient) GetTunnelToken(tunnelID uuid.UUID) (token string, err error) {
|
|
endpoint := r.baseEndpoints.accountLevel
|
|
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/token", tunnelID))
|
|
resp, err := r.sendRequest("GET", endpoint, nil)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "REST request failed")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusOK {
|
|
err = parseResponse(resp.Body, &token)
|
|
return token, err
|
|
}
|
|
|
|
return "", r.statusCodeToError("get tunnel token", resp)
|
|
}
|
|
|
|
func (r *RESTClient) GetManagementToken(tunnelID uuid.UUID) (token string, err error) {
|
|
endpoint := r.baseEndpoints.accountLevel
|
|
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/management", tunnelID))
|
|
|
|
body := &managementRequest{
|
|
Resources: []string{"logs"},
|
|
}
|
|
|
|
resp, err := r.sendRequest("POST", endpoint, body)
|
|
if err != nil {
|
|
return "", errors.Wrap(err, "REST request failed")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusOK {
|
|
err = parseResponse(resp.Body, &token)
|
|
return token, err
|
|
}
|
|
|
|
return "", r.statusCodeToError("get tunnel token", resp)
|
|
}
|
|
|
|
func (r *RESTClient) DeleteTunnel(tunnelID uuid.UUID) error {
|
|
endpoint := r.baseEndpoints.accountLevel
|
|
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v", tunnelID))
|
|
resp, err := r.sendRequest("DELETE", endpoint, nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "REST request failed")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
return r.statusCodeToError("delete tunnel", resp)
|
|
}
|
|
|
|
func (r *RESTClient) ListTunnels(filter *TunnelFilter) ([]*Tunnel, error) {
|
|
endpoint := r.baseEndpoints.accountLevel
|
|
endpoint.RawQuery = filter.encode()
|
|
resp, err := r.sendRequest("GET", endpoint, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "REST request failed")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusOK {
|
|
return parseListTunnels(resp.Body)
|
|
}
|
|
|
|
return nil, r.statusCodeToError("list tunnels", resp)
|
|
}
|
|
|
|
func parseListTunnels(body io.ReadCloser) ([]*Tunnel, error) {
|
|
var tunnels []*Tunnel
|
|
err := parseResponse(body, &tunnels)
|
|
return tunnels, err
|
|
}
|
|
|
|
func (r *RESTClient) ListActiveClients(tunnelID uuid.UUID) ([]*ActiveClient, error) {
|
|
endpoint := r.baseEndpoints.accountLevel
|
|
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
|
|
resp, err := r.sendRequest("GET", endpoint, nil)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "REST request failed")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusOK {
|
|
return parseConnectionsDetails(resp.Body)
|
|
}
|
|
|
|
return nil, r.statusCodeToError("list connection details", resp)
|
|
}
|
|
|
|
func parseConnectionsDetails(reader io.Reader) ([]*ActiveClient, error) {
|
|
var clients []*ActiveClient
|
|
err := parseResponse(reader, &clients)
|
|
return clients, err
|
|
}
|
|
|
|
func (r *RESTClient) CleanupConnections(tunnelID uuid.UUID, params *CleanupParams) error {
|
|
endpoint := r.baseEndpoints.accountLevel
|
|
endpoint.RawQuery = params.encode()
|
|
endpoint.Path = path.Join(endpoint.Path, fmt.Sprintf("%v/connections", tunnelID))
|
|
resp, err := r.sendRequest("DELETE", endpoint, nil)
|
|
if err != nil {
|
|
return errors.Wrap(err, "REST request failed")
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
return r.statusCodeToError("cleanup connections", resp)
|
|
}
|
|
|
|
func unmarshalTunnel(reader io.Reader) (*Tunnel, error) {
|
|
var tunnel Tunnel
|
|
err := parseResponse(reader, &tunnel)
|
|
return &tunnel, err
|
|
}
|