如何使用工具自動(dòng)監(jiān)測(cè)SSL證書(shū)有效期并發(fā)送提醒郵件
前言
自從云廠商的免費(fèi)ssl證書(shū)改成3個(gè)月,而且證書(shū)數(shù)量還是20個(gè)之后,自己網(wǎng)站的ssl證書(shū)就換成了其它免費(fèi)方案。但是免費(fèi)方案不會(huì)提醒證書(shū)過(guò)期,所以寫(xiě)個(gè)工具每天定時(shí)查詢證書(shū)剩余有效天數(shù),如果證書(shū)即將過(guò)期,就發(fā)送郵件提醒。
基本實(shí)現(xiàn)
最基本的代碼功能就是檢測(cè)網(wǎng)站ssl證書(shū)的有效天數(shù),可以用命令行傳參的方式指定網(wǎng)站域名。
package main import ( "crypto/tls" "flag" "fmt" "net" "os" "sync" "time" ) var ( port int wg sync.WaitGroup ) func checkssl(domain string, port int) { defer wg.Done() host := fmt.Sprintf("%s:%d", domain, port) conn, err := tls.DialWithDialer(&net.Dialer{ Timeout: time.Second * 5, Deadline: time.Now().Add(time.Second * 5), }, "tcp", host, &tls.Config{InsecureSkipVerify: true}) if err != nil { fmt.Println(err) return } defer conn.Close() stats := conn.ConnectionState() certs := stats.PeerCertificates[0] localtz, _ := time.LoadLocation("Asia/Shanghai") issueTime := certs.NotBefore.In(localtz) expireTime := certs.NotAfter.In(localtz) today := time.Now().In(localtz) dayLeft := int(expireTime.Sub(today).Hours() / 24) fmt.Printf("%s, issue time: %v, expire time: %v, days left: %v\n", domain, issueTime, expireTime, dayLeft) } func main() { flag.IntVar(&port, "p", 443, "port, example: ./checkssl -p 1443 <domain name>") flag.Parse() positionArgs := flag.Args() if len(positionArgs) == 0 { fmt.Println("Error: Missing domain name") fmt.Println("Usage: ./checkssl <domain name>") os.Exit(1) } wg.Add(len(positionArgs)) for _, arg := range positionArgs { go checkssl(arg, port) } wg.Wait() }
使用示例
# 1. 編譯 go build # 2. 命令行傳參的方式指定域名 ./check-ssl baidu.com ithome.com qq.com # 輸出 baidu.com, issue time: 2024-01-30 08:00:00 +0800 CST, expire time: 2025-03-02 07:59:59 +0800 CST, days left: 187 ithome.com, issue time: 2024-01-22 08:00:00 +0800 CST, expire time: 2025-02-22 07:59:59 +0800 CST, days left: 179 qq.com, issue time: 2024-06-04 08:00:00 +0800 CST, expire time: 2025-06-11 07:59:59 +0800 CST, days left: 288
完善功能
需要完善的功能主要是發(fā)送郵件,這里使用SMTP協(xié)議來(lái)發(fā)送郵件。如果跟我一樣用的是163郵箱,則需要先去獲取一個(gè)SMTP的授權(quán)碼。
因?yàn)樾枰渲肧MTP的連接信息,所以改成了用文件來(lái)傳入配置,也方便后期修改。配置文件config.yaml
示例:
domains: - baidu.com - qq.com email: smtp: host: "smtp.163.com" # smtp服務(wù)器的地址 port: 465 # 因?yàn)樵品?wù)器屏蔽了25端口, 只能使用tls加密的465端口 from: "" # 發(fā)送方郵箱 token: "" # 授權(quán)碼 sendto: - "qq@qq.com" # 接收方的郵箱地址 expire: 7 # 證書(shū)剩余有效天數(shù), 小于7天時(shí)發(fā)送郵件提醒
讀取配置的代碼文件config.go
,使用viper
來(lái)讀取配置文件。
package main import "github.com/spf13/viper" var ( v *viper.Viper ) type SMTPServer struct { Host string Port int Token string From string } func initViper() { v = viper.New() v.AddConfigPath(".") v.SetConfigType("yaml") v.SetConfigFile(configfile) err := v.ReadInConfig() if err != nil { panic(err) } } type configer struct{} func NewConfiger() configer { if v == nil { initViper() } return configer{} } func (c configer) GetSMTPServer() SMTPServer { return SMTPServer{ Host: v.GetString("email.smtp.host"), Port: v.GetInt("email.smtp.port"), Token: v.GetString("email.smtp.token"), From: v.GetString("email.smtp.from"), } } func (c configer) GetDomains() []string { return v.GetStringSlice("domains") } func (c configer) GetSendTos() []string { return v.GetStringSlice("email.sendto") } func (c configer) GetExpiry() int { return v.GetInt("email.expire") }
發(fā)送郵件的相關(guān)代碼文件:notify.go
package main import ( "crypto/tls" "fmt" "net/smtp" "github.com/jordan-wright/email" ) type Postman struct { SmtpServer SMTPServer SendTos []string } func (p Postman) SendEmail(domain string, dayleft int) { auth := smtp.PlainAuth("", p.SmtpServer.From, p.SmtpServer.Token, p.SmtpServer.Host) e := &email.Email{ To: p.SendTos, From: fmt.Sprintf("YXHYW <%s>", p.SmtpServer.From), Subject: fmt.Sprintf("域名 %s SSL證書(shū)過(guò)期提醒", domain), Text: []byte(fmt.Sprintf("域名 %s 的SSL證書(shū)即將過(guò)期, 剩余有效期 %d 天", domain, dayleft)), } // err := e.Send(fmt.Sprintf("%s:%d", p.SmtpServer.Host, p.SmtpServer.Port), auth) addr := fmt.Sprintf("%s:%d", p.SmtpServer.Host, p.SmtpServer.Port) fmt.Println("SMTP Server addr: ", addr) err := e.SendWithTLS(addr, auth, &tls.Config{ InsecureSkipVerify: false, ServerName: p.SmtpServer.Host, }) if err != nil { fmt.Printf("Send email failed, %v\n", err) } }
主體代碼文件main.go
,主要修改地方:檢測(cè)到證書(shū)即將過(guò)期后,調(diào)用發(fā)送郵件的相關(guān)方法。
package main import ( "crypto/tls" "flag" "fmt" "net" "sync" "time" ) var ( port int configfile string wg sync.WaitGroup c configer = NewConfiger() ) func checkssl(domain string, port int) { defer wg.Done() host := fmt.Sprintf("%s:%d", domain, port) conn, err := tls.DialWithDialer(&net.Dialer{ Timeout: time.Second * 5, Deadline: time.Now().Add(time.Second * 5), }, "tcp", host, &tls.Config{InsecureSkipVerify: true}) if err != nil { fmt.Println(err) return } defer conn.Close() stats := conn.ConnectionState() certs := stats.PeerCertificates[0] localtz, _ := time.LoadLocation("Asia/Shanghai") issueTime := certs.NotBefore.In(localtz) expireTime := certs.NotAfter.In(localtz) today := time.Now().In(localtz) dayLeft := int(expireTime.Sub(today).Hours() / 24) fmt.Printf("%s, issue time: %v, expire time: %v, days left: %v\n", domain, issueTime, expireTime, dayLeft) // c := NewConfiger() if dayLeft < c.GetExpiry() { p := Postman{SmtpServer: c.GetSMTPServer(), SendTos: c.GetSendTos()} p.SendEmail(domain, dayLeft) } } func main() { flag.IntVar(&port, "p", 443, "port, example: ./check-ssl -p 1443 <domain name>") flag.StringVar(&configfile, "c", "config.yaml", "config file") flag.Parse() conf := NewConfiger() domains := conf.GetDomains() wg.Add(len(domains)) for _, arg := range domains { go checkssl(arg, port) } wg.Wait() }
本地測(cè)試通過(guò)后,可以配到服務(wù)器的crontab
中每天執(zhí)行。
到此這篇關(guān)于如何使用工具自動(dòng)監(jiān)測(cè)SSL證書(shū)有效期并發(fā)送提醒郵件的文章就介紹到這了,更多相關(guān)[golang]查詢ssl證書(shū)剩余有效天數(shù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang小數(shù)操作指南之判斷小數(shù)點(diǎn)位數(shù)與四舍五入
這篇文章主要給大家介紹了關(guān)于Golang小數(shù)操作指南之判斷小數(shù)點(diǎn)位數(shù)與四舍五入的相關(guān)資料,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2022-03-03golang bad file descriptor問(wèn)題的解決方法
這篇文章主要給大家介紹了golang bad file descriptor問(wèn)題的解決方法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-02-02golang中按照結(jié)構(gòu)體的某個(gè)字段排序?qū)嵗a
在任何編程語(yǔ)言中,關(guān)乎到數(shù)據(jù)的排序都會(huì)有對(duì)應(yīng)的策略,下面這篇文章主要給大家介紹了關(guān)于golang中按照結(jié)構(gòu)體的某個(gè)字段排序的相關(guān)資料,需要的朋友可以參考下2022-05-05GoFrame錯(cuò)誤處理常用方法及錯(cuò)誤碼使用示例
這篇文章主要為大家介紹了GoFrame錯(cuò)誤處理常用方法及錯(cuò)誤碼使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06Go語(yǔ)言break跳轉(zhuǎn)語(yǔ)句怎么使用
這篇文章主要介紹了Go語(yǔ)言break跳轉(zhuǎn)語(yǔ)句怎么使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2023-01-01詳解golang channel有無(wú)緩沖區(qū)的區(qū)別
這篇文章主要給大家介紹了golang channel有無(wú)緩沖區(qū)的區(qū)別,無(wú)緩沖是同步的,有緩沖是異步的,文中通過(guò)代碼示例給大家講解的非常詳細(xì),需要的朋友可以參考下2024-01-01Qt6.5 grpc組件使用 + golang grpc server
這篇文章主要介紹了Qt6.5 grpc組件使用+golang grpc server示例,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-05-05