package main import ( "context" "encoding/json" "fmt" "log" "net/http" "net/url" "os" "strings" "time" vault "github.com/hashicorp/vault/api" "github.com/hashicorp/vault/api/auth/approle" "github.com/jackc/pgx/v5/pgxpool" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) func main() { ctx := context.Background() service := mustEnv("SERVICE_NAME") kvMount := mustEnv("VAULT_KV_MOUNT") client, err := vault.NewClient(vault.DefaultConfig()) check(err, "клиент Vault") auth, err := approle.NewAppRoleAuth(mustEnv("VAULT_ROLE_ID"), &approle.SecretID{FromEnv: "VAULT_SECRET_ID"}) check(err, "AppRole") login, err := client.Auth().Login(ctx, auth) check(err, "вход в Vault") log.Printf("vault: вошёл, политики %v, токен на %dс", login.Auth.Policies, login.Auth.LeaseDuration) kv := client.KVv2(kvMount) s3Secret, err := kv.Get(ctx, service+"/s3") check(err, "секрет s3") pgSecret, err := kv.Get(ctx, service+"/postgres") check(err, "секрет postgres") pgUser, pgPassword := str(pgSecret.Data, "username"), str(pgSecret.Data, "password") if os.Getenv("POSTGRES_CREDS") == "dynamic" { creds, err := client.Logical().ReadWithContext(ctx, mustEnv("VAULT_DB_MOUNT")+"/creds/"+service) check(err, "временные учётные данные") pgUser, pgPassword = str(creds.Data, "username"), str(creds.Data, "password") log.Printf("postgres: временная роль %s на %dс", pgUser, creds.LeaseDuration) } dsn := url.URL{ Scheme: "postgres", User: url.UserPassword(pgUser, pgPassword), Host: fmt.Sprintf("%s:%s", str(pgSecret.Data, "host"), str(pgSecret.Data, "port")), Path: str(pgSecret.Data, "database"), RawQuery: "sslmode=" + str(pgSecret.Data, "sslmode"), } db, err := pgxpool.New(ctx, dsn.String()) check(err, "пул postgres") defer db.Close() check(db.Ping(ctx), "подключение к postgres") log.Printf("postgres: подключился к %s как %s", dsn.Path, pgUser) endpoint, err := url.Parse(str(s3Secret.Data, "endpoint")) check(err, "endpoint s3") s3, err := minio.New(endpoint.Host, &minio.Options{ Creds: credentials.NewStaticV4(str(s3Secret.Data, "access_key"), str(s3Secret.Data, "secret_key"), ""), Secure: endpoint.Scheme == "https", }) check(err, "клиент s3") buckets := strings.Split(str(s3Secret.Data, "buckets"), ",") log.Printf("s3: бакеты %v на %s", buckets, endpoint.Host) http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) defer cancel() status := map[string]any{"service": service} var sessionUser, currentUser, database string err := db.QueryRow(ctx, "select session_user, current_user, current_database()"). Scan(&sessionUser, ¤tUser, &database) if err != nil { status["postgres"] = map[string]string{"error": err.Error()} } else { status["postgres"] = map[string]string{ "database": database, "session_user": sessionUser, "current_user": currentUser, } } objects := map[string]any{} for _, bucket := range buckets { count := 0 var listErr error for object := range s3.ListObjects(ctx, bucket, minio.ListObjectsOptions{Recursive: true}) { if object.Err != nil { listErr = object.Err break } count++ } if listErr != nil { objects[bucket] = map[string]string{"error": listErr.Error()} } else { objects[bucket] = map[string]int{"objects": count} } } status["s3"] = objects w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(status) }) log.Print("http: слушаю :8080") log.Fatal(http.ListenAndServe(":8080", nil)) } func mustEnv(name string) string { value := os.Getenv(name) if value == "" { log.Fatalf("не задана переменная окружения %s", name) } return value } func check(err error, what string) { if err != nil { log.Fatalf("%s: %v", what, err) } } func str(data map[string]any, key string) string { value, ok := data[key] if !ok { log.Fatalf("в секрете нет поля %s", key) } return fmt.Sprint(value) }