webhook-tools/scripts/tlsupdate/main.go
2024-08-16 17:48:07 +08:00

376 lines
9.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"net/url"
"os"
"path/filepath"
"time"
"github.com/huaweicloud/huaweicloud-sdk-go-v3/core/auth/basic"
elb "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3"
elbModel "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/model"
elbRegion "github.com/huaweicloud/huaweicloud-sdk-go-v3/services/elb/v3/region"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var (
cfgFile string
action string
region string
id string
msg string
notice bool = false
)
var rootCmd = &cobra.Command{
Use: "tlsupdate",
Short: "check and update tls",
Run: func(cmd *cobra.Command, args []string) {
// 如果配置文件存在,则读取配置文件
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
if err := viper.ReadInConfig(); err != nil {
fmt.Println("Error reading config file:", err)
os.Exit(1)
}
}
// 获取配置
ak := viper.GetString("huawei.ak")
sk := viper.GetString("huawei.sk")
notify := viper.GetString("notify")
webhook := viper.GetString("webhook")
tlsConfig := viper.Get("tls").([]interface{})
// 获取证书id列表
var ids []string
for _, tls := range tlsConfig {
tlsConfigMap := tls.(map[string]interface{})
ids = append(ids, tlsConfigMap["id"].(string))
}
// 如果配置文件中提供飞书链接,则通知用户
if notify != "" {
// 是否通知
notice = true
}
//发送用户格式默认是text文本
post := false
// 富文本中的链接
postUrl := ""
// 遍历配置中tls列表如果id匹配即更新或查看证书状态
for _, t := range tlsConfig {
tlsConfigmap := t.(map[string]interface{})
if id == "" {
// id为空不能更新不支持批量更新所有证书
if action == "update" {
msg = "请求出错只能指定id更新tls"
os.Exit(1)
}
// id为空即获取所有证书的有效期
if action == "status" {
for _, i := range ids {
if tlsConfigmap["id"].(string) == i {
region = tlsConfigmap["region"].(string)
// 如果未配置region默认为上海-1
if region == "" {
region = "cn-east-3"
}
// 获取状态信息
post, msg = genStatusMsg(region, ak, sk, i)
// 构造post请求链接
postUrl = generateUrl(webhook, "", map[string]interface{}{
"action": "update",
"id": id,
})
}
}
}
}
// id不为空根据id更新或查看证书有效期
if tlsConfigmap["id"].(string) == id {
key, err := readFile(tlsConfigmap["private_key"].(string))
if err != nil {
msg = fmt.Sprintf("读取key文件出错%s", err)
}
cert, err := readFile(tlsConfigmap["certificate"].(string))
if err != nil {
msg = fmt.Sprintf("读取cert文件出错%s", err)
}
region = tlsConfigmap["region"].(string)
if region == "" {
region = "cn-east-3"
}
if action == "status" {
post, msg = genStatusMsg(region, ak, sk, id)
}
if action == "update" {
msg = genUpdateMsg(region, ak, sk, id, key, cert)
}
postUrl = generateUrl(webhook, "", map[string]interface{}{
"action": "update",
"id": id,
})
}
}
// 如果配置有通知链接,则发送通知信息
if notice {
// 如果有通知信息,则发送通知
if msg != "" {
var data interface{}
// 如果post为true即发送飞书富文本信息
if post {
// 构造富文本信息
data = genPostAMsg("华为云TLS", msg, "点击续签TLS证书", postUrl)
} else {
// 发送text文本信息
// 构造文本信息
data = FeishuTextMessage{
MsgType: "text",
Content: TextContent{
Text: msg,
},
}
}
// 发送通知
if err := postRequest(notify, data); err != nil {
fmt.Println("Error sending message:", err)
os.Exit(1)
}
}
}
},
}
func main() {
cobra.OnInitialize(initConfig)
rootCmd.Flags().StringVarP(&cfgFile, "config", "c", "", "Config file (default is $HOME/config.yaml, ./config.yaml, or BINARY/config.yaml)")
rootCmd.Flags().StringVarP(&action, "action", "a", "status", "update or status")
rootCmd.Flags().StringVarP(&id, "id", "i", "", "tls id")
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
// 初始化配置文件
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
exec, err := os.Executable()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
exPath := filepath.Dir(exec)
viper.AddConfigPath(".")
viper.AddConfigPath(home)
viper.AddConfigPath(exPath)
viper.SetConfigName("config")
}
viper.AutomaticEnv() // 从环境变量中读取配置
if err := viper.ReadInConfig(); err == nil {
fmt.Println("Using config file:", viper.ConfigFileUsed())
}
}
// 构建url
func generateUrl(baseUrl, source string, parameters map[string]interface{}) string {
base, err := url.Parse(baseUrl)
if err != nil {
return ""
}
base.Path += "/" + source
query := base.Query()
for param, value := range parameters {
query.Add(param, fmt.Sprintf("%v", value))
}
base.RawQuery = query.Encode()
return base.String()
}
// post请求
func postRequest(url string, data interface{}) error {
jsonData, err := json.Marshal(data)
if err != nil {
return err
}
resp, err := http.Post(url, "application/json", bytes.NewBuffer(jsonData))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("HTTP status code: %d", resp.StatusCode)
}
return nil
}
// 读取文件
func readFile(path string) (string, error) {
data, err := os.ReadFile(path)
if err != nil {
return "", err
}
return string(data), nil
}
// 华为云ak,sk登录校验
func authenticate(region, ak, sk string) *elb.ElbClient {
auth, err := basic.NewCredentialsBuilder().
WithAk(ak).
WithSk(sk).
SafeBuild()
if err != nil {
fmt.Println("Error creating credentials:", err)
os.Exit(1)
}
r, err := elbRegion.SafeValueOf(region)
if err != nil {
fmt.Println("Error creating credentials:", err)
os.Exit(1)
}
elbClient, err := elb.ElbClientBuilder().WithRegion(r).WithCredential(auth).SafeBuild()
if err != nil {
fmt.Println("Error creating credentials:", err)
os.Exit(1)
}
client := elb.NewElbClient(elbClient)
return client
}
// tls有效期
func tlsValidity(region, ak, sk, id string) ([]string, string, error) {
client := authenticate(region, ak, sk)
request := &elbModel.ShowCertificateRequest{
CertificateId: id,
}
response, err := client.ShowCertificate(request)
if err != nil {
fmt.Println("Error creating credentials:", err)
return nil, "", err
}
return *response.Certificate.SubjectAlternativeNames, response.Certificate.ExpireTime, nil
}
// 时间差
func timeDiff(expire string) (bool, error) {
// 解析时间字符串
t, err := time.Parse(time.RFC3339, expire)
if err != nil {
fmt.Println("解析证书过期时间错误:", err)
return false, err
}
// 获取当前时间
now := time.Now()
// 计算时间差
duration := now.Sub(t)
// 检查时间差是否小于1天
if duration > -7*24*time.Hour {
return true, nil
}
return false, nil
}
// 更新tls
func updateTLS(region, ak, sk, id, key, cert string) ([]string, error) {
client := authenticate(region, ak, sk)
request := &elbModel.UpdateCertificateRequest{
CertificateId: id,
}
certificatebody := &elbModel.UpdateCertificateOption{
PrivateKey: &key,
Certificate: &cert,
}
request.Body = &elbModel.UpdateCertificateRequestBody{
Certificate: certificatebody,
}
response, err := client.UpdateCertificate(request)
if err != nil {
fmt.Println("Error creating credentials:", err)
return []string{}, err
}
return *response.Certificate.SubjectAlternativeNames, nil
}
// 构造状态通知信息
func genStatusMsg(region, ak, sk, id string) (bool, string) {
var message string
domain, expire, err := tlsValidity(region, ak, sk, id)
if err != nil {
message = fmt.Sprintf("获取证书信息失败,请检查配置文件或代码,错误信息:%v", err)
return false, message
}
diff, err := timeDiff(expire)
if err != nil {
message = fmt.Sprintf("解析证书过期时间错误,请检查:%v", err)
return false, message
}
if diff {
message = fmt.Sprintf("域名:%v\n\n证书有效期不足一周请及时更新", domain)
return true, message
}
return false, message
}
// 构造更新通知信息
func genUpdateMsg(region, ak, sk, id, key, cert string) string {
var message string
domain, err := updateTLS(region, ak, sk, id, key, cert)
if err != nil {
message = fmt.Sprintf("Error updating TLS: %v", err)
return message
}
message = fmt.Sprintf("域名:%v\nhttps证书更新成功", domain)
return message
}
func genPostAMsg(title, message, text, url string) *FeishuPostMessage {
return &FeishuPostMessage{
MsgType: "post",
Content: PostContent{
Post: Post{
ZhCn: Message{
Title: title,
Content: [][]ContentItem{
{
{
Tag: "text",
Text: message,
},
{
Tag: "a",
Text: text,
Href: url,
},
},
},
},
},
},
}
}