如何使用工具自动监测SSL证书有效期并发送提醒邮件

 更新时间:2024年10月01日 16:46:31   作者:花酒锄作田  
本文介绍了如何开发一个工具,用于每日检测SSL证书剩余有效天数并通过邮件发送提醒,工具基于命令行,通过SMTP协议发送邮件,需配置SMTP连接信息,本文还提供了配置文件样例及代码实现,帮助用户轻松部署和使用该工具

前言

自从云厂商的免费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: %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

完善功能

需要完善的功能主要是发送邮件,这里使用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, %v\n", 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: %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中每天执行。

到此这篇关于如何使用工具自动监测SSL证书有效期并发送提醒邮件的文章就介绍到这了,更多相关[golang]查询ssl证书剩余有效天数内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • golang gc的内部优化详细介绍

    golang gc的内部优化详细介绍

    Go编译器在垃圾回收(GC)的扫描标记阶段,对存储无指针键值对的map进行了优化,即在GC扫描时不深入扫描map内部数据,只检查map本身是否需要回收,这一优化显著提升了GC扫描的速度,从而减少了GC对程序性能的影响
    2024-10-10
  • Go泛型的理解和使用小结

    Go泛型的理解和使用小结

    泛型是一种非常强大的编程技术,可以提高代码的复用性和可读性,通过泛型容器和类型参数化,Go语言中的泛型可以实现更加灵活和通用的编程,提高代码的复用性和可维护性,本文给大家介绍Go泛型的理解和使用,感兴趣的朋友一起看看吧
    2023-12-12
  • 深入浅出go依赖注入工具Wire的使用

    深入浅出go依赖注入工具Wire的使用

    但随着项目规模的增长,组件之间的依赖关系变得复杂,手动管理可能会很繁琐,所以本文将深入探讨一个备受欢迎的 Go 语言依赖注入工具—— Wire,感兴趣的可以了解下
    2023-09-09
  • GoFrame框架ORM原生方法对象操作开箱体验

    GoFrame框架ORM原生方法对象操作开箱体验

    这篇文章主要为大家介绍了GoFrame框架ORM原生方法对象操作的开箱体验,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-06-06
  • Go使用XORM操作MySQL的陷阱盘点分析

    Go使用XORM操作MySQL的陷阱盘点分析

    在 Go 语言开发中,大家为了方便,通常会选择使用 ORM 操作数据库,比如使用 XORM 或 GORM 操作 MySQL,本文我们来介绍一下使用 XORM[3] 操作 MySQL 可能会遇到的陷阱
    2023-11-11
  • go语言通过odbc访问Sql Server数据库的方法

    go语言通过odbc访问Sql Server数据库的方法

    这篇文章主要介绍了go语言通过odbc访问Sql Server数据库的方法,实例分析了Go语言通过odbc连接与查SQL Server询数据库的技巧,需要的朋友可以参考下
    2015-03-03
  • Go官方工具链用法详解

    Go官方工具链用法详解

    Go官方工具链工具要求所有的Go源代码文件必须以.go后缀结尾。这里,我们假设一个最简单的Go程序放在hello.go的文件中,下面通过示例代码给大家介绍Go官方工具链用法简介,需要的朋友可以参考下
    2021-10-10
  • golang qq邮件发送验证码功能

    golang qq邮件发送验证码功能

    验证码在多个场景下发挥着重要作用,如注册/登录、短信接口保护、提交/投票、密码找回和支付验证等,以保障账号安全和防止恶意操作,此外,文章还介绍了使用golang通过qq邮件发送验证码的实现过程,包括获取授权码、下载依赖包和编写代码,感兴趣的朋友跟随小编一起看看吧
    2024-09-09
  • Golang加权轮询负载均衡的实现

    Golang加权轮询负载均衡的实现

    负载均衡器在向后端服务分发流量负载时可以使用几种策略。本文主要介绍了Golang加权轮询负载均衡,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-06-06
  • 一文搞懂Go语言中文件的读写与创建

    一文搞懂Go语言中文件的读写与创建

    这篇文章主要为大家详细介绍了Go语言中文件是如何实现读写与创建的,文中的示例代码讲解详细,对我们学习Go语言有一定帮助,需要的可以参考一下
    2022-07-07

最新评论