背景:arm64架构环境、项目需要、部分服务进程在用户密码过期后会出现进程异常;使用golang编写采集器(golang方便交叉编译)
功能:暴露采集端口、可直接通过prometheus接入采集、之后通过规则进行密码告警过期提示
代码:
package main
import (
"bufio"
"bytes"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
const (
namespace = "password_expiry"
neverExpiryValue = 99999
)
// UserExpiryCollector collects user password expiry metrics
type UserExpiryCollector struct {
expiryDays *prometheus.Desc
users []string
}
// NewUserExpiryCollector creates a new UserExpiryCollector
func NewUserExpiryCollector(users []string) *UserExpiryCollector {
return &UserExpiryCollector{
expiryDays: prometheus.NewDesc(
prometheus.BuildFQName(namespace, "", "days_to_expiry"),
"Number of days until the user's password expires",
[]string{"user"}, nil,
),
users: users,
}
}
// Describe sends the super-set of all possible descriptors of metrics collected by this Collector.
func (c *UserExpiryCollector) Describe(ch chan<- *prometheus.Desc) {
ch <- c.expiryDays
}
// Collect is called by the Prometheus registry when collecting metrics.
func (c *UserExpiryCollector) Collect(ch chan<- prometheus.Metric) {
for _, user := range c.users {
daysToExpiry, err := getDaysToExpiry(user)
if err != nil {
log.Printf("Error getting expiry for user %s: %v", user, err)
continue
}
ch <- prometheus.MustNewConstMetric(
c.expiryDays,
prometheus.GaugeValue,
float64(daysToExpiry),
user,
)
}
}
// getDaysToExpiry returns the number of days until the user's password expires.
func getDaysToExpiry(user string) (float64, error) {
cmd := exec.Command("chage", "-l", user)
cmd.Env = []string{
"LANG=en-US",
}
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
fmt.Println("Error executing command:", err)
return -2, nil
}
output := out.String()
filteredOutput := strings.Builder{}
for _, line := range strings.Split(output, "\n") {
if strings.Contains(line, "Password expires") {
filteredOutput.WriteString(strings.TrimSpace(strings.Split(line, ":")[1]))
}
}
if filteredOutput.String() == "never" {
return neverExpiryValue, nil
}
//loc, _ := time.LoadLocation("Asia/Shanghai")
//datenow, _ := time.ParseInLocation("2006-01-02 15:04:05", time.Now().Format("2006-01-02")+" 00:00:00", loc)
//dateexpire, _ := time.ParseInLocation("2006-01-02 15:04:05", filteredOutput.String()+" 00:00:00", loc)
datenow, _ := time.Parse("Jan 2, 2006 15:04:05 MST", time.Now().Format("Jan 2, 2006")+" 00:00:00 CST")
dateexpire, _ := time.Parse("Jan 2, 2006 15:04:05 MST", filteredOutput.String()+" 00:00:00 CST")
fmt.Println(dateexpire)
fmt.Println(datenow)
daysToExpiry := dateexpire.Sub(datenow).Hours() / 24
return daysToExpiry, nil
}
// getUsersWithBashShell returns a list of users with /bin/bash as their shell
func getUsersWithBashShell() ([]string, error) {
file, err := os.Open("/etc/passwd")
if err != nil {
return nil, fmt.Errorf("failed to open /etc/passwd: %w", err)
}
defer file.Close()
var users []string
scanner := bufio.NewScanner(file)
for scanner.Scan() {
line := scanner.Text()
parts := strings.Split(line, ":")
if len(parts) > 6 && parts[6] == "/bin/bash" {
users = append(users, parts[0])
}
}
if err := scanner.Err(); err != nil {
return nil, fmt.Errorf("error reading /etc/passwd: %w", err)
}
return users, nil
}
func main() {
users, err := getUsersWithBashShell()
if err != nil {
log.Fatalf("Error getting users with /bin/bash shell: %v", err)
}
log.Println("Starting password expiry exporter")
collector := NewUserExpiryCollector(users)
prometheus.MustRegister(collector)
http.Handle("/metrics", promhttp.Handler())
log.Fatal(http.ListenAndServe(":9135", nil))
}
###交叉编译的环境参考前面文章prometheusalert交叉编译