域名解析增加EdgeDNS API

This commit is contained in:
GoEdgeLab
2022-09-08 19:36:32 +08:00
parent a71333b93c
commit 80f34d8298
5 changed files with 613 additions and 67 deletions

View File

@@ -0,0 +1,441 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package dnsclients
import (
"bytes"
"crypto/tls"
"encoding/json"
"errors"
teaconst "github.com/TeaOSLab/EdgeAPI/internal/const"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/edgeapi"
"github.com/iwind/TeaGo/maps"
"github.com/iwind/TeaGo/types"
"io"
"net/http"
"regexp"
"strings"
"time"
)
var edgeDNSHTTPClient = &http.Client{
Timeout: 10 * time.Second,
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
type EdgeDNSAPIProvider struct {
host string
accessKeyId string
accessKeySecret string
role string // admin | user
accessToken string
accessTokenExpiresAt int64
}
// Auth 认证
func (this *EdgeDNSAPIProvider) Auth(params maps.Map) error {
this.role = params.GetString("role")
this.host = params.GetString("host")
this.accessKeyId = params.GetString("accessKeyId")
this.accessKeySecret = params.GetString("accessKeySecret")
if len(this.role) == 0 {
this.role = "user"
}
if len(this.host) == 0 {
return errors.New("'host' should not be empty")
}
if !regexp.MustCompile(`^(?i)(http|https):`).MatchString(this.host) {
this.host = "http://" + this.host
}
if len(this.accessKeyId) == 0 {
return errors.New("'accessKeyId' should not be empty")
}
if len(this.accessKeySecret) == 0 {
return errors.New("'accessKeySecret' should not be empty")
}
return nil
}
// GetDomains 获取所有域名列表
func (this *EdgeDNSAPIProvider) GetDomains() (domains []string, err error) {
var offset = 0
var size = 100
for {
var resp = &edgeapi.ListNSDomainsResponse{}
err = this.doAPI("/NSDomainService/ListNSDomains", map[string]any{
"offset": offset,
"size": size,
}, resp)
if err != nil {
return
}
for _, domain := range resp.Data.NSDomains {
domains = append(domains, domain.Name)
}
if len(resp.Data.NSDomains) < size {
break
}
offset += size
}
return
}
// GetRecords 获取域名解析记录列表
func (this *EdgeDNSAPIProvider) GetRecords(domain string) (records []*dnstypes.Record, err error) {
var domainResp = &edgeapi.FindDomainWithNameResponse{}
err = this.doAPI("/NSDomainService/FindNSDomainWithName", map[string]any{
"name": domain,
}, domainResp)
if err != nil {
return nil, err
}
var domainId = domainResp.Data.NSDomain.Id
if domainId == 0 {
return nil, nil
}
var offset = 0
var size = 100
for {
var recordsResp = &edgeapi.ListNSRecordsResponse{}
err = this.doAPI("/NSRecordService/ListNSRecords", map[string]any{
"nsDomainId": domainId,
"offset": offset,
"size": size,
}, recordsResp)
if err != nil {
return nil, err
}
var nsRecords = recordsResp.Data.NSRecords
for _, record := range nsRecords {
var routeCode = this.DefaultRoute()
if len(record.NSRoutes) > 0 {
routeCode = record.NSRoutes[0].Code
}
records = append(records, &dnstypes.Record{
Id: types.String(record.Id),
Name: record.Name,
Type: record.Type,
Value: record.Value,
Route: routeCode,
TTL: record.TTL,
})
}
if len(nsRecords) < size {
break
}
offset += size
}
return
}
// GetRoutes 读取域名支持的线路数据
func (this *EdgeDNSAPIProvider) GetRoutes(domain string) (routes []*dnstypes.Route, err error) {
// default
routes = append(routes, &dnstypes.Route{
Name: "默认线路",
Code: this.DefaultRoute(),
})
// 世界区域
{
var routesResp = &edgeapi.FindAllNSRoutesResponse{}
err = this.doAPI("/NSRouteService/FindAllDefaultWorldRegionRoutes", map[string]any{}, routesResp)
if err != nil {
return nil, err
}
for _, route := range routesResp.Data.NSRoutes {
routes = append(routes, &dnstypes.Route{
Name: route.Name,
Code: route.Code,
})
}
}
// 中国省份
{
var routesResp = &edgeapi.FindAllNSRoutesResponse{}
err = this.doAPI("/NSRouteService/FindAllDefaultChinaProvinceRoutes", map[string]any{}, routesResp)
if err != nil {
return nil, err
}
for _, route := range routesResp.Data.NSRoutes {
routes = append(routes, &dnstypes.Route{
Name: route.Name,
Code: route.Code,
})
}
}
// ISP
{
var routesResp = &edgeapi.FindAllNSRoutesResponse{}
err = this.doAPI("/NSRouteService/FindAllDefaultISPRoutes", map[string]any{}, routesResp)
if err != nil {
return nil, err
}
for _, route := range routesResp.Data.NSRoutes {
routes = append(routes, &dnstypes.Route{
Name: route.Name,
Code: route.Code,
})
}
}
// 自定义
{
var routesResp = &edgeapi.FindAllNSRoutesResponse{}
err = this.doAPI("/NSRouteService/FindAllNSRoutes", map[string]any{}, routesResp)
if err != nil {
return nil, err
}
for _, route := range routesResp.Data.NSRoutes {
routes = append(routes, &dnstypes.Route{
Name: route.Name,
Code: route.Code,
})
}
}
return
}
// QueryRecord 查询单个记录
func (this *EdgeDNSAPIProvider) QueryRecord(domain string, name string, recordType dnstypes.RecordType) (*dnstypes.Record, error) {
var domainResp = &edgeapi.FindDomainWithNameResponse{}
err := this.doAPI("/NSDomainService/FindNSDomainWithName", map[string]any{
"name": domain,
}, domainResp)
if err != nil {
return nil, err
}
var domainId = domainResp.Data.NSDomain.Id
if domainId == 0 {
return nil, errors.New("can not find domain '" + domain + "'")
}
var recordResp = &edgeapi.FindNSRecordWithNameAndTypeResponse{}
err = this.doAPI("/NSRecordService/FindNSRecordWithNameAndType", map[string]any{
"nsDomainId": domainId,
"name": name,
"type": recordType,
}, recordResp)
if err != nil {
return nil, err
}
var record = recordResp.Data.NSRecord
if record.Id <= 0 {
return nil, nil
}
var routeCode = this.DefaultRoute()
if len(record.NSRoutes) > 0 {
routeCode = record.NSRoutes[0].Code
}
return &dnstypes.Record{
Id: types.String(record.Id),
Name: record.Name,
Type: record.Type,
Value: record.Value,
Route: routeCode,
TTL: record.TTL,
}, nil
}
// AddRecord 设置记录
func (this *EdgeDNSAPIProvider) AddRecord(domain string, newRecord *dnstypes.Record) error {
var domainResp = &edgeapi.FindDomainWithNameResponse{}
err := this.doAPI("/NSDomainService/FindNSDomainWithName", map[string]any{
"name": domain,
}, domainResp)
if err != nil {
return err
}
var domainId = domainResp.Data.NSDomain.Id
if domainId == 0 {
return errors.New("can not find domain '" + domain + "'")
}
if newRecord.Type == dnstypes.RecordTypeCNAME && !strings.HasSuffix(newRecord.Value, ".") {
newRecord.Value += "."
}
var createResp = &edgeapi.CreateNSRecordResponse{}
var routes = []string{}
if len(newRecord.Route) > 0 {
routes = []string{newRecord.Route}
}
err = this.doAPI("/NSRecordService/CreateNSRecord", map[string]any{
"nsDomainId": domainId,
"name": newRecord.Name,
"type": strings.ToUpper(newRecord.Type),
"value": newRecord.Value,
"ttl": newRecord.TTL,
"nsRouteCodes": routes,
}, createResp)
if err != nil {
return err
}
newRecord.Id = types.String(createResp.Data.NSRecordId)
return nil
}
// UpdateRecord 修改记录
func (this *EdgeDNSAPIProvider) UpdateRecord(domain string, record *dnstypes.Record, newRecord *dnstypes.Record) error {
if newRecord.Type == dnstypes.RecordTypeCNAME && !strings.HasSuffix(newRecord.Value, ".") {
newRecord.Value += "."
}
var createResp = &edgeapi.UpdateNSRecordResponse{}
var routes = []string{}
if len(newRecord.Route) > 0 {
routes = []string{newRecord.Route}
}
err := this.doAPI("/NSRecordService/UpdateNSRecord", map[string]any{
"nsRecordId": types.Int64(record.Id),
"name": newRecord.Name,
"type": strings.ToUpper(newRecord.Type),
"value": newRecord.Value,
"ttl": newRecord.TTL,
"nsRouteCodes": routes,
"isOn": true, // important
}, createResp)
return err
}
// DeleteRecord 删除记录
func (this *EdgeDNSAPIProvider) DeleteRecord(domain string, record *dnstypes.Record) error {
var resp = &edgeapi.SuccessResponse{}
err := this.doAPI("/NSRecordService/DeleteNSRecord", map[string]any{
"nsRecordId": types.Int64(record.Id),
}, resp)
return err
}
// DefaultRoute 默认线路
func (this *EdgeDNSAPIProvider) DefaultRoute() string {
return "default"
}
func (this *EdgeDNSAPIProvider) doAPI(path string, params map[string]any, respPtr edgeapi.ResponseInterface) error {
accessToken, err := this.getToken()
if err != nil {
return err
}
paramsJSON, err := json.Marshal(params)
if err != nil {
return err
}
req, err := http.NewRequest(http.MethodPost, this.host+path, bytes.NewReader(paramsJSON))
if err != nil {
return err
}
req.Header.Set("User-Agent", teaconst.ProductName+"/"+teaconst.Version)
req.Header.Set("X-Edge-Access-Token", accessToken)
resp, err := edgeDNSHTTPClient.Do(req)
if err != nil {
return err
}
if resp.StatusCode != http.StatusOK {
return errors.New("invalid response status code '" + types.String(resp.StatusCode) + "'")
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return err
}
err = json.Unmarshal(data, respPtr)
if err != nil {
return errors.New("decode response failed: " + err.Error() + ", JSON: " + string(data))
}
if !respPtr.IsValid() {
return respPtr.Error()
}
return err
}
func (this *EdgeDNSAPIProvider) getToken() (string, error) {
if len(this.accessToken) > 0 && this.accessTokenExpiresAt > time.Now().Unix()+600 /** 600秒是防止当前服务器和API服务器之间有时间差 **/ {
return this.accessToken, nil
}
var params = maps.Map{
"type": this.role,
"accessKeyId": this.accessKeyId,
"accessKey": this.accessKeySecret,
}
paramsJSON, err := json.Marshal(params)
if err != nil {
return "", err
}
req, err := http.NewRequest(http.MethodPost, this.host+"/APIAccessTokenService/getAPIAccessToken", bytes.NewReader(paramsJSON))
if err != nil {
return "", err
}
req.Header.Set("User-Agent", teaconst.ProductName+"/"+teaconst.Version)
resp, err := edgeDNSHTTPClient.Do(req)
if err != nil {
return "", err
}
defer func() {
_ = resp.Body.Close()
}()
if resp.StatusCode != http.StatusOK {
return "", errors.New("invalid response code '" + types.String(resp.StatusCode) + "'")
}
data, err := io.ReadAll(resp.Body)
if err != nil {
return "", err
}
var tokenResp = &edgeapi.GetAPIAccessToken{}
err = json.Unmarshal(data, tokenResp)
if err != nil {
return "", err
}
if tokenResp.Code != 200 {
return "", errors.New("invalid code '" + types.String(tokenResp.Code) + "', message: " + tokenResp.Message)
}
this.accessToken = tokenResp.Data.Token
this.accessTokenExpiresAt = tokenResp.Data.ExpiresAt
return this.accessToken, nil
}

View File

@@ -0,0 +1,163 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package dnsclients_test
import (
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients"
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
"github.com/iwind/TeaGo/logs"
"github.com/iwind/TeaGo/maps"
"testing"
)
const edgeDNSAPIDomainName = "hello2.com"
func TestEdgeDNSAPIProvider_GetDomains(t *testing.T) {
provider, err := testEdgeDNSAPIProvider()
if err != nil {
t.Fatal(err)
}
domains, err := provider.GetDomains()
if err != nil {
t.Fatal(err)
}
t.Log("domains:", domains)
}
func TestEdgeDNSAPIProvider_GetRecords(t *testing.T) {
provider, err := testEdgeDNSAPIProvider()
if err != nil {
t.Fatal(err)
}
records, err := provider.GetRecords(edgeDNSAPIDomainName)
if err != nil {
t.Fatal(err)
}
logs.PrintAsJSON(records, t)
}
func TestEdgeDNSAPIProvider_GetRoutes(t *testing.T) {
provider, err := testEdgeDNSAPIProvider()
if err != nil {
t.Fatal(err)
}
routes, err := provider.GetRoutes(edgeDNSAPIDomainName)
if err != nil {
t.Fatal(err)
}
logs.PrintAsJSON(routes, t)
}
func TestEdgeDNSAPIProvider_QueryRecord(t *testing.T) {
provider, err := testEdgeDNSAPIProvider()
if err != nil {
t.Fatal(err)
}
record, err := provider.QueryRecord(edgeDNSAPIDomainName, "cdn", dnstypes.RecordTypeA)
if err != nil {
t.Fatal(err)
}
logs.PrintAsJSON(record)
}
func TestEdgeDNSAPIProvider_AddRecord(t *testing.T) {
provider, err := testEdgeDNSAPIProvider()
if err != nil {
t.Fatal(err)
}
err = provider.AddRecord(edgeDNSAPIDomainName, &dnstypes.Record{
Id: "",
Name: "example",
Type: dnstypes.RecordTypeA,
Value: "10.0.0.1",
Route: "china:province:beijing",
TTL: 300,
})
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}
func TestEdgeDNSAPIProvider_UpdateRecord(t *testing.T) {
provider, err := testEdgeDNSAPIProvider()
if err != nil {
t.Fatal(err)
}
record, err := provider.QueryRecord(edgeDNSAPIDomainName, "cdn", dnstypes.RecordTypeA)
if err != nil {
t.Fatal(err)
}
if record == nil {
t.Log("not found record")
return
}
//record.Id = ""
err = provider.UpdateRecord(edgeDNSAPIDomainName, record, &dnstypes.Record{
Id: "",
Name: record.Name,
Type: record.Type,
Value: "127.0.0.3",
Route: record.Route,
TTL: 30,
})
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}
func TestEdgeDNSAPIProvider_DeleteRecord(t *testing.T) {
provider, err := testEdgeDNSAPIProvider()
if err != nil {
t.Fatal(err)
}
record, err := provider.QueryRecord(edgeDNSAPIDomainName, "example", "A")
if err != nil {
t.Fatal(err)
}
if record == nil {
t.Log("not found")
return
}
err = provider.DeleteRecord(edgeDNSAPIDomainName, &dnstypes.Record{
Id: record.Id,
Name: "example",
Type: "A",
Value: "",
Route: "",
})
if err != nil {
t.Fatal(err)
}
t.Log("ok")
}
func TestEdgeDNSAPIProvider_DefaultRoute(t *testing.T) {
provider, err := testEdgeDNSAPIProvider()
if err != nil {
t.Fatal(err)
}
t.Log(provider.DefaultRoute())
}
func testEdgeDNSAPIProvider() (dnsclients.ProviderInterface, error) {
provider := &dnsclients.EdgeDNSAPIProvider{}
err := provider.Auth(maps.Map{
"role": "user",
"host": "http://127.0.0.1:8004",
"accessKeyId": "JOvsyXIFqkQbh5kl",
"accessKeySecret": "t0RY8YO3R58VbJJNp0RqKw9KWNpObwtE",
})
if err != nil {
return nil, err
}
return provider, nil
}

View File

@@ -1,65 +0,0 @@
// Copyright 2021 Liuxiangchao iwind.liu@gmail.com. All rights reserved.
package dnsclients
import (
"github.com/TeaOSLab/EdgeAPI/internal/dnsclients/dnstypes"
"github.com/iwind/TeaGo/maps"
)
type UserEdgeDNSProvider struct {
}
// Auth 认证
func (this *UserEdgeDNSProvider) Auth(params maps.Map) error {
// TODO
return nil
}
// GetDomains 获取所有域名列表
func (this *UserEdgeDNSProvider) GetDomains() (domains []string, err error) {
// TODO
return
}
// GetRecords 获取域名解析记录列表
func (this *UserEdgeDNSProvider) GetRecords(domain string) (records []*dnstypes.Record, err error) {
// TODO
return
}
// GetRoutes 读取域名支持的线路数据
func (this *UserEdgeDNSProvider) GetRoutes(domain string) (routes []*dnstypes.Route, err error) {
// TODO
return
}
// QueryRecord 查询单个记录
func (this *UserEdgeDNSProvider) QueryRecord(domain string, name string, recordType dnstypes.RecordType) (*dnstypes.Record, error) {
// TODO
return nil, nil
}
// AddRecord 设置记录
func (this *UserEdgeDNSProvider) AddRecord(domain string, newRecord *dnstypes.Record) error {
// TODO
return nil
}
// UpdateRecord 修改记录
func (this *UserEdgeDNSProvider) UpdateRecord(domain string, record *dnstypes.Record, newRecord *dnstypes.Record) error {
// TODO
return nil
}
// DeleteRecord 删除记录
func (this *UserEdgeDNSProvider) DeleteRecord(domain string, record *dnstypes.Record) error {
// TODO
return nil
}
// DefaultRoute 默认线路
func (this *UserEdgeDNSProvider) DefaultRoute() string {
// TODO
return ""
}

View File

@@ -13,13 +13,13 @@ const (
ProviderTypeHuaweiDNS ProviderType = "huaweiDNS" // 华为DNS ProviderTypeHuaweiDNS ProviderType = "huaweiDNS" // 华为DNS
ProviderTypeCloudFlare ProviderType = "cloudFlare" // CloudFlare DNS ProviderTypeCloudFlare ProviderType = "cloudFlare" // CloudFlare DNS
ProviderTypeLocalEdgeDNS ProviderType = "localEdgeDNS" // 和当前系统集成的EdgeDNS ProviderTypeLocalEdgeDNS ProviderType = "localEdgeDNS" // 和当前系统集成的EdgeDNS
ProviderTypeUserEdgeDNS ProviderType = "userEdgeDNS" // 通过API连接的EdgeDNS ProviderTypeEdgeDNSAPI ProviderType = "edgeDNSAPI" // 通过API连接的EdgeDNS
ProviderTypeCustomHTTP ProviderType = "customHTTP" // 自定义HTTP接口 ProviderTypeCustomHTTP ProviderType = "customHTTP" // 自定义HTTP接口
) )
// FindAllProviderTypes 所有的服务商类型 // FindAllProviderTypes 所有的服务商类型
func FindAllProviderTypes() []maps.Map { func FindAllProviderTypes() []maps.Map {
typeMaps := []maps.Map{ var typeMaps = []maps.Map{
{ {
"name": "阿里云DNS", "name": "阿里云DNS",
"code": ProviderTypeAliDNS, "code": ProviderTypeAliDNS,
@@ -40,6 +40,11 @@ func FindAllProviderTypes() []maps.Map {
"code": ProviderTypeCloudFlare, "code": ProviderTypeCloudFlare,
"description": "CloudFlare提供的DNS服务。", "description": "CloudFlare提供的DNS服务。",
}, },
{
"name": "EdgeDNS API",
"code": ProviderTypeEdgeDNSAPI,
"description": "通过API连接GoEdge商业版系统提供的DNS服务。",
},
} }
typeMaps = filterTypeMaps(typeMaps) typeMaps = filterTypeMaps(typeMaps)

View File

@@ -19,6 +19,8 @@ func FindProvider(providerType ProviderType) ProviderInterface {
return &CloudFlareProvider{} return &CloudFlareProvider{}
case ProviderTypeCustomHTTP: case ProviderTypeCustomHTTP:
return &CustomHTTPProvider{} return &CustomHTTPProvider{}
case ProviderTypeUserEdgeDNS:
return &EdgeDNSAPIProvider{}
} }
return nil return nil