前言
自从云厂商的免费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证书剩余有效天数内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!
发表评论