Skip to content

Commit

Permalink
feat: add cloudflare support
Browse files Browse the repository at this point in the history
  • Loading branch information
davidramiro committed Jun 23, 2023
1 parent b30c09c commit 936001c
Show file tree
Hide file tree
Showing 5 changed files with 200 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,5 @@ dist/
go.work

config.yml

.idea/
7 changes: 5 additions & 2 deletions config.sample.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
gandi:
baseurl: https://dns.api.gandi.net/api/v5
ttl: 300
ttl: 1800
porkbun:
baseurl: https://porkbun.com/api/json/v3
ttl: 600
ttl: 1800
cloudflare:
baseurl: https://api.cloudflare.com/client/v4
ttl: 1800
api:
port: 9595
hideApiKeyInLogs: true
Expand Down
13 changes: 13 additions & 0 deletions internal/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package api

import (
"fmt"
"github.com/davidramiro/frigabun/pkg/cloudflare"
"net/http"
"strings"

Expand Down Expand Up @@ -78,6 +79,18 @@ func HandleUpdateRequest(c echo.Context) error {
if err != nil {
return c.String(err.Code, err.Message)
}
} else if request.Registrar == "cloudflare" {
dns := &cloudflare.CloudflareDns{
IP: request.IP,
Domain: request.Domain,
Subdomain: subdomains[i],
ApiKey: request.ApiSecretKey,
ZoneId: request.ApiKey,
}
err := dns.SaveRecord()
if err != nil {
return c.String(err.Code, err.Message)
}
} else {
return c.String(http.StatusBadRequest, "missing or invalid registrar")
}
Expand Down
5 changes: 5 additions & 0 deletions internal/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@ type Config struct {
TTL int `yaml:"ttl"`
} `yaml:"porkbun"`

Cloudflare struct {
BaseUrl string `yaml:"baseurl"`
TTL int `yaml:"ttl"`
} `yaml:"cloudflare"`

Api struct {
Port string `yaml:"port"`
ApiKeyHidden bool `yaml:"hideApiKeyInLogs"`
Expand Down
175 changes: 175 additions & 0 deletions pkg/cloudflare/cloudflare.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package cloudflare

import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"

"github.com/davidramiro/frigabun/internal/config"
"github.com/davidramiro/frigabun/internal/logger"
)

type CloudflareDns struct {
IP string
Domain string
Subdomain string
ZoneId string
ApiKey string
}

type CloudflareApiRequest struct {
Subdomain string `json:"name"`
Type string `json:"type"`
TTL int `json:"ttl"`
IP string `json:"content"`
}

type CloudflareQueryResponse struct {
Errors []struct {
Message string `json:"message"`
} `json:"errors"`
Result []struct {
Name string `json:"name"`
Id string `json:"id"`
} `json:"result"`
}

type CloudflareUpdateError struct {
Code int
Message string
}

func (c *CloudflareDns) SaveRecord() *CloudflareUpdateError {

endpoint := fmt.Sprintf("%s/zones/%s/dns_records", config.AppConfig.Cloudflare.BaseUrl,
c.ZoneId)

req, err := http.NewRequest("GET", endpoint, nil)

var r CloudflareQueryResponse

req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Authorization", "Bearer "+c.ApiKey)

client := &http.Client{}

resp, err := client.Do(req)
if err != nil {
return &CloudflareUpdateError{Code: 400, Message: "error getting cloudflare request"}
}

b, _ := io.ReadAll(resp.Body)
err = json.Unmarshal(b, &r)

if resp.StatusCode != http.StatusOK || len(r.Errors) > 0 || err != nil {

logger.Log.Error().Msg("could not query record:" + string(b))
return &CloudflareUpdateError{400, "could not query record: " + string(b)}
}

var id string

if len(r.Errors) == 0 && len(r.Result) > 0 {
for _, e := range r.Result {
if e.Name == c.Subdomain+"."+c.Domain {
id = e.Id
}
}
}

if len(id) == 0 {
return c.NewRecord()
} else {
return c.UpdateRecord(id)
}
}

func (c *CloudflareDns) NewRecord() *CloudflareUpdateError {
cloudflareRequest := &CloudflareApiRequest{
Subdomain: fmt.Sprintf("%s.%s", c.Subdomain, c.Domain),
IP: c.IP,
TTL: config.AppConfig.Cloudflare.TTL,
Type: "A",
}

endpoint := fmt.Sprintf("%s/zones/%s/dns_records", config.AppConfig.Cloudflare.BaseUrl,
c.ZoneId)
logger.Log.Info().Str("subdomain", cloudflareRequest.Subdomain).Str("endpoint", endpoint).Str("IP", cloudflareRequest.IP).Msg("building update request")

body, err := json.Marshal(cloudflareRequest)
if err != nil {
logger.Log.Error().Err(err).Msg("marshalling failed")
return &CloudflareUpdateError{Code: 400, Message: "could not parse request"}
}

req, err := http.NewRequest("POST", endpoint, bytes.NewBuffer(body))
if err != nil {
logger.Log.Error().Err(err).Msg("building request failed failed")
return &CloudflareUpdateError{Code: 400, Message: "could not create request for cloudflare"}
}

req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Authorization", "Bearer "+c.ApiKey)

client := &http.Client{}

resp, err := client.Do(req)
if err != nil {
logger.Log.Error().Err(err).Msg("executing request failed")
return &CloudflareUpdateError{Code: 500, Message: "could execute request"}
}

if resp.StatusCode != 200 {
b, _ := io.ReadAll(resp.Body)
logger.Log.Error().Msg("gandi rejected request")
return &CloudflareUpdateError{Code: resp.StatusCode, Message: "cloudflare rejected request: " + string(b)}
}

return nil
}

func (c *CloudflareDns) UpdateRecord(id string) *CloudflareUpdateError {
cloudflareRequest := &CloudflareApiRequest{
Subdomain: fmt.Sprintf("%s.%s", c.Subdomain, c.Domain),
IP: c.IP,
TTL: config.AppConfig.Cloudflare.TTL,
Type: "A",
}

endpoint := fmt.Sprintf("%s/zones/%s/dns_records/%s", config.AppConfig.Cloudflare.BaseUrl,
c.ZoneId, id)
logger.Log.Info().Str("subdomain", cloudflareRequest.Subdomain).Str("endpoint", endpoint).Str("IP", cloudflareRequest.IP).Msg("building update request")

body, err := json.Marshal(cloudflareRequest)
if err != nil {
logger.Log.Error().Err(err).Msg("marshalling failed")
return &CloudflareUpdateError{Code: 400, Message: "could not parse request"}
}

req, err := http.NewRequest("PUT", endpoint, bytes.NewBuffer(body))
if err != nil {
logger.Log.Error().Err(err).Msg("building request failed failed")
return &CloudflareUpdateError{Code: 400, Message: "could not create request for cloudflare"}
}

req.Header.Set("Content-Type", "application/json; charset=utf-8")
req.Header.Set("Authorization", "Bearer "+c.ApiKey)

client := &http.Client{}

resp, err := client.Do(req)
if err != nil {
logger.Log.Error().Err(err).Msg("executing request failed")
return &CloudflareUpdateError{Code: 500, Message: "could execute request"}
}

if resp.StatusCode != 200 {
b, _ := io.ReadAll(resp.Body)
logger.Log.Error().Msg("gandi rejected request")
return &CloudflareUpdateError{Code: resp.StatusCode, Message: "cloudflare rejected request: " + string(b)}
}

return nil
}

0 comments on commit 936001c

Please sign in to comment.