215 lines
6.6 KiB
Go
215 lines
6.6 KiB
Go
package services
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"io"
|
||
"net/http"
|
||
"time"
|
||
)
|
||
|
||
const cloudflareBaseURL = "https://api.cloudflare.com/client/v4"
|
||
|
||
// CloudflareClient es el cliente HTTP para la API de Cloudflare.
|
||
type CloudflareClient struct {
|
||
token string
|
||
accountID string
|
||
httpClient *http.Client
|
||
}
|
||
|
||
// NewCloudflareClient crea un cliente con el API Token.
|
||
func NewCloudflareClient(token, accountID string) *CloudflareClient {
|
||
return &CloudflareClient{
|
||
token: token,
|
||
accountID: accountID,
|
||
httpClient: &http.Client{
|
||
Timeout: 20 * time.Second,
|
||
},
|
||
}
|
||
}
|
||
|
||
// get realiza una petición GET autenticada y devuelve el body.
|
||
func (c *CloudflareClient) get(path string, dest interface{}) error {
|
||
req, err := http.NewRequest(http.MethodGet, cloudflareBaseURL+path, nil)
|
||
if err != nil {
|
||
return fmt.Errorf("cloudflare: crear request: %w", err)
|
||
}
|
||
req.Header.Set("Authorization", "Bearer "+c.token)
|
||
req.Header.Set("Content-Type", "application/json")
|
||
|
||
resp, err := c.httpClient.Do(req)
|
||
if err != nil {
|
||
return fmt.Errorf("cloudflare: ejecutar request: %w", err)
|
||
}
|
||
defer resp.Body.Close()
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return fmt.Errorf("cloudflare: leer body: %w", err)
|
||
}
|
||
|
||
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
|
||
return fmt.Errorf("cloudflare: status %d – %s", resp.StatusCode, string(body))
|
||
}
|
||
|
||
// Cloudflare siempre envuelve en {"result":..., "success":true}
|
||
var wrapper struct {
|
||
Success bool `json:"success"`
|
||
Errors []CFError `json:"errors"`
|
||
Result json.RawMessage `json:"result"`
|
||
}
|
||
if err := json.Unmarshal(body, &wrapper); err != nil {
|
||
return fmt.Errorf("cloudflare: decodificar envelope: %w", err)
|
||
}
|
||
if !wrapper.Success {
|
||
if len(wrapper.Errors) > 0 {
|
||
return fmt.Errorf("cloudflare API error %d: %s", wrapper.Errors[0].Code, wrapper.Errors[0].Message)
|
||
}
|
||
return fmt.Errorf("cloudflare: respuesta no exitosa")
|
||
}
|
||
if dest != nil && wrapper.Result != nil {
|
||
if err := json.Unmarshal(wrapper.Result, dest); err != nil {
|
||
return fmt.Errorf("cloudflare: decodificar result: %w", err)
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// ─── Tipos de error ──────────────────────────────────────────────────────────
|
||
|
||
type CFError struct {
|
||
Code int `json:"code"`
|
||
Message string `json:"message"`
|
||
}
|
||
|
||
// ─── Tipos de respuesta ──────────────────────────────────────────────────────
|
||
|
||
// CFUser información del usuario autenticado.
|
||
type CFUser struct {
|
||
ID string `json:"id"`
|
||
Email string `json:"email"`
|
||
FirstName string `json:"first_name"`
|
||
LastName string `json:"last_name"`
|
||
Username string `json:"username"`
|
||
}
|
||
|
||
// CFZone representa una zona (dominio) en Cloudflare.
|
||
type CFZone struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Status string `json:"status"`
|
||
Paused bool `json:"paused"`
|
||
Type string `json:"type"`
|
||
NameServers []string `json:"name_servers"`
|
||
OriginalNS []string `json:"original_name_servers"`
|
||
ModifiedOn string `json:"modified_on"`
|
||
ActivatedOn string `json:"activated_on"`
|
||
Plan CFPlan `json:"plan"`
|
||
}
|
||
|
||
// CFPlan representa el plan de una zona.
|
||
type CFPlan struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
}
|
||
|
||
// CFDNSRecord representa un registro DNS.
|
||
type CFDNSRecord struct {
|
||
ID string `json:"id"`
|
||
Type string `json:"type"`
|
||
Name string `json:"name"`
|
||
Content string `json:"content"`
|
||
Proxied bool `json:"proxied"`
|
||
Proxiable bool `json:"proxiable"`
|
||
TTL int `json:"ttl"`
|
||
Priority int `json:"priority,omitempty"`
|
||
CreatedOn string `json:"created_on"`
|
||
ModifiedOn string `json:"modified_on"`
|
||
}
|
||
|
||
// CFSSLStatus representa el estado SSL de una zona.
|
||
type CFSSLStatus struct {
|
||
ID string `json:"id"`
|
||
Type string `json:"type"`
|
||
Status string `json:"status"`
|
||
Hosts []string `json:"hosts"`
|
||
PrimaryCert string `json:"primary_certificate"`
|
||
ExpiresOn string `json:"expires_on"`
|
||
}
|
||
|
||
// CFFirewallRule representa una regla de firewall (Access Rules).
|
||
type CFFirewallRule struct {
|
||
ID string `json:"id"`
|
||
Notes string `json:"notes"`
|
||
AllowedModes []string `json:"allowed_modes"`
|
||
Mode string `json:"mode"`
|
||
Configuration CFFirewallCfg `json:"configuration"`
|
||
Scope CFScope `json:"scope"`
|
||
CreatedOn string `json:"created_on"`
|
||
ModifiedOn string `json:"modified_on"`
|
||
}
|
||
|
||
// CFFirewallCfg configuración del target en la regla.
|
||
type CFFirewallCfg struct {
|
||
Target string `json:"target"`
|
||
Value string `json:"value"`
|
||
}
|
||
|
||
// CFScope scope de la regla (zone o account).
|
||
type CFScope struct {
|
||
ID string `json:"id"`
|
||
Name string `json:"name"`
|
||
Type string `json:"type"`
|
||
}
|
||
|
||
// ─── Métodos de la API ───────────────────────────────────────────────────────
|
||
|
||
// GetUser obtiene la información del usuario autenticado.
|
||
func (c *CloudflareClient) GetUser() (*CFUser, error) {
|
||
var user CFUser
|
||
if err := c.get("/user", &user); err != nil {
|
||
return nil, err
|
||
}
|
||
return &user, nil
|
||
}
|
||
|
||
// GetZones lista todas las zonas de la cuenta.
|
||
func (c *CloudflareClient) GetZones() ([]CFZone, error) {
|
||
var zones []CFZone
|
||
// pagina 1, 50 por página (máximo)
|
||
if err := c.get("/zones?per_page=50&page=1", &zones); err != nil {
|
||
return nil, err
|
||
}
|
||
return zones, nil
|
||
}
|
||
|
||
// GetDNSRecords devuelve los registros DNS de una zona.
|
||
func (c *CloudflareClient) GetDNSRecords(zoneID string) ([]CFDNSRecord, error) {
|
||
var records []CFDNSRecord
|
||
path := fmt.Sprintf("/zones/%s/dns_records?per_page=100&page=1", zoneID)
|
||
if err := c.get(path, &records); err != nil {
|
||
return nil, err
|
||
}
|
||
return records, nil
|
||
}
|
||
|
||
// GetSSLCertificates devuelve los certificate packs de una zona.
|
||
func (c *CloudflareClient) GetSSLCertificates(zoneID string) ([]CFSSLStatus, error) {
|
||
var certs []CFSSLStatus
|
||
path := fmt.Sprintf("/zones/%s/ssl/certificate_packs", zoneID)
|
||
if err := c.get(path, &certs); err != nil {
|
||
return nil, err
|
||
}
|
||
return certs, nil
|
||
}
|
||
|
||
// GetFirewallRules devuelve las reglas de acceso IP de una zona.
|
||
func (c *CloudflareClient) GetFirewallRules(zoneID string) ([]CFFirewallRule, error) {
|
||
var rules []CFFirewallRule
|
||
path := fmt.Sprintf("/zones/%s/firewall/access_rules/rules?per_page=50&page=1", zoneID)
|
||
if err := c.get(path, &rules); err != nil {
|
||
return nil, err
|
||
}
|
||
return rules, nil
|
||
}
|