Files
soft_usite/pkg/services/cloudflare_service.go
T

215 lines
6.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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
}