mirror of
https://github.com/nezhahq/nezha.git
synced 2025-01-22 20:58:14 -05:00
eb6dd2855e
* refactor ddns * update webhook
264 lines
6.9 KiB
Go
264 lines
6.9 KiB
Go
package ddns
|
||
|
||
import (
|
||
"bytes"
|
||
"crypto/hmac"
|
||
"crypto/sha256"
|
||
"encoding/hex"
|
||
"fmt"
|
||
"io"
|
||
"log"
|
||
"net/http"
|
||
"strconv"
|
||
"strings"
|
||
"time"
|
||
|
||
"github.com/naiba/nezha/pkg/utils"
|
||
)
|
||
|
||
const te = "https://dnspod.tencentcloudapi.com"
|
||
|
||
type ProviderTencentCloud struct {
|
||
secretID string
|
||
secretKey string
|
||
domainConfig *DomainConfig
|
||
resp *tcResp
|
||
}
|
||
|
||
type tcReq struct {
|
||
RecordType string `json:"RecordType"`
|
||
Domain string `json:"Domain"`
|
||
RecordLine string `json:"RecordLine"`
|
||
Subdomain string `json:"Subdomain,omitempty"`
|
||
SubDomain string `json:"SubDomain,omitempty"` // As is
|
||
Value string `json:"Value,omitempty"`
|
||
TTL uint32 `json:"TTL,omitempty"`
|
||
RecordId uint64 `json:"RecordId,omitempty"`
|
||
}
|
||
|
||
type tcResp struct {
|
||
Response struct {
|
||
RecordList []struct {
|
||
RecordId uint64
|
||
Value string
|
||
}
|
||
Error struct {
|
||
Code string
|
||
}
|
||
}
|
||
}
|
||
|
||
func NewProviderTencentCloud(id, key string) *ProviderTencentCloud {
|
||
return &ProviderTencentCloud{
|
||
secretID: id,
|
||
secretKey: key,
|
||
}
|
||
}
|
||
|
||
func (provider *ProviderTencentCloud) UpdateDomain(domainConfig *DomainConfig) error {
|
||
if domainConfig == nil {
|
||
return fmt.Errorf("获取 DDNS 配置失败")
|
||
}
|
||
provider.domainConfig = domainConfig
|
||
|
||
// 当IPv4和IPv6同时成功才算作成功
|
||
var err error
|
||
if provider.domainConfig.EnableIPv4 {
|
||
if err = provider.addDomainRecord(true); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
if provider.domainConfig.EnableIpv6 {
|
||
if err = provider.addDomainRecord(false); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
|
||
return err
|
||
}
|
||
|
||
func (provider *ProviderTencentCloud) addDomainRecord(isIpv4 bool) error {
|
||
err := provider.findDNSRecord(isIpv4)
|
||
if err != nil {
|
||
return fmt.Errorf("查找 DNS 记录时出错: %s", err)
|
||
}
|
||
|
||
if provider.resp.Response.Error.Code == "ResourceNotFound.NoDataOfRecord" { // 没有找到 DNS 记录
|
||
return provider.createDNSRecord(isIpv4)
|
||
} else if provider.resp.Response.Error.Code != "" {
|
||
return fmt.Errorf("查询 DNS 记录时出错,错误代码为: %s", provider.resp.Response.Error.Code)
|
||
}
|
||
|
||
// 默认情况下更新 DNS 记录
|
||
return provider.updateDNSRecord(isIpv4)
|
||
}
|
||
|
||
func (provider *ProviderTencentCloud) findDNSRecord(isIPv4 bool) error {
|
||
var ipType string
|
||
if isIPv4 {
|
||
ipType = "A"
|
||
} else {
|
||
ipType = "AAAA"
|
||
}
|
||
|
||
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
||
data := &tcReq{
|
||
RecordType: ipType,
|
||
Domain: realDomain,
|
||
RecordLine: "默认",
|
||
Subdomain: prefix,
|
||
}
|
||
|
||
jsonData, _ := utils.Json.Marshal(data)
|
||
body, err := provider.sendRequest("DescribeRecordList", jsonData)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
provider.resp = &tcResp{}
|
||
err = utils.Json.Unmarshal(body, provider.resp)
|
||
if err != nil {
|
||
return err
|
||
}
|
||
|
||
return nil
|
||
}
|
||
|
||
func (provider *ProviderTencentCloud) createDNSRecord(isIPv4 bool) error {
|
||
var ipType, ipAddr string
|
||
if isIPv4 {
|
||
ipType = "A"
|
||
ipAddr = provider.domainConfig.Ipv4Addr
|
||
} else {
|
||
ipType = "AAAA"
|
||
ipAddr = provider.domainConfig.Ipv6Addr
|
||
}
|
||
|
||
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
||
data := &tcReq{
|
||
RecordType: ipType,
|
||
RecordLine: "默认",
|
||
Domain: realDomain,
|
||
SubDomain: prefix,
|
||
Value: ipAddr,
|
||
TTL: 600,
|
||
}
|
||
|
||
jsonData, _ := utils.Json.Marshal(data)
|
||
_, err := provider.sendRequest("CreateRecord", jsonData)
|
||
return err
|
||
}
|
||
|
||
func (provider *ProviderTencentCloud) updateDNSRecord(isIPv4 bool) error {
|
||
var ipType, ipAddr string
|
||
if isIPv4 {
|
||
ipType = "A"
|
||
ipAddr = provider.domainConfig.Ipv4Addr
|
||
} else {
|
||
ipType = "AAAA"
|
||
ipAddr = provider.domainConfig.Ipv6Addr
|
||
}
|
||
|
||
prefix, realDomain := splitDomain(provider.domainConfig.FullDomain)
|
||
data := &tcReq{
|
||
RecordType: ipType,
|
||
RecordLine: "默认",
|
||
Domain: realDomain,
|
||
SubDomain: prefix,
|
||
Value: ipAddr,
|
||
TTL: 600,
|
||
RecordId: provider.resp.Response.RecordList[0].RecordId,
|
||
}
|
||
|
||
jsonData, _ := utils.Json.Marshal(data)
|
||
_, err := provider.sendRequest("ModifyRecord", jsonData)
|
||
return err
|
||
}
|
||
|
||
// 以下为辅助方法,如发送 HTTP 请求等
|
||
func (provider *ProviderTencentCloud) sendRequest(action string, data []byte) ([]byte, error) {
|
||
req, err := http.NewRequest("POST", te, bytes.NewBuffer(data))
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
req.Header.Set("Content-Type", "application/json")
|
||
req.Header.Set("X-TC-Version", "2021-03-23")
|
||
|
||
provider.signRequest(provider.secretID, provider.secretKey, req, action, string(data))
|
||
resp, err := utils.HttpClient.Do(req)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
defer func(Body io.ReadCloser) {
|
||
err := Body.Close()
|
||
if err != nil {
|
||
log.Printf("NEZHA>> 无法关闭HTTP响应体流: %s\n", err.Error())
|
||
}
|
||
}(resp.Body)
|
||
|
||
body, err := io.ReadAll(resp.Body)
|
||
if err != nil {
|
||
return nil, err
|
||
}
|
||
|
||
return body, nil
|
||
}
|
||
|
||
// https://github.com/jeessy2/ddns-go/blob/master/util/tencent_cloud_signer.go
|
||
|
||
func (provider *ProviderTencentCloud) sha256hex(s string) string {
|
||
b := sha256.Sum256([]byte(s))
|
||
return hex.EncodeToString(b[:])
|
||
}
|
||
|
||
func (provider *ProviderTencentCloud) hmacsha256(s, key string) string {
|
||
hashed := hmac.New(sha256.New, []byte(key))
|
||
hashed.Write([]byte(s))
|
||
return string(hashed.Sum(nil))
|
||
}
|
||
|
||
func (provider *ProviderTencentCloud) WriteString(strs ...string) string {
|
||
var b strings.Builder
|
||
for _, str := range strs {
|
||
b.WriteString(str)
|
||
}
|
||
|
||
return b.String()
|
||
}
|
||
|
||
func (provider *ProviderTencentCloud) signRequest(secretId string, secretKey string, r *http.Request, action string, payload string) {
|
||
algorithm := "TC3-HMAC-SHA256"
|
||
service := "dnspod"
|
||
host := provider.WriteString(service, ".tencentcloudapi.com")
|
||
timestamp := time.Now().Unix()
|
||
timestampStr := strconv.FormatInt(timestamp, 10)
|
||
|
||
// 步骤 1:拼接规范请求串
|
||
canonicalHeaders := provider.WriteString("content-type:application/json\nhost:", host, "\nx-tc-action:", strings.ToLower(action), "\n")
|
||
signedHeaders := "content-type;host;x-tc-action"
|
||
hashedRequestPayload := provider.sha256hex(payload)
|
||
canonicalRequest := provider.WriteString("POST\n/\n\n", canonicalHeaders, "\n", signedHeaders, "\n", hashedRequestPayload)
|
||
|
||
// 步骤 2:拼接待签名字符串
|
||
date := time.Unix(timestamp, 0).UTC().Format("2006-01-02")
|
||
credentialScope := provider.WriteString(date, "/", service, "/tc3_request")
|
||
hashedCanonicalRequest := provider.sha256hex(canonicalRequest)
|
||
string2sign := provider.WriteString(algorithm, "\n", timestampStr, "\n", credentialScope, "\n", hashedCanonicalRequest)
|
||
|
||
// 步骤 3:计算签名
|
||
secretDate := provider.hmacsha256(date, provider.WriteString("TC3", secretKey))
|
||
secretService := provider.hmacsha256(service, secretDate)
|
||
secretSigning := provider.hmacsha256("tc3_request", secretService)
|
||
signature := hex.EncodeToString([]byte(provider.hmacsha256(string2sign, secretSigning)))
|
||
|
||
// 步骤 4:拼接 Authorization
|
||
authorization := provider.WriteString(algorithm, " Credential=", secretId, "/", credentialScope, ", SignedHeaders=", signedHeaders, ", Signature=", signature)
|
||
|
||
r.Header.Add("Authorization", authorization)
|
||
r.Header.Set("Host", host)
|
||
r.Header.Set("X-TC-Action", action)
|
||
r.Header.Add("X-TC-Timestamp", timestampStr)
|
||
}
|