mirror of https://github.com/allinssl/allinssl
parent
5b7245a78d
commit
e4b456b1ee
|
@ -0,0 +1,81 @@
|
|||
package bt
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 生成 API 签名
|
||||
func (c *Config) generateSignature(method, path string, body string) (string, string) {
|
||||
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
||||
|
||||
signingString := fmt.Sprintf("%s\n%s\n%s\n%s\n%s",
|
||||
c.AccountID,
|
||||
timestamp,
|
||||
strings.ToUpper(method),
|
||||
path,
|
||||
body,
|
||||
)
|
||||
|
||||
h := hmac.New(sha256.New, []byte(c.SecretKey))
|
||||
h.Write([]byte(signingString))
|
||||
signature := hex.EncodeToString(h.Sum(nil))
|
||||
|
||||
return timestamp, signature
|
||||
}
|
||||
|
||||
// 发起 API 请求
|
||||
func (c *Config) MakeRequest(method, path string, data interface{}) (map[string]interface{}, error) {
|
||||
url := strings.TrimRight(c.BaseURL, "/") + path
|
||||
|
||||
var bodyStr string
|
||||
var bodyBytes []byte
|
||||
if data != nil {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bodyStr = string(b)
|
||||
bodyBytes = b
|
||||
}
|
||||
|
||||
timestamp, signature := c.generateSignature(method, path, bodyStr)
|
||||
|
||||
req, err := http.NewRequest(method, url, bytes.NewBuffer(bodyBytes))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Account-ID", c.AccountID)
|
||||
req.Header.Set("X-Access-Key", c.AccessKey)
|
||||
req.Header.Set("X-Timestamp", timestamp)
|
||||
req.Header.Set("X-Signature", signature)
|
||||
|
||||
client := &http.Client{}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
respBytes, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err := json.Unmarshal(respBytes, &result); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
|
@ -0,0 +1,197 @@
|
|||
package bt
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/go-acme/lego/v4/challenge"
|
||||
"github.com/go-acme/lego/v4/challenge/dns01"
|
||||
"github.com/go-acme/lego/v4/platform/config/env"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
envNamespace = "BTDOMAIN_"
|
||||
|
||||
EnvAccountID = envNamespace + "ACCOUNT_ID"
|
||||
EnvAccessKey = envNamespace + "ACCESS_KEY"
|
||||
EnvSecretKey = envNamespace + "SECRET_KEY"
|
||||
EnvBaseURL = envNamespace + "BASE_URL"
|
||||
|
||||
EnvTTL = envNamespace + "TTL"
|
||||
EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
|
||||
EnvPollingInterval = envNamespace + "POLLING_INTERVAL"
|
||||
EnvHTTPTimeout = envNamespace + "HTTP_TIMEOUT"
|
||||
)
|
||||
|
||||
var _ challenge.ProviderTimeout = (*DNSProvider)(nil)
|
||||
|
||||
type Config struct {
|
||||
AccountID string
|
||||
AccessKey string
|
||||
SecretKey string
|
||||
BaseURL string
|
||||
|
||||
PropagationTimeout time.Duration
|
||||
PollingInterval time.Duration
|
||||
TTL int
|
||||
HTTPTimeout time.Duration
|
||||
}
|
||||
|
||||
func NewConfig(accountID, accessKey, secretKey, baseURL string) *Config {
|
||||
|
||||
return &Config{
|
||||
AccountID: accountID,
|
||||
AccessKey: accessKey,
|
||||
SecretKey: secretKey,
|
||||
BaseURL: baseURL,
|
||||
TTL: 600,
|
||||
PropagationTimeout: dns01.DefaultPropagationTimeout,
|
||||
PollingInterval: dns01.DefaultPollingInterval,
|
||||
HTTPTimeout: 30 * time.Second,
|
||||
}
|
||||
}
|
||||
|
||||
func NewDefaultConfig() *Config {
|
||||
return &Config{
|
||||
BaseURL: env.GetOrDefaultString(EnvBaseURL, "https://dmp.bt.cn"),
|
||||
|
||||
TTL: env.GetOrDefaultInt(EnvTTL, 600),
|
||||
PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, dns01.DefaultPropagationTimeout),
|
||||
PollingInterval: env.GetOrDefaultSecond(EnvPollingInterval, dns01.DefaultPollingInterval),
|
||||
HTTPTimeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
|
||||
}
|
||||
}
|
||||
|
||||
type DNSProvider struct {
|
||||
config *Config
|
||||
}
|
||||
|
||||
func NewDNSProvider() (*DNSProvider, error) {
|
||||
values, err := env.Get(EnvAccountID, EnvAccessKey, EnvSecretKey)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("westcn: %w", err)
|
||||
}
|
||||
|
||||
config := NewDefaultConfig()
|
||||
config.AccountID = values[EnvAccountID]
|
||||
config.AccessKey = values[EnvAccessKey]
|
||||
config.SecretKey = values[EnvSecretKey]
|
||||
|
||||
return NewDNSProviderConfig(config)
|
||||
}
|
||||
|
||||
func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
|
||||
if config == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &DNSProvider{config: config}, nil
|
||||
}
|
||||
|
||||
func (d *DNSProvider) Timeout() (timeout, interval time.Duration) {
|
||||
return d.config.PropagationTimeout, d.config.PollingInterval
|
||||
}
|
||||
|
||||
func (d *DNSProvider) Present(domain, token, keyAuth string) error {
|
||||
return d.config.addDNSRecord(domain, keyAuth)
|
||||
}
|
||||
|
||||
func (d *DNSProvider) CleanUp(domain, token, keyAuth string) error {
|
||||
return d.config.removeDNSRecord(domain, keyAuth)
|
||||
}
|
||||
|
||||
func (c *Config) GetDomainId(domain string) (int, int) {
|
||||
domain = dns01.UnFqdn(domain)
|
||||
resp, err := c.MakeRequest("POST", "/api/v1/dns/manage/list_domains", map[string]interface{}{
|
||||
"p": "1",
|
||||
"rows": "100",
|
||||
"keyword": domain,
|
||||
})
|
||||
if err != nil {
|
||||
return 0, 0
|
||||
}
|
||||
if !resp["status"].(bool) {
|
||||
return 0, 0
|
||||
}
|
||||
data := resp["data"].(map[string]interface{})
|
||||
list := data["data"].([]interface{})
|
||||
for _, item := range list {
|
||||
d := item.(map[string]interface{})
|
||||
if d["full_domain"].(string) == domain {
|
||||
return int(d["local_id"].(float64)), int(d["domain_type"].(float64))
|
||||
}
|
||||
}
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
func (c *Config) addDNSRecord(domain, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
EffectiveFQDN := dns01.UnFqdn(info.EffectiveFQDN)
|
||||
rootDomain, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("无法获取域名的根域名: %w", err)
|
||||
}
|
||||
subDomain, err := dns01.ExtractSubDomain(EffectiveFQDN, rootDomain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("无法获取域名的子域名: %w", err)
|
||||
}
|
||||
|
||||
domainId, domainType := c.GetDomainId(rootDomain)
|
||||
if domainId == 0 {
|
||||
return nil
|
||||
}
|
||||
_, err = c.MakeRequest("POST", "/api/v1/dns/record/create", map[string]interface{}{
|
||||
"domain_id": domainId,
|
||||
"domain_type": domainType,
|
||||
"record": subDomain,
|
||||
"value": info.Value,
|
||||
"type": "TXT",
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *Config) removeDNSRecord(domain, keyAuth string) error {
|
||||
info := dns01.GetChallengeInfo(domain, keyAuth)
|
||||
|
||||
EffectiveFQDN := dns01.UnFqdn(info.EffectiveFQDN)
|
||||
rootDomain, err := dns01.FindZoneByFqdn(info.EffectiveFQDN)
|
||||
if err != nil {
|
||||
return fmt.Errorf("无法获取域名的根域名: %w", err)
|
||||
}
|
||||
subDomain, err := dns01.ExtractSubDomain(EffectiveFQDN, rootDomain)
|
||||
if err != nil {
|
||||
return fmt.Errorf("无法获取域名的子域名: %w", err)
|
||||
}
|
||||
|
||||
domainId, domainType := c.GetDomainId(rootDomain)
|
||||
if domainId == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
resp, err := c.MakeRequest("POST", "/api/v1/dns/record/list", map[string]interface{}{
|
||||
"domain_id": domainId,
|
||||
"domain_type": domainType,
|
||||
"p": "1",
|
||||
"rows": "100",
|
||||
"searchKey": subDomain,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !resp["status"].(bool) {
|
||||
return nil
|
||||
}
|
||||
data := resp["data"].(map[string]interface{})
|
||||
list := data["data"].([]interface{})
|
||||
for _, item := range list {
|
||||
d := item.(map[string]interface{})
|
||||
if d["record"].(string) == subDomain && d["type"].(string) == "TXT" && d["value"].(string) == info.Value {
|
||||
_, err = c.MakeRequest("POST", "/api/v1/dns/record/delete", map[string]interface{}{
|
||||
"domain_id": domainId,
|
||||
"domain_type": domainType,
|
||||
"record_id": int(d["record_id"].(float64)),
|
||||
})
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
Loading…
Reference in New Issue