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) } } } // 如果配置有通知链接,则发送通知信息 if notice { // 如果有通知信息,则发送通知 if msg != "" { // 如果post为true,即发送飞书富文本信息 if post { // 构造富文本信息 postData := FeishuPostMessage{ MsgType: "post", Content: PostContent{ Post: &Post{ ZhCn: &Message{ Title: "华为云TLS", Content: [][]ContentItem{ { { Tag: "text", TextContent: &TextContent{ Text: msg, }, }, { Tag: "a", AContent: &AContent{ Text: "点击续签TLS证书", Href: postUrl, }, }, }, }, }, }, }, } // 发送通知富文本信息 if err := postRequest(notify, postData); err != nil { fmt.Println("Error sending message:", err) os.Exit(1) } } // 发送text文本信息 // 构造文本信息 textData := FeishuTextMessage{ MsgType: "text", Content: TextContent{ Text: msg, }, } // 发送通知 if err := postRequest(notify, textData); 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) } bin, err := os.Executable() if err != nil { fmt.Println(err) os.Exit(1) } binPath := filepath.Dir(bin) viper.AddConfigPath(".") viper.AddConfigPath(home) viper.AddConfigPath(binPath) 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 > -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 }