qdm12 / ddns-updater

Container to update DNS records periodically with WebUI for many DNS providers
https://hub.docker.com/r/qmcgaw/ddns-updater/
MIT License
1.62k stars 158 forks source link

Feature request: Add Aliyun DNS as provider #198

Closed zzzhouuu closed 2 years ago

zzzhouuu commented 3 years ago
  1. What's the feature? Include Aliyun DNS as provider please

  2. Extra information? Thanks. Many thanks for all.

  3. Why do you need this feature? I have a home server with Aliyun DNS and Aliyun Domains and would be awesome to have automatic ddns update.

fredericrous commented 3 years ago

Aliyun is Alibaba cloud, correct? Am I looking at the correct documentation? https://www.alibabacloud.com/help/doc-detail/29772.htm?spm=a2c63.p38356.b99.91.badb2d7bjYrGPW

qdm12 commented 3 years ago

Thanks @fredericrous! Let me know if you're on it 😉 I'm still a few days away from it (in my work queue, working on other repos right now) anyway.

fredericrous commented 3 years ago

@qdm12 I should have spare time end of next week to work on this. I did more digging. Alibaba cloud has a SDK: https://github.com/aliyun/alibaba-cloud-sdk-go/blob/master/services/alidns/update_domain_record.go documentation: https://www.alibabacloud.com/help/doc-detail/66217.htm

zzzhouuu commented 3 years ago
package settings

import (
    "context"
    "encoding/json"
    "fmt"
    "github.com/aliyun/alibaba-cloud-sdk-go/sdk"
    "github.com/aliyun/alibaba-cloud-sdk-go/services/alidns"
    "github.com/qdm12/ddns-updater/internal/models"
    "github.com/qdm12/ddns-updater/internal/regex"
    "github.com/qdm12/ddns-updater/pkg/publicip/ipversion"
    "net"
    "net/http"
)

type aliyun struct {
    domain          string
    host            string
    ipVersion       ipversion.IPVersion
    regionId        string
    accessKeyId     string
    accessKeySecret string
}

func NewAliyun(data json.RawMessage, domain, host string, ipVersion ipversion.IPVersion,
    _ regex.Matcher) (s Settings, err error) {
    extraSettings := struct {
        RegionId        string `json:"region_id"`
        AccessKeyId     string `json:"access_key_id"`
        AccessKeySecret string `json:"access_key_secret"`
    }{}
    if err := json.Unmarshal(data, &extraSettings); err != nil {
        return nil, err
    }
    d := &aliyun{
        domain:          domain,
        host:            host,
        ipVersion:       ipVersion,
        regionId:        extraSettings.RegionId,
        accessKeyId:     extraSettings.AccessKeyId,
        accessKeySecret: extraSettings.AccessKeySecret,
    }
    if err := d.isValid(); err != nil {
        return nil, err
    }
    return d, nil
}

func (d *aliyun) isValid() error {
    switch {
    case len(d.accessKeyId) == 0:
        return ErrEmptyAccessKeyId
    case len(d.accessKeySecret) == 0:
        return ErrEmptyAccessKeySecret
    case d.host == "*":
        return ErrHostWildcard
    }
    return nil
}

func (d *aliyun) String() string {
    return fmt.Sprintf("[domain: %s | host: %s | provider: aliyun]", d.domain, d.host)
}

func (d *aliyun) Domain() string {
    return d.domain
}

func (d *aliyun) Host() string {
    return d.host
}

func (d *aliyun) IPVersion() ipversion.IPVersion {
    return d.ipVersion
}

func (d *aliyun) Proxied() bool {
    return false
}

func (d *aliyun) BuildDomainName() string {
    return buildDomainName(d.host, d.domain)
}

func (d *aliyun) newClient() *sdk.Client {
    client, err := sdk.NewClientWithAccessKey(d.regionId, d.accessKeyId, d.accessKeySecret)
    if err != nil {
        // Handle exceptions
        panic(err)
    }
    return client
}

func (d *aliyun) HTML() models.HTMLRow {
    return models.HTMLRow{
        Domain:    models.HTML(fmt.Sprintf("<a href=\"http://%s\">%s</a>", d.BuildDomainName(), d.BuildDomainName())),
        Host:      models.HTML(d.Host()),
        Provider:  "<a href=\"https://aliyun.com/\">AliDNS</a>",
        IPVersion: models.HTML(d.ipVersion.String()),
    }
}

func (d *aliyun) getRecord(recordType string, client *sdk.Client) (record alidns.Record, err error) {
    request := alidns.CreateDescribeSubDomainRecordsRequest()
    request.SubDomain = d.BuildDomainName()
    request.Type = recordType

    response := alidns.CreateDescribeSubDomainRecordsResponse()
    err = client.DoAction(request, response)
    if err != nil {
        return alidns.Record{}, err
    }

    if len(response.DomainRecords.Record) == 0 {
        return alidns.Record{}, ErrRecordNotFound
    } else if response.DomainRecords.Record[0].RecordId == "" {
        return alidns.Record{}, ErrRecordIDNotFound
    }

    return response.DomainRecords.Record[0], nil
}

func (d *aliyun) Update(ctx context.Context, client *http.Client, ip net.IP) (newIP net.IP, err error) {
    recordType := A
    var ipStr string
    if ip.To4() == nil { // IPv6
        recordType = AAAA
        ipStr = ip.To16().String()
    } else {
        ipStr = ip.To4().String()
    }

    aliClient := d.newClient()
    record, err := d.getRecord(recordType, aliClient)
    if err != nil {
        return nil, fmt.Errorf("%s: %w", ErrGetRecordID, err)
    }

    if record.Value == ipStr {
        return ip, nil
    }

    request := alidns.CreateUpdateDomainRecordRequest()
    request.RR = d.host
    request.RecordId = record.RecordId
    request.Type = recordType
    request.Value = ipStr

    response := alidns.CreateUpdateDomainRecordResponse()

    err = aliClient.DoAction(request, response)
    if err != nil {
        return nil, err
    }

    return ip, nil
}
fredericrous commented 3 years ago

nice work @zzzhouuu could you create a PR that way will begin the review process. Also I don't think ErrHostWildcard is relevant for alidns. It's possible to set a wildcard with this api, isn't it? therefore I would let the possibility to set it.

qdm12 commented 3 years ago

Thanks! I can do it right now as a PR don't worry 😉 be back soon with a test image

qdm12 commented 3 years ago

Actually I'll use https://www.alibabacloud.com/help/doc-detail/34272.html?spm=a2c5t.11065259.1996646101.searchclickresult.56ec6428yZ0qoE directly to have one less dependency.

I'm currently fighting against vulnerabilities on other repos due to dependencies, so the less the better, especially since Alibaba's one seems rather huge.

fredericrous commented 3 years ago

yeah idk, normally Go compiler should compile only what's needed so even if the lib is huge this shouldn't impact more than necessary the size of the binary. Regarding vulnerabilities...alibaba's sdk looks pretty active but that's true that among the dependencies it depends on there are some old ones that doesn't look active like github.com/davecgh/go-spew . Apparently authentication is not hard to do see https://github.com/TimothyYe/godns/blob/master/handler/alidns/alidns.go

qdm12 commented 2 years ago

Support added in #252 through the SDK. I'll eventually get to removing the SDK dependency because I'm stubborn 😄

qdm12 commented 2 years ago

I reworked the code in 8a14828cdc5a6e311788f479c5745ff26ce6aebe to have no SDK dependency and only do http calls to their API directly. The Docker image is available qmcgaw/ddns-updater:aliyun-nosdk, maybe @zzzhouuu can you try it sometime?

Also thanks @fredericrous the links you posted helped me quite a bit 😉

qdm12 commented 3 months ago

@zzzhouuu does the current ddns-updater program work today with Aliyun? Thanks!

zzzhouuu commented 3 months ago

@zzzhouuu does the current ddns-updater program work today with Aliyun? Thanks!

I tested it to work.

========================================
========================================
============= ddns-updater =============
========================================
=========== Made with ❤️ by ============
======= https://github.com/qdm12 =======
========================================
========================================

Running version latest built on 2024-06-15T09:57:45.623Z (commit 3494cfb)

🔧 Need help? https://github.com/qdm12/ddns-updater/discussions/new
🐛 Bug? https://github.com/qdm12/ddns-updater/issues/new
✨ New feature? https://github.com/qdm12/ddns-updater/issues/new
☕ Discussion? https://github.com/qdm12/ddns-updater/discussions/new
💻 Email? quentin.mcgaw@gmail.com
💰 Help me? https://www.paypal.me/qmcgaw https://github.com/sponsors/qdm12
2024-06-15T10:39:40Z INFO Settings summary:
├── HTTP client
|   └── Timeout: 10s
├── Update
|   ├── Period: 5m0s
|   └── Cooldown: 5m0s
├── Public IP fetching
|   ├── HTTP enabled: yes
|   ├── HTTP IP providers
|   |   └── all
|   ├── HTTP IPv4 providers
|   |   └── all
|   ├── HTTP IPv6 providers
|   |   └── all
|   ├── DNS enabled: yes
|   ├── DNS timeout: 3s
|   └── DNS over TLS providers
|       └── all
├── Resolver: use Go default resolver
├── Server
|   ├── Listening address: :8000
|   └── Root URL: /
├── Health
|   └── Server listening address: 127.0.0.1:9999
├── Paths
|   └── Data directory: /updater/data
├── Backup: disabled
└── Logger
    ├── Level: info
    └── Caller: hidden
2024-06-15T10:39:40Z INFO reading JSON config from file /updater/data/config.json
2024-06-15T10:39:40Z INFO Found single setting to update record
2024-06-15T10:39:40Z INFO Reading history from database: domain xxxx.xxx host @ ipv4
2024-06-15T10:39:40Z INFO [healthcheck server] health http server listening on 127.0.0.1:9999
2024-06-15T10:39:40Z INFO [http server] http server listening on [::]:8000
2024-06-15T10:39:40Z INFO [backup] disabled
2024-06-15T10:39:41Z INFO ipv4 address of xxxx.xxx is 10.0.0.136 and your ipv4 address  is xx.xx.xx.xx
2024-06-15T10:39:41Z INFO Updating record [domain: xxxx.xxx | host: @ | provider: aliyun | ip: ipv4] to use xx.xx.xx.xx
2024-06-15T10:39:41Z ERROR updating record: HTTP status is not valid: 400: {"RequestId":"3CD076DF-14DD-578F-AFF5-AACF44F2C433","HostId":"alidns.aliyuncs.com","Code":"DomainRecordDuplicate","Message":"The DNS record already exists.","Recommend":"https://api.aliyun.com/troubleshoot?q=DomainRecordDuplicate&product=Alidns&requestId=3CD076DF-14DD-578F-AFF5-AACF44F2C433"}
2024-06-15T10:44:41Z INFO ipv4 address of xxxx.xxx is 10.0.0.136 and your ipv4 address  is xx.xx.xx.xx
2024-06-15T10:44:41Z INFO Updating record [domain: xxxx.xxx | host: @ | provider: aliyun | ip: ipv4] to use xx.xx.xx.xx
qdm12 commented 3 months ago

Thank you so much @zzzhouuu glad it's working fine (I was wondering if it was working ok!) 👍 Enjoy!

a-w-1806 commented 2 months ago

Folks, is it expected if I see "Code":"DomainRecordDuplicate","Message":"The DNS record already exists." but can confirm ddns-updater writes to Aliyun successfully?

qdm12 commented 2 months ago

@a-w-1806 which program version are you using?