├── .github └── workflows │ └── go.yml ├── .gitignore ├── README.md ├── a.go ├── build.sh ├── go.mod ├── main.go ├── providers ├── aliyun.go ├── aliyun_model.go ├── namesilo.go ├── namesilo_model.go ├── provider.go ├── qcloud.go ├── qcloud_model.go ├── qcloud_v3.go └── qcloud_v3_model.go └── utils ├── domain.go ├── helper.go └── httpclient.go /.github/workflows/go.yml: -------------------------------------------------------------------------------- 1 | name: Go 2 | on: [push] 3 | jobs: 4 | 5 | build: 6 | name: Build 7 | runs-on: ubuntu-latest 8 | steps: 9 | 10 | - name: Set up Go 1.13 11 | uses: actions/setup-go@v1 12 | with: 13 | go-version: 1.13 14 | id: go 15 | 16 | - name: Check out code into the Go module directory 17 | uses: actions/checkout@v2 18 | 19 | - name: Get dependencies 20 | run: | 21 | go get -v -t -d ./... 22 | if [ -f Gopkg.toml ]; then 23 | curl https://raw.githubusercontent.com/golang/dep/master/install.sh | sh 24 | dep ensure 25 | fi 26 | 27 | - name: Build 28 | run: go build -v . 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .DS_Store 3 | t_test.go 4 | a.go 5 | a.go.bak 6 | a 7 | certbot-feehi-hook 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | let's encrypt Certbot hook written in golang 2 | =============================== 3 | 4 | lets'encrypt的通配符https证书官方推荐使用certbot客户端获取。 5 | certbot验证域名是否属于某人,通过产生一个随机字符串,让用户设置一个TXT记录到此值后,当验证成功后颁发证书。 6 | 该证书有效期为3个月,意味着每3个月得更新一次,每次手工设置dns解析及其麻烦,还容易忘记更新证书。 7 | 好在certbot提供了一个hook,产生随机后调用hook来自动设置dns记录,官方预制了几家dns服务商的hook, 8 | 但是国内的阿里云、腾讯云都不在此例。此插件支持国内阿里云、腾讯云等其他常用dns服务商, 9 | 使用golang编写,服务器无需搭建运行环境,直接下载二进制分发程序即可。 10 | 11 | 12 | 支持的DNS服务商 13 | ------- 14 | - [x] 阿里云 15 | 16 | - [x] 腾讯云 17 | 18 | - [x] Namesilo 19 | 20 | 21 | DNS服务商密钥获取 22 | --------------- 23 | 1. 阿里云进入管理后台,悬浮右上角的头像,点击下拉菜单的***accesskeys***进入用户信息管理的安全信息管理 24 | AccessKey ID即为ali_AccessKey_ID 25 | Access Key Secret 即为阿里云ACCESS_KEY_SECRET 26 | 27 | 2. 腾讯云进入管理后台,悬浮右上角账号,点击下拉菜单的***访问管理***,点击左侧的访问密钥,再点击API密钥管理 28 | SecretId即为qcloud_SecretId 29 | SecretKey即为qcloud_SecretKey 30 | 31 | 3. Namesilo Visit the API Manager page within your account for details 32 | 33 | 34 | 获取插件 35 | --------------- 36 | 1. 下载二进制发行版 [点此下载](https://resource-1251086492.cos.ap-shanghai.myqcloud.com/opensource/certbot-feehi-hook) 下载后请记得***chmod +x certbot-feehi-hook***给予执行权限 37 | 38 | 2. 源码编译安装 39 | ```bash 40 | $ go get github.com/liufee/certbot-feehi-hook 41 | $ cd $GOPATH/src/liufee/certbot-feehi-hook 42 | $ sh build.sh 43 | ``` 44 | 45 | 使用方式 46 | --------------- 47 | >为了方便展示,以下shell命令安装均做了变形处理,如执行失败,请手动去掉换行符,将命令调整为单行 48 | 49 | >以下示例命令均为dns解析在阿里云的,如果为腾讯云请将所有 50 | --type=aliyun换成--type=qcloud 51 | --ali_AccessKey_ID=阿里云ACCESS_KEY_ID 换成 --qcloud_SecretId=腾讯云secretId 52 | --ali_Access_Key_Secret=阿里云ACCESS_KEY_SECRET 换成 --qcloud_SecretKey=腾讯云secretKey 53 | 54 | 1. 直接使用 55 | 1.1 安装certbot 56 | ```bash 57 | $ wget https://dl.eff.org/certbot-auto && mv certbot-auto certbot && chmod +x certbot 58 | $ mv certbot /usr/bin 59 | ``` 60 | 1.2 61 | ```bash 62 | $ certbot certonly 63 | -d *.您的域名.com --manual --preferred-challenges dns 64 | --manual-auth-hook "/存放certbot-feehi-hook的目录(下载的certboot-feehi-hook存放目录)/certbot-feehi-hook --type=aliyun --action=add --ali_AccessKey_ID=阿里云ACCESS_KEY_ID --ali_Access_Key_Secret=阿里云ACCESS_KEY_SECRET" 65 | --manual-cleanup-hook "/hook/certbot-feehi-hook --type=aliyun --action=delete --ali_AccessKey_ID=阿里云ACCESS_KEY_ID --ali_Access_Key_Secret=阿里云ACCESS_KEY_SECRET" 66 | ``` 67 | 68 | 2. 通过docker certbot/certbot官方镜像 69 | * 申请证书 70 | ```bash 71 | $ docker run -it --rm --name certbot 72 | -v "/宿主机证书存放目录:/etc/letsencrypt" 73 | -v "/tmp:/var/log/letsencrypt" 74 | -v "/存放certbot-feehi-hook的目录(下载的certboot-feehi-hook存放目录):/hook" 75 | certbot/certbot certonly 76 | -d *.您的域名.com --manual --preferred-challenges dns 77 | --manual-auth-hook "/hook/certbot-feehi-hook --type=aliyun --action=add --ali_AccessKey_ID=阿里云ACCESS_KEY_ID --ali_Access_Key_Secret=阿里云ACCESS_KEY_SECRET" 78 | --manual-cleanup-hook "/hook/certbot-feehi-hook --type=aliyun --action=delete --ali_AccessKey_ID=阿里云ACCESS_KEY_ID --ali_Access_Key_Secret=阿里云ACCESS_KEY_SECRET" 79 | ``` 80 | * 更新证书 81 | ```bash 82 | $ /path/to/docker run -it --rm --name certbot \ 83 | -v "/宿主机证书存放目录:/etc/letsencrypt" \ 84 | -v "/tmp:/var/log/letsencrypt" 85 | -v "/存放certbot-feehi-hook的目录(下载的certboot-feehi-hook存放目录):/hook" 86 | certbot/certbot renew 87 | --manual --preferred-challenges dns 88 | --manual-auth-hook "/hook/certbot-feehi-hook.sh --type=aliyun --action=add --ali_AccessKey_ID=阿里云ACCESS_KEY_ID --ali_Access_Key_Secret=阿里云ACCESS_KEY_SECRET" 89 | --manual-cleanup-hook "/hook/certbot-feehi-hook.sh --type=aliyun --action=delete --ali_AccessKey_ID=阿里云ACCESS_KEY_ID --ali_Access_Key_Secret=阿里云ACCESS_KEY_SECRET" 90 | --deploy-hook "service nginx restart" 91 | ``` 92 | 可以配置定时任务每个月执行一次: 0 0 1 * * 上面的命令 93 | 94 | 当且仅当成功更新证书后会执行--deploy-hook,根据自身web服务器情况进行重启web服务器重新加载新证书 95 | 96 | 97 | 帮助 98 | --------------- 99 | 1. QQ群 258780872 100 | 101 | 2. 微信
![微信](http://img-1251086492.cosgz.myqcloud.com/github/wechat.png) 102 | 103 | 3. Email job@feehi.com 104 | -------------------------------------------------------------------------------- /a.go: -------------------------------------------------------------------------------- 1 | package main 2 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | CGO_ENABLED=0 GOOS=linux go build -o certbot-feehi-hook -a -ldflags '-extldflags "-static"' . -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module certbot-feehi-hook 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "certbot-feehi-hook/providers" 5 | "flag" 6 | "fmt" 7 | "log" 8 | "net" 9 | "os" 10 | "time" 11 | ) 12 | 13 | const ver = "2.0.0" 14 | 15 | var ( 16 | h = flag.Bool("h", false, "This help") 17 | v = flag.Bool("v", false, "Show certbot-feehi-hook version") 18 | logFile = flag.String("log", "/tmp/cert-book-feehi-log.txt", "log file") 19 | 20 | providerType = flag.String("type", "", "Your dns provider. Current support aliyun, qcloud") 21 | action = flag.String("action", "", "add or delete. manual-auth-hook use add, manual-cleanup-hook use delete") 22 | 23 | aliyunKey = flag.String("ali_AccessKey_ID", "", "aliyun access id") 24 | aliyunSecret = flag.String("ali_Access_Key_Secret", "", "aliyun access key secret") 25 | 26 | qcloudKey = flag.String("qcloud_SecretId", "", "qcloud SecretId") 27 | qcloudSecret = flag.String("qcloud_SecretKey", "", "qcloud SecretKey") 28 | 29 | namesiloApiKey = flag.String("namesilo_apikey", "", "namesilo ApiKey") 30 | ) 31 | 32 | var validationKey string 33 | var domain string 34 | 35 | func main() { 36 | flag.Parse() 37 | 38 | if *h { 39 | flag.Usage() 40 | os.Exit(0) 41 | } 42 | if *v { 43 | fmt.Println("Ver", ver) 44 | os.Exit(0) 45 | } 46 | 47 | l, err := os.OpenFile(*logFile, os.O_CREATE|os.O_TRUNC|os.O_RDWR, 0777) 48 | if err != nil { 49 | panic(err) 50 | } 51 | log.SetOutput(l) 52 | 53 | log.Println("Auto dns record manage start") 54 | parseCertbotPassedValue() 55 | var p providers.Provider 56 | log.Printf("Your dns provider is %s \n", *providerType) 57 | switch *providerType { 58 | case "aliyun": 59 | p = providers.NewAliyun(domain, *aliyunKey, *aliyunSecret) 60 | case "qcloud": 61 | p = providers.NewQcloudV3(domain, *qcloudKey, *qcloudSecret) 62 | case "namesilo": 63 | p = providers.NewNamesilo(domain, *namesiloApiKey) 64 | default: 65 | if *providerType == "" { 66 | panic("--type must be your dns provider type, such as aliyun qcloud namesilo") 67 | } 68 | panic("Not support " + *providerType + " yet") 69 | } 70 | run(p) 71 | } 72 | 73 | func run(p providers.Provider) { 74 | log.Println("Current action is ", *action) 75 | switch *action { 76 | case "add": 77 | log.Println("Will add record TXT _acme-challenge ", validationKey) 78 | result, err := p.ResolveDomainName("TXT", "_acme-challenge", validationKey) 79 | if err != nil { 80 | log.Fatalf("Auto resolve record TXT _acme-challenge %s comes to error %s \n", validationKey, err) 81 | } 82 | if !result { 83 | log.Fatalln("Auto resolve txt record failed") 84 | } 85 | log.Println("Auto resolve txt record success") 86 | log.Println("Check if record effected") 87 | for { 88 | records, _ := net.LookupTXT("_acme-challenge." + domain) 89 | if len(records) > 0 { 90 | break 91 | } 92 | log.Println("Record not effected", records) 93 | time.Sleep(time.Second * 20) 94 | } 95 | log.Println("feehi Hook finish") 96 | case "delete": 97 | log.Println("Will delete record TXT _acme-challenge ") 98 | result, err := p.DeleteResolveDomainName("TXT", "_acme-challenge") 99 | if err != nil { 100 | log.Printf("Auto delete record TXT _acme-challenge %s comes to error %s \n", validationKey, err) 101 | } 102 | if result { 103 | log.Println("Auto delete txt record success") 104 | } else { 105 | log.Println("Auto delete txt record failed") 106 | } 107 | default: 108 | panic("action only support add or delete") 109 | } 110 | os.Exit(0) 111 | } 112 | 113 | func parseCertbotPassedValue() { 114 | validationKey = os.Getenv("CERTBOT_VALIDATION") 115 | if validationKey == "" { 116 | panic("Not get CERTBOT_VALIDATION") 117 | } 118 | domain = os.Getenv("CERTBOT_DOMAIN") 119 | if domain == "" { 120 | panic("Not get CERTBOT_DOMAIN") 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /providers/aliyun.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | import ( 4 | "certbot-feehi-hook/utils" 5 | "crypto/hmac" 6 | "crypto/md5" 7 | "crypto/sha1" 8 | "encoding/base64" 9 | "encoding/json" 10 | "errors" 11 | "log" 12 | "math/rand" 13 | "net/url" 14 | "sort" 15 | "strconv" 16 | "strings" 17 | "time" 18 | ) 19 | 20 | const aliBaseUrl = "http://alidns.aliyuncs.com" 21 | 22 | func NewAliyun(domain, aliKey, aliSecret string) *Aliyun { 23 | if aliKey == "" { 24 | panic("ali_AccessKey_ID must be passed") 25 | } 26 | if aliSecret == "" { 27 | panic("ali_Access_Key_Secret must be passed") 28 | } 29 | domain, levelsDomainName := utils.ParseDomain(domain) 30 | return &Aliyun{ 31 | DomainName: domain, 32 | LevelsDomainName: levelsDomainName, 33 | AppKey: aliKey, 34 | AppSecret: aliSecret, 35 | } 36 | } 37 | 38 | type Aliyun struct { 39 | AppKey string 40 | AppSecret string 41 | DomainName string 42 | LevelsDomainName string 43 | } 44 | 45 | func (a *Aliyun) ResolveDomainName(dnsType string, RR string, value string) (bool, error) { 46 | var result bool 47 | var err error 48 | if a.LevelsDomainName != "" { 49 | RR += "." + a.LevelsDomainName 50 | } 51 | record, err := a.GetRecordByTypeAndPr(dnsType, RR) 52 | if err != nil { 53 | return false, err 54 | } 55 | if record == nil { //need new add 56 | log.Println("add new record") 57 | result, err = a.AddRecord(dnsType, RR, value) 58 | } else { 59 | log.Println(" record already exist, do update") 60 | result, err = a.UpdateRecord(dnsType, RR, value, record.RecordId) 61 | } 62 | if err != nil { 63 | return false, err 64 | } 65 | return result, nil 66 | } 67 | 68 | func (a *Aliyun) DeleteResolveDomainName(dnsType string, RR string) (bool, error) { 69 | if a.LevelsDomainName != "" { 70 | RR += "." + a.LevelsDomainName 71 | } 72 | record, err := a.GetRecordByTypeAndPr(dnsType, RR) 73 | if err != nil { 74 | return false, err 75 | } 76 | if record != nil { 77 | result, err := a.DeleteRecord(record.RecordId) 78 | if err != nil { 79 | return false, err 80 | } 81 | return result, nil 82 | } 83 | log.Println("Dns record not exists") 84 | return false, nil 85 | } 86 | 87 | func (a *Aliyun) GetRecords() (aliyunGetRecordsResult, error) { 88 | var result aliyunGetRecordsResult 89 | businessParams := map[string]string{ 90 | "Action": "DescribeDomainRecords", 91 | "PageSize": "100", 92 | } 93 | body, err := a.Do(businessParams) 94 | if err != nil { 95 | return result, err 96 | } 97 | err = json.Unmarshal(body, &result) 98 | if err != nil { 99 | return result, err 100 | } 101 | return result, nil 102 | } 103 | 104 | func (a *Aliyun) AddRecord(dnsType string, RR string, value string) (bool, error) { 105 | businessParams := map[string]string{ 106 | "Action": "AddDomainRecord", 107 | "Type": dnsType, //TXT 108 | "RR": RR, //www 109 | "Value": value, //8.8.8.8 110 | } 111 | body, err := a.Do(businessParams) 112 | if err != nil { 113 | return false, err 114 | } 115 | var b aliyunResult 116 | err = json.Unmarshal(body, &b) 117 | if err != nil { 118 | return false, err 119 | } 120 | if b.Code != "" { 121 | return false, errors.New(b.Code + ":" + b.Message) 122 | } 123 | return true, nil 124 | } 125 | 126 | func (a *Aliyun) UpdateRecord(dnsType string, RR string, value string, recordId string) (bool, error) { 127 | businessParams := map[string]string{ 128 | "Action": "UpdateDomainRecord", 129 | "Type": dnsType, //TXT 130 | "RR": RR, //www 131 | "Value": value, //8.8.8.8 132 | "RecordId": recordId, 133 | } 134 | body, err := a.Do(businessParams) 135 | if err != nil { 136 | return false, err 137 | } 138 | var b aliyunResult 139 | err = json.Unmarshal(body, &b) 140 | if err != nil { 141 | return false, err 142 | } 143 | if b.Code != "" { 144 | return false, errors.New(b.Code + ":" + b.Message) 145 | } 146 | return true, nil 147 | } 148 | 149 | func (a *Aliyun) DeleteRecord(recordId string) (bool, error) { 150 | businessParams := map[string]string{ 151 | "Action": "DeleteDomainRecord", 152 | "RecordId": recordId, 153 | } 154 | body, err := a.Do(businessParams) 155 | if err != nil { 156 | return false, err 157 | } 158 | var b aliyunResult 159 | err = json.Unmarshal(body, &b) 160 | if err != nil { 161 | return false, err 162 | } 163 | if b.Code != "" { 164 | return false, errors.New(b.Code + ":" + b.Message) 165 | } 166 | return true, nil 167 | } 168 | 169 | func (a *Aliyun) Do(businessParams map[string]string) ([]byte, error) { 170 | md5.Sum([]byte(strconv.Itoa(rand.Int()))) 171 | params := map[string]string{ 172 | "DomainName": a.DomainName, 173 | "Format": "JSON", 174 | "Version": "2015-01-09", 175 | "AccessKeyId": a.AppKey, 176 | "Timestamp": time.Now().UTC().Format("2006-01-02T15:04:05-0700"), 177 | "SignatureMethod": "HMAC-SHA1", 178 | "SignatureVersion": "1.0", 179 | "SignatureNonce": utils.GetRandomString(16), 180 | } 181 | 182 | for k, v := range businessParams { 183 | params[k] = v 184 | } 185 | signValue := a.signature(params, "GET") 186 | u := url.Values{} 187 | for k, v := range params { 188 | u.Add(k, v) 189 | } 190 | u.Add("Signature", signValue) 191 | return utils.HttpGet(aliBaseUrl, u) 192 | } 193 | 194 | func (a *Aliyun) signature(params map[string]string, method string) string { 195 | var keys = func() []string { 196 | var keys []string 197 | for key := range params { 198 | keys = append(keys, key) 199 | } 200 | return keys 201 | }() 202 | sort.Strings(keys) 203 | stringToSign := strings.ToUpper(method) + "&" + url.QueryEscape("/") + "&" 204 | 205 | var requestParamString = "" 206 | 207 | for _, key := range keys { 208 | requestParamString += "&" + url.QueryEscape(key) + "=" + url.QueryEscape(params[key]) 209 | } 210 | requestParamString = strings.Trim(requestParamString, "&") 211 | stringToSign = stringToSign + url.QueryEscape(requestParamString) 212 | 213 | key := []byte(a.AppSecret + "&") 214 | mac := hmac.New(sha1.New, key) 215 | mac.Write([]byte(stringToSign)) 216 | b := mac.Sum(nil) 217 | return base64.StdEncoding.EncodeToString(b) 218 | } 219 | 220 | func (a *Aliyun) GetRecordByTypeAndPr(tp, RR string) (*aliyunRecord, error) { 221 | var record aliyunRecord 222 | recordsResult, err := a.GetRecords() 223 | if err != nil { 224 | return &record, err 225 | } 226 | for _, v := range recordsResult.DomainRecords.Record { 227 | if v.Type == tp && v.RR == RR { 228 | return &v, nil 229 | } 230 | } 231 | return nil, nil 232 | } 233 | -------------------------------------------------------------------------------- /providers/aliyun_model.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | type aliyunResult struct { 4 | RequestId string 5 | Code string 6 | Message string 7 | } 8 | 9 | type aliyunGetRecordsResult struct { 10 | PageNumber int 11 | TotalCount int 12 | PageSize int 13 | RequestId string 14 | DomainRecords struct { 15 | Record []aliyunRecord 16 | } 17 | } 18 | 19 | type aliyunRecord struct { 20 | RR string `json:"RR"` 21 | Status string 22 | Value string 23 | Weight int 24 | RecordId string 25 | Type string 26 | DomainName string 27 | Locked bool 28 | Line string 29 | TTL int 30 | } 31 | -------------------------------------------------------------------------------- /providers/namesilo.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | import ( 4 | "certbot-feehi-hook/utils" 5 | "encoding/xml" 6 | "fmt" 7 | "net/url" 8 | ) 9 | 10 | func NewNamesilo(domain, apiKey string) *Namesilo { 11 | return &Namesilo{ 12 | apiKey: apiKey, 13 | domain: domain, 14 | } 15 | } 16 | 17 | var namesiloURL = "https://www.namesilo.com/api/" 18 | 19 | type Namesilo struct { 20 | apiKey string 21 | domain string 22 | } 23 | 24 | func (n *Namesilo) ResolveDomainName(dnsType string, pr string, value string) (bool, error) { 25 | record, err := n.GetRecordByTypeAndPr(dnsType, pr) 26 | if err != nil { 27 | return false, err 28 | } 29 | if record != nil { // delete 30 | _, err = n.DeleteRecord(record.RecordID) 31 | if err != nil { 32 | return false, err 33 | } 34 | } 35 | // add 36 | _, err = n.AddRecord(dnsType, pr, value) 37 | if err != nil { 38 | return false, err 39 | } 40 | return true, nil 41 | } 42 | 43 | func (n *Namesilo) AddRecord(dnsType string, pr string, value string) (bool, error) { 44 | query := url.Values{} 45 | query.Add("rrtype", dnsType) 46 | query.Add("domain", n.domain) 47 | query.Add("rrhost", pr) 48 | query.Add("rrvalue", value) 49 | query.Add("rrttl", "3600") 50 | res, err := n.do("dnsAddRecord", query) 51 | if err != nil { 52 | panic(err) 53 | } 54 | var response struct { 55 | Reply struct { 56 | Code int `xml:"code"` 57 | Detail string `xml:"detail"` 58 | RecordID string `xml:"record_id"` 59 | } `xml:"reply"` 60 | } 61 | err = xml.Unmarshal(res, &response) 62 | if err != nil { 63 | return false, err 64 | } 65 | if response.Reply.Code != 300 { 66 | return false, fmt.Errorf(response.Reply.Detail) 67 | } 68 | return true, nil 69 | } 70 | 71 | func (n *Namesilo) GetRecords() ([]ResourceRecord, error) { 72 | query := url.Values{} 73 | query.Add("domain", n.domain) 74 | res, err := n.do("dnsListRecords", query) 75 | if err != nil { 76 | panic(err) 77 | } 78 | var response struct { 79 | Reply struct { 80 | Code int `xml:"code"` 81 | Detail string `xml:"detail"` 82 | ResourceRecords []ResourceRecord `xml:"resource_record"` 83 | } `xml:"reply"` 84 | } 85 | err = xml.Unmarshal(res, &response) 86 | if err != nil { 87 | return nil, err 88 | } 89 | if response.Reply.Code != 300 { 90 | return nil, fmt.Errorf(response.Reply.Detail) 91 | } 92 | return response.Reply.ResourceRecords, nil 93 | } 94 | func (n *Namesilo) DeleteResolveDomainName(dnsType string, pr string) (bool, error) { 95 | record, err := n.GetRecordByTypeAndPr(dnsType, pr) 96 | if err != nil { 97 | return false, err 98 | } 99 | if record != nil { 100 | b, err := n.DeleteRecord(record.RecordID) 101 | if err != nil { 102 | return false, err 103 | } 104 | return b, nil 105 | } 106 | return true, nil 107 | } 108 | 109 | func (n *Namesilo) GetRecordByTypeAndPr(tp, RR string) (*ResourceRecord, error) { 110 | var record ResourceRecord 111 | recordsResult, err := n.GetRecords() 112 | if err != nil { 113 | return &record, err 114 | } 115 | for _, v := range recordsResult { 116 | if v.Type == tp && v.Host == RR+"."+n.domain { 117 | return &v, nil 118 | } 119 | } 120 | return nil, nil 121 | } 122 | 123 | func (n *Namesilo) DeleteRecord(rrid string) (bool, error) { 124 | query := url.Values{} 125 | query.Add("rrid", rrid) 126 | query.Add("domain", n.domain) 127 | res, err := n.do("dnsDeleteRecord", query) 128 | if err != nil { 129 | return false, err 130 | } 131 | var response struct { 132 | Reply struct { 133 | Code int `xml:"code"` 134 | Detail string `xml:"detail"` 135 | } `xml:"reply"` 136 | } 137 | err = xml.Unmarshal(res, &response) 138 | if err != nil { 139 | return false, err 140 | } 141 | if response.Reply.Code != 300 { 142 | return false, fmt.Errorf(response.Reply.Detail) 143 | } 144 | return true, nil 145 | } 146 | 147 | func (n *Namesilo) do(operation string, query url.Values) ([]byte, error) { 148 | query.Add("version", "1") 149 | query.Add("type", "xml") 150 | query.Add("key", n.apiKey) 151 | return utils.HttpGet(namesiloURL+operation, query) 152 | } 153 | -------------------------------------------------------------------------------- /providers/namesilo_model.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | type ResourceRecord struct { 4 | RecordID string `xml:"record_id"` 5 | Type string `xml:"type"` 6 | Host string `xml:"host"` 7 | Value string `xml:"value"` 8 | TTL int `xml:"ttl"` 9 | Distance int `xml:"distance"` 10 | } 11 | -------------------------------------------------------------------------------- /providers/provider.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | type Provider interface { 4 | ResolveDomainName(dnsType string, pr string, value string) (bool, error) 5 | DeleteResolveDomainName(dnsType string, pr string) (bool, error) 6 | } 7 | -------------------------------------------------------------------------------- /providers/qcloud.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | import ( 4 | "certbot-feehi-hook/utils" 5 | "crypto/hmac" 6 | "crypto/sha1" 7 | "encoding/base64" 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | "log" 12 | "net/url" 13 | "sort" 14 | "strconv" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | const qcloudBaseUrl = "https://cns.api.qcloud.com/v2/index.php" 20 | 21 | func NewQcloud(domain, qcloudAppkey, qcloudAppsecret string) *qcloud { 22 | if qcloudAppkey == "" { 23 | panic("qcloud_SecretId must be passed") 24 | } 25 | if qcloudAppsecret == "" { 26 | panic("qcloud_SecretKey must be passed") 27 | } 28 | domain, levelsDomainName := utils.ParseDomain(domain) 29 | return &qcloud{ 30 | DomainName: domain, 31 | LevelsDomainName: levelsDomainName, 32 | AppKey: qcloudAppkey, 33 | AppSecret: qcloudAppsecret, 34 | } 35 | } 36 | 37 | type qcloud struct { 38 | AppKey string 39 | AppSecret string 40 | DomainName string 41 | LevelsDomainName string 42 | } 43 | 44 | func (q qcloud) ResolveDomainName(dnsType string, RR string, value string) (bool, error) { 45 | var result bool 46 | var err error 47 | 48 | if q.LevelsDomainName != "" { 49 | RR += "." + q.LevelsDomainName 50 | } 51 | 52 | record, err := q.GetRecordByTypeAndPr(dnsType, RR) 53 | 54 | if err != nil { 55 | return false, err 56 | } 57 | if record == nil { //need new add 58 | log.Println("add new record") 59 | result, err = q.AddRecord(dnsType, RR, value) 60 | } else { 61 | log.Println(" record already exist, do update") 62 | result, err = q.UpdateRecord(dnsType, RR, value, strconv.Itoa(record.Id)) 63 | } 64 | if err != nil { 65 | return false, err 66 | } 67 | return result, nil 68 | } 69 | 70 | func (q *qcloud) DeleteResolveDomainName(dnsType string, RR string) (bool, error) { 71 | if q.LevelsDomainName != "" { 72 | RR += "." + q.LevelsDomainName 73 | } 74 | record, err := q.GetRecordByTypeAndPr(dnsType, RR) 75 | if err != nil { 76 | return false, err 77 | } 78 | if record != nil { 79 | result, err := q.DeleteRecord(strconv.Itoa(record.Id)) 80 | if err != nil { 81 | return false, err 82 | } 83 | return result, nil 84 | } 85 | log.Println("Dns record not exists") 86 | return false, nil 87 | } 88 | 89 | func (q *qcloud) GetRecords() (qcloudGetRecordsResult, error) { 90 | var result qcloudGetRecordsResult 91 | businessParams := map[string]string{ 92 | //"subDomain": RR, 93 | "length": "100", 94 | } 95 | body, err := q.Do("RecordList", "GET", businessParams) 96 | if err != nil { 97 | return result, err 98 | } 99 | err = json.Unmarshal(body, &result) 100 | if err != nil { 101 | return result, err 102 | } 103 | if result.Code != 0 { 104 | return result, errors.New(result.Message) 105 | } 106 | return result, nil 107 | } 108 | 109 | func (q *qcloud) AddRecord(dnsType string, RR string, value string) (bool, error) { 110 | businessParams := map[string]string{ 111 | "recordType": dnsType, //TXT 112 | "recordLine": "默认", //www 113 | "subDomain": RR, 114 | "value": value, //8.8.8.8 115 | } 116 | body, err := q.Do("RecordCreate", "GET", businessParams) 117 | if err != nil { 118 | return false, err 119 | } 120 | var b qcloudResult 121 | err = json.Unmarshal(body, &b) 122 | if err != nil { 123 | return false, err 124 | } 125 | if b.Code != 0 { 126 | return false, errors.New(b.Message) 127 | } 128 | return true, nil 129 | } 130 | 131 | func (q *qcloud) UpdateRecord(dnsType string, RR string, value string, recordId string) (bool, error) { 132 | businessParams := map[string]string{ 133 | "recordId": recordId, 134 | "recordType": dnsType, //TXT 135 | "recordLine": "默认", //www 136 | "subDomain": RR, 137 | "value": value, //8.8.8.8 138 | } 139 | body, err := q.Do("RecordModify", "GET", businessParams) 140 | if err != nil { 141 | return false, err 142 | } 143 | var b qcloudResult 144 | err = json.Unmarshal(body, &b) 145 | if err != nil { 146 | return false, err 147 | } 148 | if b.Code != 0 { 149 | return false, errors.New(b.Message) 150 | } 151 | return true, nil 152 | } 153 | 154 | func (q *qcloud) DeleteRecord(recordId string) (bool, error) { 155 | businessParams := map[string]string{ 156 | "recordId": recordId, 157 | } 158 | body, err := q.Do("RecordDelete", "GET", businessParams) 159 | if err != nil { 160 | return false, err 161 | } 162 | var b qcloudResult 163 | err = json.Unmarshal(body, &b) 164 | if err != nil { 165 | return false, err 166 | } 167 | if b.Code != 0 { 168 | return false, errors.New(b.Message) 169 | } 170 | return true, nil 171 | } 172 | 173 | func (q *qcloud) Do(action string, httpRequestMethod string, businessParams map[string]string) ([]byte, error) { 174 | params := map[string]string{ 175 | "Action": action, 176 | "Nonce": utils.GetRandomString(6), 177 | "Timestamp": fmt.Sprintf("%d", time.Now().Unix()), 178 | "SecretId": q.AppKey, 179 | "domain": q.DomainName, 180 | } 181 | for k, v := range businessParams { 182 | params[k] = v 183 | } 184 | signValue := q.signature(action, httpRequestMethod, params) 185 | u := url.Values{} 186 | for k, v := range params { 187 | u.Add(k, v) 188 | } 189 | u.Add("Signature", signValue) 190 | return utils.HttpGet(qcloudBaseUrl, u) 191 | } 192 | 193 | func (q *qcloud) signature(action string, httpRequestMethod string, params map[string]string) string { 194 | var keys = func() []string { 195 | var keys []string 196 | for key := range params { 197 | keys = append(keys, key) 198 | } 199 | return keys 200 | }() 201 | sort.Strings(keys) 202 | var requestParamString string 203 | for _, key := range keys { 204 | requestParamString += "&" + strings.Replace(key, "_", ".", -1) + "=" + params[key] 205 | } 206 | requestParamString = strings.Trim(requestParamString, "&") 207 | u := strings.Replace(qcloudBaseUrl, "https://", "", 1) 208 | stringToSign := httpRequestMethod + u + "?" + requestParamString 209 | key := []byte(q.AppSecret) 210 | mac := hmac.New(sha1.New, key) 211 | mac.Write([]byte(stringToSign)) 212 | b := mac.Sum(nil) 213 | return base64.StdEncoding.EncodeToString(b) 214 | } 215 | 216 | func (q *qcloud) GetRecordByTypeAndPr(tp, RR string) (*qcloudRecord, error) { 217 | var record qcloudRecord 218 | recordsResult, err := q.GetRecords() 219 | if err != nil { 220 | return &record, err 221 | } 222 | for _, v := range recordsResult.Data.Records { 223 | if v.Type == tp && v.Name == RR { 224 | return &v, nil 225 | } 226 | } 227 | return nil, nil 228 | } 229 | -------------------------------------------------------------------------------- /providers/qcloud_model.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | type qcloudResult struct { 4 | Code int 5 | Message string 6 | } 7 | 8 | type qcloudRecord struct { 9 | Id int 10 | Ttl int 11 | Value string 12 | Enabled int 13 | Status string 14 | UpdatedOn string `json:"updated_on"` 15 | QProjectId int `json:"q_project_id"` 16 | Name string 17 | Line string 18 | LineId string `json:"line_id"` 19 | Type string 20 | Remark string 21 | Mx int 22 | Hold string 23 | } 24 | 25 | type qcloudGetRecordsResult struct { 26 | Code int 27 | Message string 28 | CodeDesc string 29 | Data struct { 30 | Domain struct { 31 | Id string 32 | Name string 33 | Punycode string 34 | Grade string 35 | Owner string 36 | ExtStatus string `json:"ext_status"` 37 | Ttl int 38 | MinTtl int `json:"min_ttl"` 39 | DnspodNs []string `json:"dnspod_ns"` 40 | Status string 41 | QProjectId int `json:"q_project_id"` 42 | } 43 | Info struct { 44 | SubDomains string `json:"sub_domains"` 45 | RecordTotal string `json:"record_total"` 46 | } 47 | Records []qcloudRecord 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /providers/qcloud_v3.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | import ( 4 | "certbot-feehi-hook/utils" 5 | "crypto/hmac" 6 | "crypto/sha256" 7 | "encoding/hex" 8 | "encoding/json" 9 | "errors" 10 | "fmt" 11 | "log" 12 | "net/url" 13 | "sort" 14 | "strconv" 15 | "strings" 16 | "time" 17 | ) 18 | 19 | const qcloudV3BaseUrl = "https://dnspod.tencentcloudapi.com" 20 | const service = "dnspod" 21 | const version = "2021-03-23" 22 | const scope = "tc3_request" 23 | 24 | type qcloudV3 struct { 25 | secretId string 26 | secretKey string 27 | domain string 28 | LevelsDomainName string 29 | } 30 | 31 | func NewQcloudV3(domain string, secretId string, secretKey string) *qcloudV3 { 32 | domain, levelsDomainName := utils.ParseDomain(domain) 33 | return &qcloudV3{ 34 | secretId: secretId, 35 | secretKey: secretKey, 36 | domain: domain, 37 | LevelsDomainName: levelsDomainName, 38 | } 39 | } 40 | 41 | func (q *qcloudV3) GetRecords() ([]QcloudV3Record, error) { 42 | payload := map[string]interface{}{ 43 | "Domain": q.domain, 44 | "Limit": 3000, 45 | } 46 | body, err := json.Marshal(payload) 47 | if err != nil { 48 | return nil, err 49 | } 50 | result, err := q.Do(version, "DescribeRecordList", string(body)) 51 | if err != nil { 52 | return nil, err 53 | 54 | } 55 | var response QcloudV3GetRecordsResult 56 | err = json.Unmarshal(result, &response) 57 | if err != nil { 58 | return nil, err 59 | } 60 | if response.Response.Error.Code != "" { 61 | return nil, errors.New(response.Response.Error.Code + " " + response.Response.Error.Message) 62 | } 63 | return response.Response.RecordList, nil 64 | } 65 | 66 | func (q *qcloudV3) AddRecord(dnsType string, RR string, value string) (bool, error) { 67 | payload := map[string]interface{}{ 68 | "Domain": q.domain, 69 | "RecordType": dnsType, 70 | "RecordLine": "默认", 71 | "SubDomain": RR, 72 | "Value": value, 73 | } 74 | body, err := json.Marshal(payload) 75 | if err != nil { 76 | return false, err 77 | } 78 | result, err := q.Do(version, "CreateRecord", string(body)) 79 | if err != nil { 80 | return false, err 81 | 82 | } 83 | var response QcloudV3AddOrUpdateRecordResult 84 | err = json.Unmarshal(result, &response) 85 | if err != nil { 86 | return false, err 87 | } 88 | if response.Response.Error.Code != "" { 89 | return false, errors.New(response.Response.Error.Code + " " + response.Response.Error.Message) 90 | } 91 | if response.Response.RecordId != 0 { 92 | return true, nil 93 | } 94 | return false, errors.New("add record not return a record id") 95 | } 96 | 97 | func (q *qcloudV3) UpdateRecord(dnsType string, RR string, value string, recordId int) (bool, error) { 98 | payload := map[string]interface{}{ 99 | "Domain": q.domain, 100 | "RecordType": dnsType, 101 | "RecordLine": "默认", 102 | "SubDomain": RR, 103 | "Value": value, 104 | "RecordId": recordId, 105 | } 106 | body, err := json.Marshal(payload) 107 | if err != nil { 108 | return false, err 109 | } 110 | result, err := q.Do(version, "ModifyRecord", string(body)) 111 | if err != nil { 112 | return false, err 113 | 114 | } 115 | var response QcloudV3AddOrUpdateRecordResult 116 | err = json.Unmarshal(result, &response) 117 | if err != nil { 118 | return false, err 119 | } 120 | if response.Response.Error.Code != "" { 121 | return false, errors.New(response.Response.Error.Code + " " + response.Response.Error.Message) 122 | } 123 | if response.Response.RecordId != 0 { 124 | return true, nil 125 | } 126 | return false, errors.New("update record not return a record id") 127 | 128 | } 129 | 130 | func (q *qcloudV3) DeleteRecord(recordId int) (bool, error) { 131 | payload := map[string]interface{}{ 132 | "Domain": q.domain, 133 | "RecordId": recordId, 134 | } 135 | body, err := json.Marshal(payload) 136 | if err != nil { 137 | return false, err 138 | } 139 | result, err := q.Do(version, "DeleteRecord", string(body)) 140 | if err != nil { 141 | return false, err 142 | 143 | } 144 | var response QcloudV3GetRecordsResult 145 | err = json.Unmarshal(result, &response) 146 | if err != nil { 147 | return false, err 148 | } 149 | if response.Response.Error.Code != "" { 150 | return false, errors.New(response.Response.Error.Code + " " + response.Response.Error.Message) 151 | } 152 | return true, nil 153 | } 154 | 155 | func (q *qcloudV3) ResolveDomainName(dnsType string, RR string, value string) (bool, error) { 156 | var result bool 157 | var err error 158 | 159 | if q.LevelsDomainName != "" { 160 | RR += "." + q.LevelsDomainName 161 | } 162 | 163 | record, err := q.GetRecordByTypeAndPr(dnsType, RR) 164 | 165 | if err != nil { 166 | return false, err 167 | } 168 | if record == nil { //need new add 169 | log.Println("add new record") 170 | result, err = q.AddRecord(dnsType, RR, value) 171 | } else { 172 | log.Println(" record already exist, do update") 173 | result, err = q.UpdateRecord(dnsType, RR, value, record.RecordId) 174 | } 175 | if err != nil { 176 | return false, err 177 | } 178 | return result, nil 179 | } 180 | func (q *qcloudV3) DeleteResolveDomainName(dnsType string, RR string) (bool, error) { 181 | if q.LevelsDomainName != "" { 182 | RR += "." + q.LevelsDomainName 183 | } 184 | record, err := q.GetRecordByTypeAndPr(dnsType, RR) 185 | if err != nil { 186 | return false, err 187 | } 188 | if record != nil { 189 | result, err := q.DeleteRecord(record.RecordId) 190 | if err != nil { 191 | return false, err 192 | } 193 | return result, nil 194 | } 195 | log.Println("Dns record not exists") 196 | return false, nil 197 | } 198 | 199 | func (q *qcloudV3) GetRecordByTypeAndPr(tp, RR string) (*QcloudV3Record, error) { 200 | var record QcloudV3Record 201 | recordsResult, err := q.GetRecords() 202 | if err != nil { 203 | return &record, err 204 | } 205 | for _, v := range recordsResult { 206 | if v.Type == tp && v.Name == RR { 207 | return &v, nil 208 | } 209 | } 210 | return nil, nil 211 | } 212 | 213 | func (q *qcloudV3) signature(date string, headers url.Values, payload string) (string, string) { 214 | signedHeaderNames := []string{} 215 | signedHeaderRawNames := []string{} 216 | for key := range headers { 217 | signedHeaderNames = append(signedHeaderNames, strings.ToLower(key)) 218 | signedHeaderRawNames = append(signedHeaderRawNames, key) 219 | } 220 | sort.Strings(signedHeaderNames) 221 | sort.Strings(signedHeaderRawNames) 222 | headersKeyValues := "" 223 | for _, headerName := range signedHeaderRawNames { 224 | headersKeyValues += strings.ToLower(headerName) + ":" + strings.ToLower(headers.Get(headerName)) + "\n" 225 | } 226 | payloadHash := sha256.Sum256([]byte(payload)) 227 | payloadHashStr := strings.ToLower(hex.EncodeToString(payloadHash[:])) 228 | 229 | //1. 拼接规范请求串 230 | canonicalRequest := 231 | "POST\n" + // HTTPRequestMethod 232 | "/\n" + //CanonicalURI 233 | "\n" + //CanonicalQueryString 234 | headersKeyValues + "\n" + //CanonicalHeaders 235 | strings.Join(signedHeaderNames, ";") + "\n" + //SignedHeaders 236 | payloadHashStr 237 | //fmt.Println(canonicalRequest) 238 | //2. 拼接待签名字符串 239 | credentialScope := fmt.Sprintf("%s/%s/%s", date, service, scope) 240 | hash := sha256.Sum256([]byte(canonicalRequest)) 241 | hexStr := hex.EncodeToString(hash[:]) 242 | 243 | stringToSign := "TC3-HMAC-SHA256" + "\n" + headers.Get("X-TC-Timestamp") + "\n" + credentialScope + "\n" + strings.ToLower(hexStr) 244 | //fmt.Println(stringToSign) 245 | 246 | // 派生签名密钥 247 | secretDate := hmacSHA256([]byte("TC3"+q.secretKey), date) 248 | secretService := hmacSHA256(secretDate, service) 249 | secretSigning := hmacSHA256(secretService, scope) 250 | 251 | // 最终签名 252 | signatureBytes := hmacSHA256(secretSigning, stringToSign) 253 | signature := hex.EncodeToString(signatureBytes) 254 | return signature, strings.Join(signedHeaderNames, ";") 255 | } 256 | 257 | func (q *qcloudV3) Do(version string, action string, payload string) ([]byte, error) { 258 | 259 | curTimestamp := int(time.Now().Unix()) 260 | //curTimestamp = 1750687184 261 | date := time.Unix(int64(curTimestamp), 0).Format(time.DateOnly) 262 | headers := url.Values{} 263 | headers.Add("X-TC-Action", action) 264 | headers.Add("X-TC-Version", version) 265 | headers.Add("X-TC-Timestamp", strconv.Itoa(curTimestamp)) 266 | headers.Add("Content-Type", "application/json") 267 | headers.Add("Host", "dnspod.tencentcloudapi.com") 268 | signature, signedHeadersStr := q.signature(date, headers, payload) 269 | authorization := fmt.Sprintf("TC3-HMAC-SHA256 Credential=%s/%s/%s/%s, SignedHeaders=%s, Signature=%s", q.secretId, date, service, scope, signedHeadersStr, signature) 270 | //fmt.Println(authorization) 271 | headers.Add("Authorization", authorization) 272 | 273 | return utils.HttpPost(qcloudV3BaseUrl, headers, payload) 274 | } 275 | 276 | func hmacSHA256(key []byte, data string) []byte { 277 | h := hmac.New(sha256.New, key) 278 | h.Write([]byte(data)) 279 | return h.Sum(nil) 280 | } 281 | -------------------------------------------------------------------------------- /providers/qcloud_v3_model.go: -------------------------------------------------------------------------------- 1 | package providers 2 | 3 | type QcloudV3Error struct { 4 | Error struct { 5 | Code string 6 | Message string 7 | } 8 | } 9 | type QcloudV3GetRecordsResult struct { 10 | Response struct { 11 | RequestId string 12 | QcloudV3Error 13 | RecordCountInfo struct { 14 | SubdomainCount int 15 | ListCount int 16 | TotalCount int 17 | } 18 | RecordList []QcloudV3Record 19 | } 20 | } 21 | 22 | type QcloudV3Record struct { 23 | RecordId int 24 | Value string 25 | Statue string 26 | UpdatedOn string 27 | Name string 28 | Line string 29 | LineId string 30 | Type string 31 | Remark string 32 | TTL int 33 | MX int 34 | MonitorStatus string 35 | DefaultNS bool 36 | } 37 | 38 | type QcloudV3AddOrUpdateRecordResult struct { 39 | Response struct { 40 | RequestId string 41 | QcloudV3Error 42 | RecordId int 43 | } 44 | } 45 | 46 | type QcloudV3DeleteRecordResult struct { 47 | Response struct { 48 | RequestId string 49 | QcloudV3Error 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /utils/domain.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "log" 5 | "strings" 6 | ) 7 | 8 | var threePointDomains = []string{ 9 | ".gov.cn", 10 | ".net.cn", 11 | ".com.cn", 12 | ".org.cn", 13 | ".co.uk", 14 | } 15 | 16 | func ParseDomain(domain string) (rootDomain, levelsDomain string) { 17 | rootDomain = "" 18 | levelsDomain = "" 19 | dotNum := strings.Count(domain, ".") 20 | if dotNum < 1 { 21 | log.Fatalln("Domain format error") 22 | } 23 | if dotNum == 1 { 24 | rootDomain = domain 25 | return 26 | } 27 | 28 | var t = 0 29 | var dotTime = 2 30 | for _, item := range threePointDomains { 31 | if strings.HasSuffix(domain, item) { 32 | dotTime = 3 33 | break 34 | } 35 | } 36 | for i := len(domain) - 1; i > -1; i-- { 37 | char := domain[i] 38 | if char == byte('.') { 39 | t++ 40 | } 41 | if t < dotTime { 42 | rootDomain += string(char) 43 | } else { 44 | levelsDomain += string(char) 45 | } 46 | } 47 | rootDomain = reverseString(rootDomain) 48 | levelsDomain = reverseString(levelsDomain) 49 | levelsDomain = strings.TrimRight(levelsDomain, ".") 50 | return 51 | } 52 | 53 | func reverseString(s string) string { 54 | runes := []rune(s) 55 | for from, to := 0, len(runes)-1; from < to; from, to = from+1, to-1 { 56 | runes[from], runes[to] = runes[to], runes[from] 57 | } 58 | return string(runes) 59 | } 60 | -------------------------------------------------------------------------------- /utils/helper.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "math/rand" 5 | "time" 6 | ) 7 | 8 | func GetRandomString(l int) string { 9 | str := "0123456789abcdefghijklmnopqrstuvwxyz" 10 | bytes := []byte(str) 11 | result := []byte{} 12 | r := rand.New(rand.NewSource(time.Now().UnixNano())) 13 | for i := 0; i < l; i++ { 14 | result = append(result, bytes[r.Intn(len(bytes))]) 15 | } 16 | return string(result) 17 | } 18 | -------------------------------------------------------------------------------- /utils/httpclient.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bytes" 5 | "io/ioutil" 6 | "net/http" 7 | "net/url" 8 | ) 9 | 10 | func HttpGet(url string, query url.Values) ([]byte, error) { 11 | url += "?" + query.Encode() 12 | req, err := http.NewRequest("GET", url, nil) 13 | if err != nil { 14 | return nil, err 15 | } 16 | resp, err := http.DefaultClient.Do(req) 17 | if err != nil { 18 | return nil, err 19 | } 20 | defer resp.Body.Close() 21 | result, err := ioutil.ReadAll(resp.Body) 22 | if err != nil { 23 | return nil, err 24 | } 25 | return result, nil 26 | } 27 | 28 | func HttpPost(url string, headers url.Values, payload string) ([]byte, error) { 29 | req, err := http.NewRequest("POST", url, bytes.NewBuffer([]byte(payload))) 30 | if err != nil { 31 | return nil, err 32 | } 33 | for k, vs := range headers { 34 | req.Header[k] = append(req.Header[k], vs...) 35 | } 36 | resp, err := http.DefaultClient.Do(req) 37 | if err != nil { 38 | return nil, err 39 | } 40 | defer resp.Body.Close() 41 | result, err := ioutil.ReadAll(resp.Body) 42 | if err != nil { 43 | return nil, err 44 | } 45 | return result, nil 46 | } 47 | --------------------------------------------------------------------------------