[golang]查询ssl证书剩余有效天数并邮件提醒

前言

自从云厂商的免费ssl证书改成3个月,而且证书数量还是20个之后,自己网站的ssl证书就换成了其它免费方案。但是免费方案不会提醒证书过期,所以写个工具每天定时查询证书剩余有效天数,如果证书即将过期,就发送邮件提醒。

基本实现

最基本的代码功能就是检测网站ssl证书的有效天数,可以用命令行传参的方式指定网站域名。

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: %vn", 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 

完善功能

需要完善的功能主要是发送邮件,这里使用SMTP协议来发送邮件。如果跟我一样用的是163邮箱,则需要先去获取一个SMTP的授权码。

因为需要配置SMTP的连接信息,所以改成了用文件来传入配置,也方便后期修改。配置文件config.yaml示例:

domains:   - baidu.com   - qq.com  email:   smtp:     host: "smtp.163.com"  # smtp服务器的地址     port: 465  # 因为云服务器屏蔽了25端口, 只能使用tls加密的465端口     from: ""   # 发送方邮箱     token: ""  # 授权码   sendto:     - "qq@qq.com"  # 接收方的邮箱地址   expire: 7  # 证书剩余有效天数, 小于7天时发送邮件提醒 

读取配置的代码文件config.go,使用viper来读取配置文件。

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") }  

发送邮件的相关代码文件: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证书过期提醒", domain), 		Text:    []byte(fmt.Sprintf("域名 %s 的SSL证书即将过期, 剩余有效期 %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, %vn", err) 	} } 

主体代码文件main.go,主要修改地方:检测到证书即将过期后,调用发送邮件的相关方法。

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: %vn", 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() } 

本地测试通过后,可以配到服务器的crontab中每天执行。

发表评论

评论已关闭。

相关文章