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

打印 上一主题 下一主题

主题 913|帖子 913|积分 2739

媒介

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

最基本的代码功能就是检测网站ssl证书的有效天数,可以用下令行传参的方式指定网站域名。
  1. package main
  2. import (
  3.         "crypto/tls"
  4.         "flag"
  5.         "fmt"
  6.         "net"
  7.         "os"
  8.         "sync"
  9.         "time"
  10. )
  11. var (
  12.         port int
  13.         wg   sync.WaitGroup
  14. )
  15. func checkssl(domain string, port int) {
  16.         defer wg.Done()
  17.         host := fmt.Sprintf("%s:%d", domain, port)
  18.         conn, err := tls.DialWithDialer(&net.Dialer{
  19.                 Timeout:  time.Second * 5,
  20.                 Deadline: time.Now().Add(time.Second * 5),
  21.         }, "tcp", host, &tls.Config{InsecureSkipVerify: true})
  22.         if err != nil {
  23.                 fmt.Println(err)
  24.                 return
  25.         }
  26.         defer conn.Close()
  27.         stats := conn.ConnectionState()
  28.         certs := stats.PeerCertificates[0]
  29.         localtz, _ := time.LoadLocation("Asia/Shanghai")
  30.         issueTime := certs.NotBefore.In(localtz)
  31.         expireTime := certs.NotAfter.In(localtz)
  32.         today := time.Now().In(localtz)
  33.         dayLeft := int(expireTime.Sub(today).Hours() / 24)
  34.         fmt.Printf("%s, issue time: %v, expire time: %v, days left: %v\n", domain, issueTime, expireTime, dayLeft)
  35. }
  36. func main() {
  37.         flag.IntVar(&port, "p", 443, "port, example: ./checkssl -p 1443 <domain name>")
  38.         flag.Parse()
  39.         positionArgs := flag.Args()
  40.         if len(positionArgs) == 0 {
  41.                 fmt.Println("Error: Missing domain name")
  42.                 fmt.Println("Usage: ./checkssl <domain name>")
  43.                 os.Exit(1)
  44.         }
  45.         wg.Add(len(positionArgs))
  46.         for _, arg := range positionArgs {
  47.                 go checkssl(arg, port)
  48.         }
  49.         wg.Wait()
  50. }
复制代码
使用示例
  1. # 1. 编译
  2. go build
  3. # 2. 命令行传参的方式指定域名
  4. ./check-ssl baidu.com ithome.com qq.com
  5. # 输出
  6. 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
  7. 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
  8. 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. domains:
  2.   - baidu.com
  3.   - qq.com
  4. email:
  5.   smtp:
  6.     host: "smtp.163.com"  # smtp服务器的地址
  7.     port: 465  # 因为云服务器屏蔽了25端口, 只能使用tls加密的465端口
  8.     from: ""   # 发送方邮箱
  9.     token: ""  # 授权码
  10.   sendto:
  11.     - "qq@qq.com"  # 接收方的邮箱地址
  12.   expire: 7  # 证书剩余有效天数, 小于7天时发送邮件提醒
复制代码
读取配置的代码文件config.go,使用viper来读取配置文件。
  1. package main
  2. import "github.com/spf13/viper"
  3. var (
  4.         v *viper.Viper
  5. )
  6. type SMTPServer struct {
  7.         Host  string
  8.         Port  int
  9.         Token string
  10.         From  string
  11. }
  12. func initViper() {
  13.         v = viper.New()
  14.         v.AddConfigPath(".")
  15.         v.SetConfigType("yaml")
  16.         v.SetConfigFile(configfile)
  17.         err := v.ReadInConfig()
  18.         if err != nil {
  19.                 panic(err)
  20.         }
  21. }
  22. type configer struct{}
  23. func NewConfiger() configer {
  24.         if v == nil {
  25.                 initViper()
  26.         }
  27.         return configer{}
  28. }
  29. func (c configer) GetSMTPServer() SMTPServer {
  30.         return SMTPServer{
  31.                 Host:  v.GetString("email.smtp.host"),
  32.                 Port:  v.GetInt("email.smtp.port"),
  33.                 Token: v.GetString("email.smtp.token"),
  34.                 From:  v.GetString("email.smtp.from"),
  35.         }
  36. }
  37. func (c configer) GetDomains() []string {
  38.         return v.GetStringSlice("domains")
  39. }
  40. func (c configer) GetSendTos() []string {
  41.         return v.GetStringSlice("email.sendto")
  42. }
  43. func (c configer) GetExpiry() int {
  44.         return v.GetInt("email.expire")
  45. }
复制代码
发送邮件的相关代码文件:notify.go
  1. package main
  2. import (
  3.         "crypto/tls"
  4.         "fmt"
  5.         "net/smtp"
  6.         "github.com/jordan-wright/email"
  7. )
  8. type Postman struct {
  9.         SmtpServer SMTPServer
  10.         SendTos    []string
  11. }
  12. func (p Postman) SendEmail(domain string, dayleft int) {
  13.         auth := smtp.PlainAuth("", p.SmtpServer.From, p.SmtpServer.Token, p.SmtpServer.Host)
  14.         e := &email.Email{
  15.                 To:      p.SendTos,
  16.                 From:    fmt.Sprintf("YXHYW <%s>", p.SmtpServer.From),
  17.                 Subject: fmt.Sprintf("域名 %s SSL证书过期提醒", domain),
  18.                 Text:    []byte(fmt.Sprintf("域名 %s 的SSL证书即将过期, 剩余有效期 %d 天", domain, dayleft)),
  19.         }
  20.         // err := e.Send(fmt.Sprintf("%s:%d", p.SmtpServer.Host, p.SmtpServer.Port), auth)
  21.         addr := fmt.Sprintf("%s:%d", p.SmtpServer.Host, p.SmtpServer.Port)
  22.         fmt.Println("SMTP Server addr: ", addr)
  23.         err := e.SendWithTLS(addr, auth, &tls.Config{
  24.                 InsecureSkipVerify: false,
  25.                 ServerName:         p.SmtpServer.Host,
  26.         })
  27.         if err != nil {
  28.                 fmt.Printf("Send email failed, %v\n", err)
  29.         }
  30. }
复制代码
主体代码文件main.go,紧张修改地方:检测到证书即将过期后,调用发送邮件的相关方法。
  1. package main
  2. import (
  3.         "crypto/tls"
  4.         "flag"
  5.         "fmt"
  6.         "net"
  7.         "sync"
  8.         "time"
  9. )
  10. var (
  11.         port       int
  12.         configfile string
  13.         wg         sync.WaitGroup
  14.         c          configer = NewConfiger()
  15. )
  16. func checkssl(domain string, port int) {
  17.         defer wg.Done()
  18.         host := fmt.Sprintf("%s:%d", domain, port)
  19.         conn, err := tls.DialWithDialer(&net.Dialer{
  20.                 Timeout:  time.Second * 5,
  21.                 Deadline: time.Now().Add(time.Second * 5),
  22.         }, "tcp", host, &tls.Config{InsecureSkipVerify: true})
  23.         if err != nil {
  24.                 fmt.Println(err)
  25.                 return
  26.         }
  27.         defer conn.Close()
  28.         stats := conn.ConnectionState()
  29.         certs := stats.PeerCertificates[0]
  30.         localtz, _ := time.LoadLocation("Asia/Shanghai")
  31.         issueTime := certs.NotBefore.In(localtz)
  32.         expireTime := certs.NotAfter.In(localtz)
  33.         today := time.Now().In(localtz)
  34.         dayLeft := int(expireTime.Sub(today).Hours() / 24)
  35.         fmt.Printf("%s, issue time: %v, expire time: %v, days left: %v\n", domain, issueTime, expireTime, dayLeft)
  36.         // c := NewConfiger()
  37.         if dayLeft < c.GetExpiry() {
  38.                 p := Postman{SmtpServer: c.GetSMTPServer(), SendTos: c.GetSendTos()}
  39.                 p.SendEmail(domain, dayLeft)
  40.         }
  41. }
  42. func main() {
  43.         flag.IntVar(&port, "p", 443, "port, example: ./check-ssl -p 1443 <domain name>")
  44.         flag.StringVar(&configfile, "c", "config.yaml", "config file")
  45.         flag.Parse()
  46.         conf := NewConfiger()
  47.         domains := conf.GetDomains()
  48.         wg.Add(len(domains))
  49.         for _, arg := range domains {
  50.                 go checkssl(arg, port)
  51.         }
  52.         wg.Wait()
  53. }
复制代码
当地测试通过后,可以配到服务器的crontab中每天执行。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

正序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

郭卫东

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表