136 lines
4.2 KiB
Go
136 lines
4.2 KiB
Go
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")
|
|
database := mustEnv("DATABASE_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/"+database)
|
|
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+"-"+database)
|
|
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)
|
|
}
|