| 
                            
                                  自从云厂商的免费ssl证书改成3个月,而且证书数量还是20个之后,自己网站的ssl证书就换成了其它免费方案。但是免费方案不会提醒证书过期,所以写个工具每天定时查询证书剩余有效天数,如果证书即将过期,就发送邮件提醒。 基本实现最基本的代码功能就是检测网站ssl证书的有效天数,可以用命令行传参的方式指定网站域名。 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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 2 3 4 5 6 7 8 9 | # 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示例: 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 | 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来读取配置文件。 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | 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 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | 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, %v\n", err)     } } |  主体代码文件main.go,主要修改地方:检测到证书即将过期后,调用发送邮件的相关方法。 
	
		
			| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 | 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() } |  本地测试通过后,可以配到服务器的crontab中每天执行。 
 |