From d2a6de86b4b8b115f174bc39d490551403f0ccda Mon Sep 17 00:00:00 2001 From: DeveloperDurp Date: Fri, 14 Mar 2025 22:12:50 +0000 Subject: [PATCH 1/6] Configure Container Scanning in `.gitlab-ci.yml`, creating this file if it does not already exist --- .gitlab-ci.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 195d65e..0c23d83 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,9 +5,16 @@ # Container Scanning customization: https://docs.gitlab.com/ee/user/application_security/container_scanning/#customizing-the-container-scanning-settings # Note that environment variables can be set in several places # See https://docs.gitlab.com/ee/ci/variables/#cicd-variable-precedence + +# container_scanning: +# variables: +# DOCKER_IMAGE: ... +# DOCKER_USER: ... +# DOCKER_PASSWORD: ... stages: - test sast: stage: test include: - template: Security/SAST.gitlab-ci.yml +- template: Jobs/Container-Scanning.gitlab-ci.yml From 9c579ad13eb53f08851518c00f214f2ed37c191e Mon Sep 17 00:00:00 2001 From: DeveloperDurp Date: Fri, 14 Mar 2025 22:13:49 +0000 Subject: [PATCH 2/6] Configure Secret Detection in `.gitlab-ci.yml`, creating this file if it does not already exist --- .gitlab-ci.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 195d65e..e35fec0 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,3 +11,4 @@ sast: stage: test include: - template: Security/SAST.gitlab-ci.yml +- template: Security/Secret-Detection.gitlab-ci.yml From 94467587395bb90a201adf87e413de393d333163 Mon Sep 17 00:00:00 2001 From: DeveloperDurp Date: Sat, 15 Mar 2025 09:57:09 -0500 Subject: [PATCH 3/6] initial commit --- .gitignore | 2 + go.mod | 16 +++++ go.sum | 16 +++++ main.go | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 212 insertions(+) create mode 100644 .gitignore create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f08d9d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.env +/.idea/ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b838d5a --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module gitlab.com/durfy/durpdns + +go 1.24.1 + +require ( + github.com/caarlos0/env/v6 v6.10.1 + github.com/cloudflare/cloudflare-go/v4 v4.1.0 + github.com/joho/godotenv v1.5.1 +) + +require ( + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/tidwall/sjson v1.2.5 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..19e576b --- /dev/null +++ b/go.sum @@ -0,0 +1,16 @@ +github.com/caarlos0/env/v6 v6.10.1 h1:t1mPSxNpei6M5yAeu1qtRdPAK29Nbcf/n3G7x+b3/II= +github.com/caarlos0/env/v6 v6.10.1/go.mod h1:hvp/ryKXKipEkcuYjs9mI4bBCg+UI0Yhgm5Zu0ddvwc= +github.com/cloudflare/cloudflare-go/v4 v4.1.0 h1:1SjQZaPbUe23fSoCuMuN7EblVo+RIldNGd4pfkPCpW4= +github.com/cloudflare/cloudflare-go/v4 v4.1.0/go.mod h1:XcYpLe7Mf6FN87kXzEWVnJ6z+vskW/k6eUqgqfhFE9k= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/tidwall/gjson v1.14.2/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY= +github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28= diff --git a/main.go b/main.go new file mode 100644 index 0000000..3b39ff3 --- /dev/null +++ b/main.go @@ -0,0 +1,178 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "github.com/caarlos0/env/v6" + "github.com/cloudflare/cloudflare-go/v4" + "github.com/cloudflare/cloudflare-go/v4/dns" + "github.com/cloudflare/cloudflare-go/v4/option" + "github.com/joho/godotenv" + "io" + "log" + "net/http" + "time" +) + +type IPResponse struct { + IPAddress string `json:"YourFuckingIPAddress"` + Location string `json:"YourFuckingLocation"` + Hostname string `json:"YourFuckingHostname"` + ISP string `json:"YourFuckingISP"` + TorExit bool `json:"YourFuckingTorExit"` + City string `json:"YourFuckingCity"` + Country string `json:"YourFuckingCountry"` + CountryCode string `json:"YourFuckingCountryCode"` +} + +type Config struct { + CloudflareZoneID string `env:"CLOUDFLARE_ZONE_ID"` + CloudFlareAPIKey string `env:"CLOUDFLARE_API_KEY"` + CloudflareEmail string `env:"CLOUDFLARE_EMAIL"` +} + +func main() { + + err := godotenv.Load(".env") + if err != nil { + log.Fatal("Failed to load env file") + return + } + + config := &Config{} + err = env.Parse(config) + if err != nil { + log.Fatal("Failed to load env file") + return + } + + if config.CloudflareEmail == "" || config.CloudFlareAPIKey == "" || config.CloudflareZoneID == "" { + log.Fatal("Email, API and ZoneID need to be set") + return + } + + client := cloudflare.NewClient( + option.WithAPIKey(config.CloudFlareAPIKey), + option.WithAPIEmail(config.CloudflareEmail), + ) + + ExistingIP, err := getIP() + if err != nil { + fmt.Println(err) + return + } + + fmt.Println("Your public IP address is:", ExistingIP.IPAddress) + +do: + for { + CurrentIP, err := getIP() + if err != nil { + fmt.Println(err) + break + } + + if ExistingIP.IPAddress != CurrentIP.IPAddress { + ExistingIP = CurrentIP + fmt.Println( + "Your public IP address has updated:", + ExistingIP.IPAddress, + ) + + err := updateDNSRecords( + client, + config.CloudflareZoneID, + CurrentIP.IPAddress, + ) + if err != nil { + fmt.Println(err) + break + } + + } else { + fmt.Println("Your public IP address has not changed.") + } + + time.Sleep(10 * time.Second) + continue do + } +} + +func getIP() (*IPResponse, error) { + + var ip IPResponse + resp, err := http.Get("https://myip.wtf/json") + if err != nil { + return nil, err + } + defer resp.Body.Close() + + data, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + err = json.Unmarshal(data, &ip) + return &ip, err +} + +func getDNSRecords( + client *cloudflare.Client, + zoneID string, +) (*[]dns.RecordResponse, error) { + dnslist, err := client.DNS.Records.List( + context.TODO(), + dns.RecordListParams{ + ZoneID: cloudflare.F(zoneID), + }, + ) + if err != nil { + return nil, err + } + + var records []dns.RecordResponse + for _, item := range dnslist.Result { + + if item.Type == "A" { + records = append(records, item) + } + } + return &records, nil +} + +func updateDNSRecords( + client *cloudflare.Client, + zoneID string, + ipAddress string, +) error { + records, err := getDNSRecords(client, zoneID) + if err != nil { + return err + } + + for _, record := range *records { + + NewDNS := dns.ARecordParam{ + Comment: cloudflare.F(record.Comment), + Content: cloudflare.F(ipAddress), + Name: cloudflare.F(record.Name), + Proxied: cloudflare.F(record.Proxied), + TTL: cloudflare.F(record.TTL), + Type: cloudflare.F(dns.ARecordTypeA), + } + + _, err = client.DNS.Records.Edit( + context.TODO(), record.ID, dns.RecordEditParams{ + ZoneID: cloudflare.F(zoneID), + Record: NewDNS, + }, + ) + if err != nil { + return err + } + + fmt.Printf("Updated IP address for %s\n", record.Name) + } + return nil +} From 06c9569e82e2ea78de353349a11c6cf802563a5e Mon Sep 17 00:00:00 2001 From: DeveloperDurp Date: Sat, 15 Mar 2025 12:16:00 -0500 Subject: [PATCH 4/6] Add timeout --- main.go | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/main.go b/main.go index 3b39ff3..69848ab 100644 --- a/main.go +++ b/main.go @@ -63,6 +63,15 @@ func main() { return } + err = updateDNSRecords( + client, + config.CloudflareZoneID, + ExistingIP.IPAddress, + ) + if err != nil { + fmt.Println(err) + } + fmt.Println("Your public IP address is:", ExistingIP.IPAddress) do: @@ -70,7 +79,7 @@ do: CurrentIP, err := getIP() if err != nil { fmt.Println(err) - break + continue do } if ExistingIP.IPAddress != CurrentIP.IPAddress { @@ -101,8 +110,11 @@ do: func getIP() (*IPResponse, error) { + client := &http.Client{ + Timeout: time.Second * 5, + } var ip IPResponse - resp, err := http.Get("https://myip.wtf/json") + resp, err := client.Get("https://myip.wtf/json") if err != nil { return nil, err } From 03b36127db7b20a3406b151753ff9284c6441e32 Mon Sep 17 00:00:00 2001 From: DeveloperDurp Date: Sun, 16 Mar 2025 08:58:40 -0500 Subject: [PATCH 5/6] Updated to 3 providers to add more reliability --- main.go | 249 +++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 194 insertions(+), 55 deletions(-) diff --git a/main.go b/main.go index 69848ab..1ba50d3 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "context" "encoding/json" + "errors" "fmt" "github.com/caarlos0/env/v6" "github.com/cloudflare/cloudflare-go/v4" @@ -12,11 +13,14 @@ import ( "io" "log" "net/http" + "regexp" "time" ) -type IPResponse struct { - IPAddress string `json:"YourFuckingIPAddress"` +var ipPattern = `^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$` + +type WTFIPResponse struct { + IP string `json:"YourFuckingIPAddress"` Location string `json:"YourFuckingLocation"` Hostname string `json:"YourFuckingHostname"` ISP string `json:"YourFuckingISP"` @@ -26,10 +30,111 @@ type IPResponse struct { CountryCode string `json:"YourFuckingCountryCode"` } +type IpifyResponse struct { + IP string `json:"ip"` +} + +type IpInfoResponse struct { + IP string `json:"ip"` + Hostname string `json:"hostname"` + City string `json:"city"` + Region string `json:"region"` + Country string `json:"country"` + Loc string `json:"loc"` + Org string `json:"org"` + Postal string `json:"postal"` + Timezone string `json:"timezone"` + Readme string `json:"readme"` +} +type Response struct { + IPAddress string +} + +type ResponseInterface interface { + getIP() Response +} + +func (r *WTFIPResponse) getIP() Response { + return Response{IPAddress: r.IP} +} +func (r *IpInfoResponse) getIP() Response { + return Response{IPAddress: r.IP} +} +func (r *IpifyResponse) getIP() Response { + return Response{IPAddress: r.IP} +} + +func NewIpifyProvider() *IpProvider { + return &IpProvider{ + url: "https://api.ipify.org?format=json", + resp: new(IpifyResponse), + client: http.Client{ + Timeout: 5 * time.Second, + }, + } +} + +func NewIpInfoProvider() *IpProvider { + return &IpProvider{ + url: "https://ipinfo.io/json", + resp: new(IpInfoResponse), + client: http.Client{ + Timeout: 5 * time.Second, + }, + } +} + +func NewWTFProvider() *IpProvider { + return &IpProvider{ + url: "https://myip.wtf/json", + resp: new(WTFIPResponse), + client: http.Client{ + Timeout: 5 * time.Second, + }, + } +} + +type IpProvider struct { + url string + resp interface{} + client http.Client +} + +func (p *IpProvider) get() *Response { + + body, err := p.client.Get(p.url) + if err != nil { + return nil + } + defer body.Body.Close() + + data, err := io.ReadAll(body.Body) + if err != nil { + return nil + } + + err = json.Unmarshal(data, &p.resp) + if err != nil { + return nil + } + + resp := p.resp.(ResponseInterface).getIP() + pattern := regexp.MustCompile(ipPattern) + + if !pattern.MatchString(resp.IPAddress) { + return nil + } + + return &resp +} + type Config struct { CloudflareZoneID string `env:"CLOUDFLARE_ZONE_ID"` CloudFlareAPIKey string `env:"CLOUDFLARE_API_KEY"` CloudflareEmail string `env:"CLOUDFLARE_EMAIL"` + IpProviders []*IpProvider + Client *cloudflare.Client + Timeout time.Duration } func main() { @@ -40,7 +145,9 @@ func main() { return } - config := &Config{} + config := &Config{ + Timeout: 5 * time.Second, + } err = env.Parse(config) if err != nil { log.Fatal("Failed to load env file") @@ -52,81 +159,107 @@ func main() { return } - client := cloudflare.NewClient( + config.Client = cloudflare.NewClient( option.WithAPIKey(config.CloudFlareAPIKey), option.WithAPIEmail(config.CloudflareEmail), ) - ExistingIP, err := getIP() - if err != nil { - fmt.Println(err) - return - } + config.IpProviders = append( + config.IpProviders, + NewWTFProvider(), + ) + config.IpProviders = append( + config.IpProviders, + NewIpifyProvider(), + ) + config.IpProviders = append( + config.IpProviders, + NewIpInfoProvider(), + ) + + ExistingIP, err := GetIPAddress(config) + + fmt.Printf("The current IP address is: %s\n", ExistingIP) err = updateDNSRecords( - client, - config.CloudflareZoneID, - ExistingIP.IPAddress, + config, + ExistingIP, ) if err != nil { fmt.Println(err) } - fmt.Println("Your public IP address is:", ExistingIP.IPAddress) - do: for { - CurrentIP, err := getIP() + CurrentIP, err := GetIPAddress(config) if err != nil { fmt.Println(err) continue do } - if ExistingIP.IPAddress != CurrentIP.IPAddress { - ExistingIP = CurrentIP - fmt.Println( - "Your public IP address has updated:", - ExistingIP.IPAddress, - ) - - err := updateDNSRecords( - client, - config.CloudflareZoneID, - CurrentIP.IPAddress, - ) - if err != nil { - fmt.Println(err) - break - } - - } else { - fmt.Println("Your public IP address has not changed.") + if ExistingIP == CurrentIP { + time.Sleep(config.Timeout) + continue do } - time.Sleep(10 * time.Second) + ExistingIP = CurrentIP + fmt.Printf( + "Your IP address has changed: %s\n", + CurrentIP, + ) + + err = updateDNSRecords( + config, + CurrentIP, + ) + if err != nil { + fmt.Println(err) + } + + time.Sleep(config.Timeout) continue do } } -func getIP() (*IPResponse, error) { +func GetIPAddress(config *Config) (string, error) { - client := &http.Client{ - Timeout: time.Second * 5, - } - var ip IPResponse - resp, err := client.Get("https://myip.wtf/json") - if err != nil { - return nil, err - } - defer resp.Body.Close() + responses := []*Response{} - data, err := io.ReadAll(resp.Body) - if err != nil { - return nil, err + for _, provider := range config.IpProviders { + resp := provider.get() + responses = append(responses, resp) } - err = json.Unmarshal(data, &ip) - return &ip, err + // Count the occurrences of each IPAddress + counts := map[string]int{} + for _, r := range responses { + if r != nil { + IP := r.IPAddress + if count, ok := counts[IP]; ok { + counts[IP] = count + 1 + } else { + counts[IP] = 1 + } + } + } + + // Find the IPAddress that repeats the most + maxCount := 0 + CurrentIP := "" + for IP, count := range counts { + if count > maxCount { + maxCount = count + CurrentIP = IP + } + } + + pattern := regexp.MustCompile(ipPattern) + + if !pattern.MatchString(CurrentIP) { + return "", errors.New("Did not find a valid IP") + } + + return CurrentIP, nil } func getDNSRecords( @@ -154,17 +287,23 @@ func getDNSRecords( } func updateDNSRecords( - client *cloudflare.Client, - zoneID string, + config *Config, ipAddress string, ) error { - records, err := getDNSRecords(client, zoneID) + records, err := getDNSRecords( + config.Client, + config.CloudflareZoneID, + ) if err != nil { return err } for _, record := range *records { + if record.Content == ipAddress { + continue + } + NewDNS := dns.ARecordParam{ Comment: cloudflare.F(record.Comment), Content: cloudflare.F(ipAddress), @@ -174,9 +313,9 @@ func updateDNSRecords( Type: cloudflare.F(dns.ARecordTypeA), } - _, err = client.DNS.Records.Edit( + _, err = config.Client.DNS.Records.Edit( context.TODO(), record.ID, dns.RecordEditParams{ - ZoneID: cloudflare.F(zoneID), + ZoneID: cloudflare.F(config.CloudflareZoneID), Record: NewDNS, }, ) From 10f35901f135be5cef369b01b4664b8a923b6088 Mon Sep 17 00:00:00 2001 From: Renovate Bot Date: Fri, 9 May 2025 10:19:41 +0000 Subject: [PATCH 6/6] Add renovate.json --- renovate.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 renovate.json diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..7190a60 --- /dev/null +++ b/renovate.json @@ -0,0 +1,3 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json" +}