package cloudflare import ( "bytes" "context" "encoding/json" "fmt" "io" "net/http" "time" "github.com/pkg/errors" ) // Client represents a Cloudflare API client type Client struct { apiToken string accountID string httpClient *http.Client baseURL string } // NewClient creates a new Cloudflare API client func NewClient(apiToken, accountID string) *Client { return &Client{ apiToken: apiToken, accountID: accountID, httpClient: &http.Client{ Timeout: 30 * time.Second, }, baseURL: "https://api.cloudflare.com/client/v4", } } // makeRequest makes an HTTP request to the Cloudflare API func (c *Client) makeRequest(ctx context.Context, method, path string, body interface{}) (*http.Response, error) { var reqBody io.Reader if body != nil { jsonData, err := json.Marshal(body) if err != nil { return nil, errors.Wrap(err, "failed to marshal request body") } reqBody = bytes.NewBuffer(jsonData) } url := c.baseURL + path req, err := http.NewRequestWithContext(ctx, method, url, reqBody) if err != nil { return nil, errors.Wrap(err, "failed to create request") } req.Header.Set("Authorization", "Bearer "+c.apiToken) req.Header.Set("Content-Type", "application/json") resp, err := c.httpClient.Do(req) if err != nil { return nil, errors.Wrap(err, "request failed") } return resp, nil } // parseResponse parses the Cloudflare API response func (c *Client) parseResponse(resp *http.Response, result interface{}) error { defer resp.Body.Close() if resp.StatusCode < 200 || resp.StatusCode >= 300 { bodyBytes, _ := io.ReadAll(resp.Body) return fmt.Errorf("API error: %d %s - %s", resp.StatusCode, resp.Status, string(bodyBytes)) } if result != nil { if err := json.NewDecoder(resp.Body).Decode(result); err != nil { return errors.Wrap(err, "failed to decode response") } } return nil } // Tunnel represents a Cloudflare Tunnel type Tunnel struct { ID string Name string Status string Connections int CreatedAt time.Time } // DNSRecord represents a DNS record type DNSRecord struct { ID string Type string Name string Content string TTL int ZoneID string ZoneName string } // ZeroTrustPolicy represents a Zero Trust access policy type ZeroTrustPolicy struct { ID string Name string Decision string Include []map[string]interface{} Exclude []map[string]interface{} Require []map[string]interface{} } // ListTunnels lists all Cloudflare Tunnels func (c *Client) ListTunnels(ctx context.Context) ([]Tunnel, error) { resp, err := c.makeRequest(ctx, "GET", fmt.Sprintf("/accounts/%s/cfd_tunnel", c.accountID), nil) if err != nil { return nil, err } var apiResponse struct { Success bool `json:"success"` Result []struct { ID string `json:"id"` Name string `json:"name"` Status string `json:"status"` Connections int `json:"connections"` CreatedAt time.Time `json:"created_at"` } `json:"result"` Errors []interface{} `json:"errors"` } if err := c.parseResponse(resp, &apiResponse); err != nil { return nil, err } if !apiResponse.Success { return nil, fmt.Errorf("API returned success=false") } tunnels := make([]Tunnel, len(apiResponse.Result)) for i, t := range apiResponse.Result { tunnels[i] = Tunnel{ ID: t.ID, Name: t.Name, Status: t.Status, Connections: t.Connections, CreatedAt: t.CreatedAt, } } return tunnels, nil } // ListDNSRecords lists DNS records for a zone func (c *Client) ListDNSRecords(ctx context.Context, zoneID string) ([]DNSRecord, error) { resp, err := c.makeRequest(ctx, "GET", fmt.Sprintf("/zones/%s/dns_records", zoneID), nil) if err != nil { return nil, err } var apiResponse struct { Success bool `json:"success"` Result []struct { ID string `json:"id"` Type string `json:"type"` Name string `json:"name"` Content string `json:"content"` TTL int `json:"ttl"` ZoneID string `json:"zone_id"` ZoneName string `json:"zone_name"` } `json:"result"` Errors []interface{} `json:"errors"` } if err := c.parseResponse(resp, &apiResponse); err != nil { return nil, err } if !apiResponse.Success { return nil, fmt.Errorf("API returned success=false") } records := make([]DNSRecord, len(apiResponse.Result)) for i, r := range apiResponse.Result { records[i] = DNSRecord{ ID: r.ID, Type: r.Type, Name: r.Name, Content: r.Content, TTL: r.TTL, ZoneID: r.ZoneID, ZoneName: r.ZoneName, } } return records, nil } // ListZones lists all DNS zones func (c *Client) ListZones(ctx context.Context) ([]string, error) { resp, err := c.makeRequest(ctx, "GET", "/zones", nil) if err != nil { return nil, err } var apiResponse struct { Success bool `json:"success"` Result []struct { ID string `json:"id"` Name string `json:"name"` } `json:"result"` Errors []interface{} `json:"errors"` } if err := c.parseResponse(resp, &apiResponse); err != nil { return nil, err } if !apiResponse.Success { return nil, fmt.Errorf("API returned success=false") } zoneIDs := make([]string, len(apiResponse.Result)) for i, z := range apiResponse.Result { zoneIDs[i] = z.ID } return zoneIDs, nil } // ListZeroTrustPolicies lists Zero Trust access policies func (c *Client) ListZeroTrustPolicies(ctx context.Context) ([]ZeroTrustPolicy, error) { resp, err := c.makeRequest(ctx, "GET", fmt.Sprintf("/accounts/%s/access/policies", c.accountID), nil) if err != nil { return nil, err } var apiResponse struct { Success bool `json:"success"` Result []struct { ID string `json:"id"` Name string `json:"name"` Decision string `json:"decision"` Include []map[string]interface{} `json:"include"` Exclude []map[string]interface{} `json:"exclude"` Require []map[string]interface{} `json:"require"` } `json:"result"` Errors []interface{} `json:"errors"` } if err := c.parseResponse(resp, &apiResponse); err != nil { return nil, err } if !apiResponse.Success { return nil, fmt.Errorf("API returned success=false") } policies := make([]ZeroTrustPolicy, len(apiResponse.Result)) for i, p := range apiResponse.Result { policies[i] = ZeroTrustPolicy{ ID: p.ID, Name: p.Name, Decision: p.Decision, Include: p.Include, Exclude: p.Exclude, Require: p.Require, } } return policies, nil }