206 lines
6.4 KiB
Go
Executable file
206 lines
6.4 KiB
Go
Executable file
package main
|
||
|
||
import (
|
||
"crypto/x509"
|
||
"encoding/pem"
|
||
"fmt"
|
||
"log"
|
||
"os"
|
||
"path/filepath"
|
||
"strings"
|
||
"sync"
|
||
"time"
|
||
)
|
||
|
||
// Поддерживаемые расширения сертификатов
|
||
var ccValidExtensions = map[string]bool{
|
||
".crt": true, ".pem": true, ".cer": true, ".cert": true,
|
||
".der": true, ".p7b": true, ".p7c": true, ".p7s": true, ".csr": true,
|
||
}
|
||
|
||
type ccCertInfo struct {
|
||
Path string
|
||
NotAfter time.Time
|
||
DaysLeft int
|
||
CommonName string
|
||
Format string
|
||
}
|
||
|
||
// runCertCheck запускает проверку сертификатов
|
||
func runCertCheck() error {
|
||
hostname, err := os.Hostname()
|
||
if err != nil {
|
||
hostname = "unknown"
|
||
}
|
||
|
||
log.Printf("Начинаю проверку сертификатов в %s на хосте: %s", cfgSSLDir, hostname)
|
||
|
||
certPaths, err := ccFindCerts(cfgSSLDir)
|
||
if err != nil {
|
||
return fmt.Errorf("ошибка при поиске сертификатов: %v", err)
|
||
}
|
||
|
||
log.Printf("Найдено файлов сертификатов: %d", len(certPaths))
|
||
|
||
pathsChan := make(chan string, len(certPaths))
|
||
resultsChan := make(chan ccCertInfo, len(certPaths))
|
||
var wg sync.WaitGroup
|
||
|
||
for i := 0; i < cfgMaxWorkers; i++ {
|
||
wg.Add(1)
|
||
go func() {
|
||
defer wg.Done()
|
||
for path := range pathsChan {
|
||
info, err := ccParseCert(path)
|
||
if err != nil {
|
||
log.Printf("Не удалось распарсить %s: %v", path, err)
|
||
continue
|
||
}
|
||
resultsChan <- info
|
||
}
|
||
}()
|
||
}
|
||
|
||
for _, p := range certPaths {
|
||
pathsChan <- p
|
||
}
|
||
close(pathsChan)
|
||
|
||
go func() {
|
||
wg.Wait()
|
||
close(resultsChan)
|
||
}()
|
||
|
||
validCerts := 0
|
||
for cert := range resultsChan {
|
||
validCerts++
|
||
ccCheckAndAlert(cert, hostname)
|
||
}
|
||
|
||
log.Printf("Успешно обработано сертификатов: %d", validCerts)
|
||
log.Println("Проверка завершена")
|
||
return nil
|
||
}
|
||
|
||
func ccFindCerts(root string) ([]string, error) {
|
||
var paths []string
|
||
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
|
||
if err != nil {
|
||
log.Printf("Ошибка доступа к %s: %v", path, err)
|
||
return nil
|
||
}
|
||
if info.IsDir() {
|
||
return nil
|
||
}
|
||
ext := strings.ToLower(filepath.Ext(path))
|
||
if ccValidExtensions[ext] {
|
||
paths = append(paths, path)
|
||
}
|
||
return nil
|
||
})
|
||
return paths, err
|
||
}
|
||
|
||
func ccParseCert(path string) (ccCertInfo, error) {
|
||
data, err := os.ReadFile(path)
|
||
if err != nil {
|
||
return ccCertInfo{}, err
|
||
}
|
||
|
||
var cert *x509.Certificate
|
||
format := "Unknown"
|
||
|
||
block, _ := pem.Decode(data)
|
||
if block != nil {
|
||
format = "PEM"
|
||
switch block.Type {
|
||
case "CERTIFICATE":
|
||
cert, err = x509.ParseCertificate(block.Bytes)
|
||
case "CERTIFICATE REQUEST":
|
||
return ccCertInfo{}, fmt.Errorf("это CSR файл, а не сертификат")
|
||
default:
|
||
return ccCertInfo{}, fmt.Errorf("неподдерживаемый тип PEM блока: %s", block.Type)
|
||
}
|
||
} else {
|
||
format = "DER"
|
||
cert, err = x509.ParseCertificate(data)
|
||
}
|
||
|
||
if err != nil {
|
||
return ccCertInfo{}, err
|
||
}
|
||
|
||
daysLeft := int(time.Until(cert.NotAfter).Hours() / 24)
|
||
return ccCertInfo{
|
||
Path: path, NotAfter: cert.NotAfter, DaysLeft: daysLeft,
|
||
CommonName: cert.Subject.CommonName, Format: format,
|
||
}, nil
|
||
}
|
||
|
||
func ccCheckAndAlert(cert ccCertInfo, hostname string) {
|
||
log.Printf("Сертификат: %s | Format: %s | CN: %s | Истекает: %s | Дней осталось: %d",
|
||
cert.Path, cert.Format, cert.CommonName, cert.NotAfter.Format("2006-01-02"), cert.DaysLeft)
|
||
|
||
var message, emoji string
|
||
|
||
switch {
|
||
case cert.DaysLeft < 0:
|
||
emoji = "💀💀💀"
|
||
message = fmt.Sprintf(
|
||
"%s СЕРТИФИКАТ УЖЕ ИСТЁК! %s\n\n"+
|
||
"🖥 Хост: <b>%s</b>\n📄 Путь: <code>%s</code>\n🔐 CN: %s\n"+
|
||
"📅 Истёк: %s\n⏱ Просрочен на: %d дней\n📦 Формат: %s\n\n"+
|
||
"🔥 КРИТИЧЕСКАЯ СИТУАЦИЯ! СЕРВИС МОЖЕТ НЕ РАБОТАТЬ!",
|
||
emoji, emoji, hostname, cert.Path, cert.CommonName,
|
||
cert.NotAfter.Format("2006-01-02 15:04:05"), -cert.DaysLeft, cert.Format)
|
||
|
||
case cert.DaysLeft <= cfgCertCriticalDays:
|
||
emoji = "🚨🚨🚨"
|
||
message = fmt.Sprintf(
|
||
"%s КРИТИЧНО! НЕМЕДЛЕННО ОБНОВИТЕ СЕРТИФИКАТ! %s\n\n"+
|
||
"🖥 Хост: <b>%s</b>\n📄 Путь: <code>%s</code>\n🔐 CN: %s\n"+
|
||
"📅 Истекает: %s\n⏱ Осталось дней: %d\n📦 Формат: %s\n\n"+
|
||
"⚠️ ТРЕБУЕТСЯ НЕМЕДЛЕННОЕ ДЕЙСТВИЕ!",
|
||
emoji, emoji, hostname, cert.Path, cert.CommonName,
|
||
cert.NotAfter.Format("2006-01-02 15:04:05"), cert.DaysLeft, cert.Format)
|
||
|
||
case cert.DaysLeft <= cfgCertWarningDays:
|
||
emoji = "⚠️⚠️"
|
||
message = fmt.Sprintf(
|
||
"%s ВНИМАНИЕ! Срочно требуется обновление сертификата! %s\n\n"+
|
||
"🖥 Хост: <b>%s</b>\n📄 Путь: <code>%s</code>\n🔐 CN: %s\n"+
|
||
"📅 Истекает: %s\n⏱ Осталось дней: %d\n📦 Формат: %s\n\n"+
|
||
"Запланируйте обновление в ближайшее время!",
|
||
emoji, emoji, hostname, cert.Path, cert.CommonName,
|
||
cert.NotAfter.Format("2006-01-02 15:04:05"), cert.DaysLeft, cert.Format)
|
||
|
||
case cert.DaysLeft <= cfgCertInfoDays:
|
||
emoji = "ℹ️"
|
||
message = fmt.Sprintf(
|
||
"%s Информация: сертификат скоро истечёт\n\n"+
|
||
"🖥 Хост: <b>%s</b>\n📄 Путь: <code>%s</code>\n🔐 CN: %s\n"+
|
||
"📅 Истекает: %s\n⏱ Осталось дней: %d\n📦 Формат: %s\n\n"+
|
||
"Рекомендуется запланировать обновление.",
|
||
emoji, hostname, cert.Path, cert.CommonName,
|
||
cert.NotAfter.Format("2006-01-02 15:04:05"), cert.DaysLeft, cert.Format)
|
||
|
||
default:
|
||
return // всё ок, алерт не нужен
|
||
}
|
||
|
||
if err := telegramSendHTML(message); err != nil {
|
||
log.Printf("Ошибка отправки в Telegram: %v", err)
|
||
} else {
|
||
log.Println("✅ Алерт успешно отправлен в Telegram")
|
||
}
|
||
if err := emailSendHTML("Auspex: Срок действия сертификата", message); err != nil {
|
||
log.Printf("Ошибка отправки Email: %v", err)
|
||
} else {
|
||
log.Println("✅ Алерт успешно отправлен на Email")
|
||
}
|
||
if err := mattermostSend(message); err != nil {
|
||
log.Printf("❌ Ошибка отправки в Mattermost: %v", err)
|
||
} else {
|
||
log.Println("✅ Алерт успешно отправлен в Mattermost")
|
||
}
|
||
}
|