Compare commits

...

13 Commits

Author SHA1 Message Date
c0aa73c1bf
Add govulncheck to tools 2025-03-20 19:19:20 +01:00
078c5fc785
Add gomajor to tools 2025-03-20 19:16:37 +01:00
73c8486fc8
flake.lock: Update
Flake lock file updates:

• Updated input 'nixpkgs':
    'github:NixOS/nixpkgs/d69ab0d71b22fa1ce3dbeff666e6deb4917db049?narHash=sha256-k7VSqvv0r1r53nUI/IfPHCppkUAddeXn843YlAC5DR0%3D' (2025-03-05)
  → 'github:NixOS/nixpkgs/b6eaf97c6960d97350c584de1b6dcff03c9daf42?narHash=sha256-Txwa5uO%2BqpQXrNG4eumPSD%2BhHzzYi/CdaM80M9XRLCo%3D' (2025-03-18)
2025-03-20 19:12:46 +01:00
ed4d970723
Make metrics endpoint configurable 2025-03-20 18:57:31 +01:00
9b54cec29c
Update cli to v3 2025-03-20 18:53:23 +01:00
1230fb52d7
Update go-toml 2025-03-20 18:40:50 +01:00
cd0d5c469f
Update pgx 2025-03-20 18:39:18 +01:00
398160e6eb Merge pull request 'Remove unused code' (#12) from remove-old-crust into master
Reviewed-on: #12
2025-03-20 17:35:22 +00:00
3a5fa290e2
Remove unused code 2025-03-20 18:34:28 +01:00
e4327cd3a8
Fix table text alignment 2025-03-19 23:21:26 +01:00
cdbcf0e03b
Merge branch 'remove-zap' 2025-03-19 23:18:06 +01:00
46d9f4d64a
Replace zap with slog 2025-03-19 23:16:38 +01:00
49553fa965
Update go deps 2025-03-19 22:58:47 +01:00
20 changed files with 190 additions and 722 deletions

View File

@ -35,6 +35,9 @@ ThrottleSpeed = 10240.0
# Must be either "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE" # Must be either "DEBUG", "INFO", "WARN", "ERROR", "FATAL", "NONE"
# Default: "INFO" # Default: "INFO"
LogLevel = "INFO" LogLevel = "INFO"
# Enable metrics endpoint
# Default: false
MetricsEnable = false
# Enable access logging # Enable access logging
# Default: true # Default: true
AccessLogEnable = true AccessLogEnable = true

View File

@ -3,6 +3,7 @@ package main
import ( import (
"context" "context"
"fmt" "fmt"
"log/slog"
"net/http" "net/http"
"os" "os"
"os/signal" "os/signal"
@ -16,22 +17,14 @@ import (
"git.t-juice.club/torjus/apiary/web" "git.t-juice.club/torjus/apiary/web"
"github.com/coreos/go-systemd/daemon" "github.com/coreos/go-systemd/daemon"
sshlib "github.com/gliderlabs/ssh" sshlib "github.com/gliderlabs/ssh"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
) )
func main() { func main() {
app := &cli.App{ app := &cli.Command{
Name: "apiary", Name: "apiary",
Version: apiary.FullVersion(), Version: apiary.FullVersion(),
Authors: []*cli.Author{
{
Name: "Torjus Håkestad",
Email: "torjus@usit.uio.no",
},
},
Commands: []*cli.Command{ Commands: []*cli.Command{
{ {
Name: "serve", Name: "serve",
@ -41,13 +34,13 @@ func main() {
}, },
} }
if err := app.Run(os.Args); err != nil { if err := app.Run(context.Background(), os.Args); err != nil {
fmt.Printf("Error: %s\n", err) fmt.Printf("Error: %s\n", err)
os.Exit(1) os.Exit(1)
} }
} }
func ActionServe(c *cli.Context) error { func ActionServe(ctx context.Context, cmd *cli.Command) error {
cfg, err := getConfig() cfg, err := getConfig()
if err != nil { if err != nil {
return err return err
@ -55,17 +48,17 @@ func ActionServe(c *cli.Context) error {
// Setup logging // Setup logging
loggers := setupLoggers(cfg) loggers := setupLoggers(cfg)
loggers.rootLogger.Infow("Starting apiary.", "version", apiary.FullVersion()) loggers.rootLogger.Info("Starting apiary.", "version", apiary.FullVersion())
// Setup store // Setup store
var s store.LoginAttemptStore var s store.LoginAttemptStore
switch cfg.Store.Type { switch cfg.Store.Type {
case "MEMORY", "memory": case "MEMORY", "memory":
loggers.rootLogger.Infow("Initialized store.", "store_type", "memory") loggers.rootLogger.Info("Initialized store.", "store_type", "memory")
s = &store.MemoryStore{} s = &store.MemoryStore{}
case "POSTGRES", "postgres": case "POSTGRES", "postgres":
pgStartTime := time.Now() pgStartTime := time.Now()
loggers.rootLogger.Debugw("Initializing store.", "store_type", "postgres") loggers.rootLogger.Debug("Initializing store.", "store_type", "postgres")
pgStore, err := store.NewPostgresStore(cfg.Store.Postgres.DSN) pgStore, err := store.NewPostgresStore(cfg.Store.Postgres.DSN)
if err != nil { if err != nil {
return err return err
@ -73,36 +66,16 @@ func ActionServe(c *cli.Context) error {
if err := pgStore.InitDB(); err != nil { if err := pgStore.InitDB(); err != nil {
return err return err
} }
loggers.rootLogger.Infow("Initialized store.", "store_type", "postgres", "init_time", time.Since(pgStartTime)) loggers.rootLogger.Info("Initialized store.", "store_type", "postgres", "init_time", time.Since(pgStartTime))
if cfg.Store.EnableCache { if cfg.Store.EnableCache {
loggers.rootLogger.Debugw("Initializing store.", "store_type", "cache-postgres") loggers.rootLogger.Debug("Initializing store.", "store_type", "cache-postgres")
startTime := time.Now() startTime := time.Now()
cachingStore := store.NewCachingStore(pgStore) cachingStore := store.NewCachingStore(pgStore)
s = cachingStore s = cachingStore
loggers.rootLogger.Infow("Initialized store.", "store_type", "cache-postgres", "init_time", time.Since(startTime)) loggers.rootLogger.Info("Initialized store.", "store_type", "cache-postgres", "init_time", time.Since(startTime))
} else { } else {
s = pgStore s = pgStore
} }
case "bolt", "BOLT":
boltStartTime := time.Now()
loggers.rootLogger.Debugw("Initializing store.", "store_type", "bolt")
boltStore, err := store.NewBBoltStore(cfg.Store.Bolt.DBPath)
if err != nil {
return err
}
defer boltStore.Close()
loggers.rootLogger.Infow("Initialized store.", "store_type", "bolt", "init_time", time.Since(boltStartTime))
if cfg.Store.EnableCache {
loggers.rootLogger.Debugw("Initializing store.", "store_type", "cache-bolt")
startTime := time.Now()
cachingStore := store.NewCachingStore(boltStore)
s = cachingStore
loggers.rootLogger.Infow("Initialized store.", "store_type", "cache-bolt", "init_time", time.Since(startTime))
} else {
s = boltStore
}
default: default:
return fmt.Errorf("Invalid store configured") return fmt.Errorf("Invalid store configured")
} }
@ -111,7 +84,7 @@ func ActionServe(c *cli.Context) error {
interruptChan := make(chan os.Signal, 1) interruptChan := make(chan os.Signal, 1)
signal.Notify(interruptChan, os.Interrupt) signal.Notify(interruptChan, os.Interrupt)
rootCtx, rootCancel := context.WithCancel(c.Context) rootCtx, rootCancel := context.WithCancel(ctx)
defer rootCancel() defer rootCancel()
serversCtx, serversCancel := context.WithCancel(rootCtx) serversCtx, serversCancel := context.WithCancel(rootCtx)
defer serversCancel() defer serversCancel()
@ -153,18 +126,18 @@ func ActionServe(c *cli.Context) error {
// Start ssh server // Start ssh server
go func() { go func() {
loggers.rootLogger.Infow("Starting SSH server.", "addr", cfg.Honeypot.ListenAddr) loggers.rootLogger.Info("Starting SSH server.", "addr", cfg.Honeypot.ListenAddr)
if err := hs.ListenAndServe(); err != nil && err != sshlib.ErrServerClosed { if err := hs.ListenAndServe(); err != nil && err != sshlib.ErrServerClosed {
loggers.rootLogger.Warnw("SSH server returned error.", "error", err) loggers.rootLogger.Warn("SSH server returned error.", "error", err)
} }
loggers.rootLogger.Infow("SSH server stopped.") loggers.rootLogger.Info("SSH server stopped.")
}() }()
// Start web server // Start web server
go func() { go func() {
loggers.rootLogger.Infow("Starting web server.", "addr", cfg.Frontend.ListenAddr) loggers.rootLogger.Info("Starting web server.", "addr", cfg.Frontend.ListenAddr)
if err := web.StartServe(); err != nil && err != http.ErrServerClosed { if err := web.StartServe(); err != nil && err != http.ErrServerClosed {
loggers.rootLogger.Warnw("Web server returned error.", "error", err) loggers.rootLogger.Warn("Web server returned error.", "error", err)
} }
}() }()
@ -184,7 +157,7 @@ func ActionServe(c *cli.Context) error {
return return
} }
if err != nil { if err != nil {
loggers.rootLogger.Warnw("Unable to connect to NOTIFY_SOCKET.", "error", err) loggers.rootLogger.Warn("Unable to connect to NOTIFY_SOCKET.", "error", err)
return return
} }
loggers.rootLogger.Debug("Sent READY=1 to NOTIFY_SOCKET.") loggers.rootLogger.Debug("Sent READY=1 to NOTIFY_SOCKET.")
@ -192,11 +165,11 @@ func ActionServe(c *cli.Context) error {
// Setup timer // Setup timer
timeout, err := daemon.SdWatchdogEnabled(false) timeout, err := daemon.SdWatchdogEnabled(false)
if err != nil { if err != nil {
loggers.rootLogger.Warnw("Unable to get watchdog timeout.", "error", err) loggers.rootLogger.Warn("Unable to get watchdog timeout.", "error", err)
return return
} }
if timeout == 0 { if timeout == 0 {
loggers.rootLogger.Infow("Systemd watchdog not enabled.") loggers.rootLogger.Info("Systemd watchdog not enabled.")
return return
} }
@ -209,14 +182,14 @@ func ActionServe(c *cli.Context) error {
case <-ticker.C: case <-ticker.C:
if healthy == nil { if healthy == nil {
if _, err := daemon.SdNotify(false, daemon.SdNotifyWatchdog); err != nil { if _, err := daemon.SdNotify(false, daemon.SdNotifyWatchdog); err != nil {
loggers.rootLogger.Warnw("Error notifying watchdog.", "err", err) loggers.rootLogger.Warn("Error notifying watchdog.", "err", err)
} }
continue continue
} }
// TODO: If unhealthy, should we retry healthcheck immediately, otherwise service will most likely get killed by watchdog. // TODO: If unhealthy, should we retry healthcheck immediately, otherwise service will most likely get killed by watchdog.
loggers.rootLogger.Errorw("Store reported not healthy, might get killed by watchdog.", "err", healthy) loggers.rootLogger.Error("Store reported not healthy, might get killed by watchdog.", "err", healthy)
case <-notifyCtx.Done(): case <-notifyCtx.Done():
loggers.rootLogger.Debugw("Notify context cancelled.") loggers.rootLogger.Debug("Notify context cancelled.")
return return
} }
} }
@ -230,7 +203,7 @@ func ActionServe(c *cli.Context) error {
defer sshShutdownCancel() defer sshShutdownCancel()
loggers.rootLogger.Info("SSH server shutdown started.") loggers.rootLogger.Info("SSH server shutdown started.")
if err := hs.Shutdown(sshShutdownCtx); err != nil { if err := hs.Shutdown(sshShutdownCtx); err != nil {
loggers.rootLogger.Infow("Error shutting down SSH server.", "error", err) loggers.rootLogger.Info("Error shutting down SSH server.", "error", err)
} }
loggers.rootLogger.Info("SSH server shutdown complete.") loggers.rootLogger.Info("SSH server shutdown complete.")
@ -240,7 +213,7 @@ func ActionServe(c *cli.Context) error {
loggers.rootLogger.Info("Web server shutdown started.") loggers.rootLogger.Info("Web server shutdown started.")
if err := web.Shutdown(webShutdownCtx); err != nil { if err := web.Shutdown(webShutdownCtx); err != nil {
loggers.rootLogger.Infow("Error shutting down web server.", "error", err) loggers.rootLogger.Info("Error shutting down web server.", "error", err)
} }
loggers.rootLogger.Info("Web server shutdown complete.") loggers.rootLogger.Info("Web server shutdown complete.")
rootCancel() rootCancel()
@ -252,49 +225,36 @@ func ActionServe(c *cli.Context) error {
} }
type loggerCollection struct { type loggerCollection struct {
rootLogger *zap.SugaredLogger rootLogger *slog.Logger
honeypotLogger *zap.SugaredLogger honeypotLogger *slog.Logger
webAccessLogger *zap.SugaredLogger webAccessLogger *slog.Logger
webServerLogger *zap.SugaredLogger webServerLogger *slog.Logger
} }
func setupLoggers(cfg config.Config) *loggerCollection { func setupLoggers(cfg config.Config) *loggerCollection {
logEncoderCfg := zap.NewProductionEncoderConfig() opts := &slog.HandlerOptions{}
logEncoderCfg.EncodeCaller = func(caller zapcore.EntryCaller, enc zapcore.PrimitiveArrayEncoder) {}
var level zap.AtomicLevel
switch strings.ToUpper(cfg.Honeypot.LogLevel) { switch strings.ToUpper(cfg.Honeypot.LogLevel) {
case "INFO": case "INFO":
level = zap.NewAtomicLevelAt(zap.InfoLevel) opts.Level = slog.LevelInfo
case "DEBUG": case "DEBUG":
level = zap.NewAtomicLevelAt(zap.DebugLevel) opts.Level = slog.LevelDebug
opts.AddSource = true
case "WARN", "WARNING": case "WARN", "WARNING":
level = zap.NewAtomicLevelAt(zap.WarnLevel) opts.Level = slog.LevelWarn
case "ERR", "ERROR": case "ERR", "ERROR":
level = zap.NewAtomicLevelAt(zap.WarnLevel) opts.Level = slog.LevelError
default: default:
level = zap.NewAtomicLevelAt(zap.InfoLevel) opts.Level = slog.LevelInfo
}
logEncoderCfg.EncodeLevel = zapcore.CapitalColorLevelEncoder
logEncoderCfg.EncodeTime = zapcore.ISO8601TimeEncoder
logEncoderCfg.EncodeDuration = zapcore.StringDurationEncoder
rootLoggerCfg := &zap.Config{
Level: level,
OutputPaths: []string{"stdout"},
ErrorOutputPaths: []string{"stderr"},
Encoding: "console",
EncoderConfig: logEncoderCfg,
}
rootLogger, err := rootLoggerCfg.Build()
if err != nil {
panic(err)
} }
handler := slog.NewTextHandler(os.Stdout, opts)
rootLogger := slog.New(handler)
return &loggerCollection{ return &loggerCollection{
rootLogger: rootLogger.Named("APP").Sugar(), rootLogger: rootLogger.With("module", "application"),
honeypotLogger: rootLogger.Named("HON").Sugar(), honeypotLogger: rootLogger.With("module", "honeypot"),
webAccessLogger: rootLogger.Named("ACC").Sugar(), webAccessLogger: rootLogger.With("module", "web-access-log"),
webServerLogger: rootLogger.Named("WEB").Sugar(), webServerLogger: rootLogger.With("module", "web-server"),
} }
} }

View File

@ -6,7 +6,7 @@ import (
"os" "os"
"strings" "strings"
"github.com/pelletier/go-toml" "github.com/pelletier/go-toml/v2"
) )
type Config struct { type Config struct {
@ -18,17 +18,12 @@ type StoreConfig struct {
Type string `toml:"Type"` Type string `toml:"Type"`
EnableCache bool `toml:"EnableCache"` EnableCache bool `toml:"EnableCache"`
Postgres PostgresStoreConfig `toml:"Postgres"` Postgres PostgresStoreConfig `toml:"Postgres"`
Bolt BoltStoreConfig `toml:"Bolt"`
} }
type PostgresStoreConfig struct { type PostgresStoreConfig struct {
DSN string `toml:"DSN"` DSN string `toml:"DSN"`
} }
type BoltStoreConfig struct {
DBPath string `toml:"DBPath"`
}
type HoneypotConfig struct { type HoneypotConfig struct {
ListenAddr string `toml:"ListenAddr"` ListenAddr string `toml:"ListenAddr"`
LogLevel string `toml:"LogLevel"` LogLevel string `toml:"LogLevel"`
@ -39,6 +34,7 @@ type HoneypotConfig struct {
type FrontendConfig struct { type FrontendConfig struct {
ListenAddr string `toml:"ListenAddr"` ListenAddr string `toml:"ListenAddr"`
LogLevel string `toml:"LogLevel"` LogLevel string `toml:"LogLevel"`
MetricsEnable bool `toml:"MetricsEnable"`
AccessLogEnable bool `toml:"AccessLogEnable"` AccessLogEnable bool `toml:"AccessLogEnable"`
AccessLogIgnoreMetrics bool `toml:"AccessLogIgnoreMetrics"` AccessLogIgnoreMetrics bool `toml:"AccessLogIgnoreMetrics"`
Autocert FrontendAutocertConfig `toml:"Autocert"` Autocert FrontendAutocertConfig `toml:"Autocert"`

6
flake.lock generated
View File

@ -2,11 +2,11 @@
"nodes": { "nodes": {
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1741173522, "lastModified": 1742288794,
"narHash": "sha256-k7VSqvv0r1r53nUI/IfPHCppkUAddeXn843YlAC5DR0=", "narHash": "sha256-Txwa5uO+qpQXrNG4eumPSD+hHzzYi/CdaM80M9XRLCo=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "d69ab0d71b22fa1ce3dbeff666e6deb4917db049", "rev": "b6eaf97c6960d97350c584de1b6dcff03c9daf42",
"type": "github" "type": "github"
}, },
"original": { "original": {

View File

@ -75,7 +75,7 @@
mkdir -p web/frontend/dist mkdir -p web/frontend/dist
cp -r ${frontend}/* web/frontend/dist cp -r ${frontend}/* web/frontend/dist
''; '';
vendorHash = "sha256-griWN9fQ0X2ClPDOzVXV80MpdcEFZfe/WaYm7L7fAc8="; vendorHash = "sha256-t/DNKefG+UxZEfNYNUmOPSw6aqgNuz6v7qxow5JRXsE=";
ldflags = [ "-X git.t-juice.club/torjus/apiary.Build=${rev}" ]; ldflags = [ "-X git.t-juice.club/torjus/apiary.Build=${rev}" ];
tags = [ tags = [
"embed" "embed"

View File

@ -188,6 +188,7 @@ input[type=text] {
.live_table th, .live_table th,
.live_table td { .live_table td {
padding: 12px 15px; padding: 12px 15px;
text-align: left;
} }
.live_table tbody tr { .live_table tbody tr {
border-bottom: 1px solid var(--table-row-odd); border-bottom: 1px solid var(--table-row-odd);

69
go.mod
View File

@ -1,46 +1,49 @@
module git.t-juice.club/torjus/apiary module git.t-juice.club/torjus/apiary
go 1.18 go 1.24.0
toolchain go1.24.1
require ( require (
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/fujiwara/shapeio v1.0.0 github.com/fujiwara/shapeio v1.0.0
github.com/gliderlabs/ssh v0.3.5 github.com/gliderlabs/ssh v0.3.8
github.com/go-chi/chi/v5 v5.0.10 github.com/go-chi/chi/v5 v5.2.1
github.com/google/uuid v1.3.1 github.com/google/uuid v1.6.0
github.com/jackc/pgx/v4 v4.18.1 github.com/jackc/pgx/v5 v5.7.2
github.com/oschwald/maxminddb-golang v1.12.0 github.com/oschwald/maxminddb-golang v1.13.1
github.com/pelletier/go-toml v1.9.5 github.com/pelletier/go-toml/v2 v2.2.3
github.com/prometheus/client_golang v1.17.0 github.com/prometheus/client_golang v1.21.1
github.com/urfave/cli/v2 v2.25.7 github.com/urfave/cli/v3 v3.0.0-beta1
go.etcd.io/bbolt v1.3.7 golang.org/x/crypto v0.36.0
go.uber.org/zap v1.26.0
golang.org/x/crypto v0.14.0
) )
require ( require (
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be // indirect
github.com/beorn7/perks v1.0.1 // indirect github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.3 // indirect github.com/icholy/gomajor v0.14.0 // indirect
github.com/golang/protobuf v1.5.3 // indirect
github.com/jackc/chunkreader/v2 v2.0.1 // indirect
github.com/jackc/pgconn v1.14.1 // indirect
github.com/jackc/pgio v1.0.0 // indirect
github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect
github.com/jackc/pgproto3/v2 v2.3.2 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect github.com/jackc/puddle/v2 v2.2.2 // indirect
github.com/jackc/pgtype v1.14.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/prometheus/client_model v0.5.0 // indirect github.com/prometheus/client_model v0.6.1 // indirect
github.com/prometheus/common v0.44.0 // indirect github.com/prometheus/common v0.63.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect github.com/prometheus/procfs v0.16.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect golang.org/x/mod v0.22.0 // indirect
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/net v0.37.0 // indirect
go.uber.org/multierr v1.11.0 // indirect golang.org/x/sync v0.12.0 // indirect
golang.org/x/net v0.17.0 // indirect golang.org/x/sys v0.31.0 // indirect
golang.org/x/sys v0.13.0 // indirect golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 // indirect
golang.org/x/text v0.13.0 // indirect golang.org/x/text v0.23.0 // indirect
golang.org/x/time v0.3.0 // indirect golang.org/x/time v0.11.0 // indirect
google.golang.org/protobuf v1.31.0 // indirect golang.org/x/tools v0.29.0 // indirect
golang.org/x/vuln v1.1.4 // indirect
google.golang.org/protobuf v1.36.5 // indirect
)
tool (
github.com/icholy/gomajor
golang.org/x/vuln/cmd/govulncheck
) )

317
go.sum
View File

@ -1,21 +1,11 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc=
github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4= github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I=
github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ=
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf h1:iW4rZ826su+pqaw19uhpSCzhj44qo35pNgKFGqzDKkU=
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/cpuguy83/go-md2man/v2 v2.0.3 h1:qMCsGGgs+MAzDFyp9LpAe1Lqy/fY/qCovCm0qnXZOBM=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -23,254 +13,81 @@ github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/fujiwara/shapeio v1.0.0 h1:xG5D9oNqCSUUbryZ/jQV3cqe1v2suEjwPIcEg1gKM8M= github.com/fujiwara/shapeio v1.0.0 h1:xG5D9oNqCSUUbryZ/jQV3cqe1v2suEjwPIcEg1gKM8M=
github.com/fujiwara/shapeio v1.0.0/go.mod h1:LmEmu6L/8jetyj1oewewFb7bZCNRwE7wLCUNzDLaLVA= github.com/fujiwara/shapeio v1.0.0/go.mod h1:LmEmu6L/8jetyj1oewewFb7bZCNRwE7wLCUNzDLaLVA=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
github.com/go-chi/chi/v5 v5.0.10 h1:rLz5avzKpjqxrYwXNfmjkrYYXOyLJd37pz53UFHC6vk= github.com/go-chi/chi/v5 v5.2.1 h1:KOIHODQj58PmL80G2Eak4WdvUzjSJSm0vG72crDCqb8=
github.com/go-chi/chi/v5 v5.0.10/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= github.com/go-chi/chi/v5 v5.2.1/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786 h1:rcv+Ippz6RAtvaGgKxc+8FQIpxHgsF+HBzPyYL2cyVU=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/google/go-cmdtest v0.4.1-0.20220921163831-55ab3332a786/go.mod h1:apVn/GCasLZUVpAJ6oWAuyP7Ne7CEsQbTnc0plM3m+o=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= github.com/google/renameio v0.1.0 h1:GOZbcHa3HfsPKPlmyPyN2KEohoMXOhdMbHrvbpl2QaA=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg=
github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.3.1 h1:KjJaJ9iWZ3jOFZIf1Lqf4laDRCasjl0BCmnEGxkdLb4= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.3.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= github.com/icholy/gomajor v0.14.0 h1:qr0eGSMyLcZa7lmSuiplH3kr6j4oH6ET8nkdZqpDcks=
github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= github.com/icholy/gomajor v0.14.0/go.mod h1:oJuk5dppdmnvUQRKsUNelWZ7KmYpvYKVupt/OQ0y9UI=
github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8=
github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk=
github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA=
github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE=
github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s=
github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o=
github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY=
github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI=
github.com/jackc/pgconn v1.14.0/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
github.com/jackc/pgconn v1.14.1 h1:smbxIaZA08n6YuxEX1sDyjV/qkbtUtkH20qLkR9MUR4=
github.com/jackc/pgconn v1.14.1/go.mod h1:9mBNlny0UvkgJdCDvdVHYSjI+8tD2rnKK69Wz8ti++E=
github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE=
github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8=
github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE=
github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc=
github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak=
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI=
github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ=
github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
github.com/jackc/pgproto3/v2 v2.3.2 h1:7eY55bdBeCz1F2fTzSz69QC+pG46jYq9/jtSPiJ5nn0= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/jackc/pgproto3/v2 v2.3.2/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE=
github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8=
github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
github.com/jackc/pgtype v1.14.0 h1:y+xUdabmyMkJLyApYuPj38mW+aAIqCe5uuBB51rH3Vw=
github.com/jackc/pgtype v1.14.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4=
github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y=
github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM=
github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc=
github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs=
github.com/jackc/pgx/v4 v4.18.1 h1:YP7G1KABtKpB5IHrO9vYwSrCOhs7p3uqhvhhQBptya0=
github.com/jackc/pgx/v4 v4.18.1/go.mod h1:FydWkUyadDmdNH/mHnGob881GawxeEm7TcMCzkb+qQE=
github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/lib/pq v1.10.2 h1:AqzbZs4ZoCBp+GtejcpCpcxM3zlSMx29dXbUSeVtJb8=
github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ=
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo=
github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v1.17.0 h1:rl2sfwZMtSthVU752MqfjQozy7blglC+1SOtjMAMh+Q= github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
github.com/prometheus/client_golang v1.17.0/go.mod h1:VeL+gMmOAxkS2IqfCq0ZmHSL+LjWfWDUmp1mBz9JgUY= github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw= github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
github.com/prometheus/common v0.44.0 h1:+5BrQJwiBB9xsMygAB3TNvpQKOwlkc25LbISbrdOOfY= github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
github.com/prometheus/common v0.44.0/go.mod h1:ofAIvZbQ1e/nugmZGz4/qCb9Ap1VoSTIO7x0VV9VvuY= github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo= github.com/prometheus/procfs v0.16.0 h1:xh6oHhKwnOJKMYiYBDWmkHqQPyiY40sny36Cmx2bbsM=
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo= github.com/prometheus/procfs v0.16.0/go.mod h1:8veyXUu3nGP7oaCxhX6yeaM5u4stL2FeMXnCqhDthZg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ=
github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU=
github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4=
github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ=
github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o=
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/urfave/cli/v3 v3.0.0-beta1 h1:6DTaaUarcM0wX7qj5Hcvs+5Dm3dyUTBbEwIWAjcw9Zg=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= github.com/urfave/cli/v3 v3.0.0-beta1/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7 h1:FemxDzfMUcK2f3YY4H+05K9CDzbSVr2+q/JKN45pey0=
go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= golang.org/x/telemetry v0.0.0-20240522233618-39ace7a40ae7/go.mod h1:pRgIJT+bRLFKnoM1ldnzKoxTIn14Yxz928LQRYYgIN0=
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk= golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA=
go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM=
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220826181053-bd7e27e6170d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58=
golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.0.0-20220826154423-83b083e8dc8b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220825204002-c680a09ffe64/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.0.0-20220722155259-a9ba230a4035/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20210220033141-f8bda1e9f3ba/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.29.0 h1:Xx0h3TtM9rzQpQuR4dKLrdglAmCEN5Oi+P74JdhdzXE=
golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.29.0/go.mod h1:KMQVMRsVxU6nHCFXrBPhDB8XncLNLM0lIy/F14RP588=
golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/vuln v1.1.4 h1:Ju8QsuyhX3Hk8ma3CesTbO8vfJD9EvUBgHvkxHBzj0I=
golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/vuln v1.1.4/go.mod h1:F+45wmU18ym/ca5PLTPLsSzr2KppzswxPP603ldA67s=
golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8=
google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI=
gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=

View File

@ -1,11 +0,0 @@
package ssh
type ActionType int
const (
ActionTypeLogPassword ActionType = iota
ActionTypeLogPasswordSlow
ActionTypeLogCommandAndExit
ActionTypeSendGarbage
)
const ActionTypeDefault ActionType = ActionTypeLogPassword

View File

@ -13,7 +13,7 @@ var mmdb []byte
func (s *HoneypotServer) LookupCountry(ip net.IP) string { func (s *HoneypotServer) LookupCountry(ip net.IP) string {
db, err := maxminddb.FromBytes(mmdb) db, err := maxminddb.FromBytes(mmdb)
if err != nil { if err != nil {
s.Logger.Warnw("Error opening geoip database", "error", err) s.Logger.Warn("Error opening geoip database", "error", err)
return "??" return "??"
} }
@ -25,7 +25,7 @@ func (s *HoneypotServer) LookupCountry(ip net.IP) string {
err = db.Lookup(ip, &record) err = db.Lookup(ip, &record)
if err != nil { if err != nil {
s.Logger.Warnw("Error doing geoip lookup", "error", err) s.Logger.Warn("Error doing geoip lookup", "error", err)
return "??" return "??"
} }
if record.Country.ISOCode == "None" { if record.Country.ISOCode == "None" {

View File

@ -3,6 +3,7 @@ package ssh
import ( import (
"context" "context"
"io" "io"
"log/slog"
"net" "net"
"os" "os"
"time" "time"
@ -16,11 +17,10 @@ import (
"git.t-juice.club/torjus/apiary/models" "git.t-juice.club/torjus/apiary/models"
sshlib "github.com/gliderlabs/ssh" sshlib "github.com/gliderlabs/ssh"
"github.com/google/uuid" "github.com/google/uuid"
"go.uber.org/zap"
) )
type HoneypotServer struct { type HoneypotServer struct {
Logger *zap.SugaredLogger Logger *slog.Logger
sshServer *sshlib.Server sshServer *sshlib.Server
@ -33,7 +33,7 @@ type HoneypotServer struct {
func NewHoneypotServer(cfg config.HoneypotConfig, store store.LoginAttemptStore) (*HoneypotServer, error) { func NewHoneypotServer(cfg config.HoneypotConfig, store store.LoginAttemptStore) (*HoneypotServer, error) {
var hs HoneypotServer var hs HoneypotServer
hs.attemptStore = store hs.attemptStore = store
hs.Logger = zap.NewNop().Sugar() hs.Logger = slog.New(slog.NewTextHandler(io.Discard, nil))
hs.sshServer = &sshlib.Server{ hs.sshServer = &sshlib.Server{
Addr: cfg.ListenAddr, Addr: cfg.ListenAddr,
@ -94,19 +94,19 @@ func (hs *HoneypotServer) passwordHandler(ctx sshlib.Context, password string) b
} }
country := hs.LookupCountry(la.RemoteIP) country := hs.LookupCountry(la.RemoteIP)
if utf8.RuneCountInString(country) > 2 { if utf8.RuneCountInString(country) > 2 {
hs.Logger.Warnw("Too many characters in country", "country", country, "runecount", utf8.RuneCountInString(country)) hs.Logger.Warn("Too many characters in country", "country", country, "runecount", utf8.RuneCountInString(country))
country = "??" country = "??"
} }
la.Country = country la.Country = country
hs.Logger.Infow("Login attempt", hs.Logger.Info("Login attempt",
"remote_ip", la.RemoteIP.String(), "remote_ip", la.RemoteIP.String(),
"username", la.Username, "username", la.Username,
"password", la.Password, "password", la.Password,
"country", la.Country) "country", la.Country)
if err := hs.attemptStore.AddAttempt(&la); err != nil { if err := hs.attemptStore.AddAttempt(&la); err != nil {
hs.Logger.Warnw("Error adding attempt to store", "error", err) hs.Logger.Warn("Error adding attempt to store", "error", err)
} }
for _, cFunc := range hs.attemptsCallbacks { for _, cFunc := range hs.attemptsCallbacks {
@ -117,7 +117,7 @@ func (hs *HoneypotServer) passwordHandler(ctx sshlib.Context, password string) b
} }
func (s *HoneypotServer) connCallback(ctx sshlib.Context, conn net.Conn) net.Conn { func (s *HoneypotServer) connCallback(ctx sshlib.Context, conn net.Conn) net.Conn {
s.Logger.Debugw("Connection received.", "remote_addr", conn.RemoteAddr()) s.Logger.Debug("Connection received.", "remote_addr", conn.RemoteAddr())
throttledConn := newThrottledConn(conn) throttledConn := newThrottledConn(conn)
throttledConn.SetSpeed(s.throttleSpeed) throttledConn.SetSpeed(s.throttleSpeed)

View File

@ -1,235 +0,0 @@
package store
import (
"encoding/binary"
"encoding/json"
"fmt"
"sort"
"strings"
"git.t-juice.club/torjus/apiary/models"
bolt "go.etcd.io/bbolt"
)
// var _ LoginAttemptStore = &BBoltStore{}
var bktKeyLogins []byte = []byte("logins")
type BBoltStore struct {
db *bolt.DB
}
func NewBBoltStore(path string) (*BBoltStore, error) {
db, err := bolt.Open(path, 0o666, nil)
if err != nil {
return nil, fmt.Errorf("error opening database: %w", err)
}
var store BBoltStore
store.db = db
err = db.Update(func(tx *bolt.Tx) error {
_, err := tx.CreateBucketIfNotExists(bktKeyLogins)
return err
})
if err != nil {
return nil, fmt.Errorf("error creating database bucket: %w", err)
}
return &store, nil
}
func (s *BBoltStore) Close() error {
return s.db.Close()
}
func (s *BBoltStore) AddAttempt(l *models.LoginAttempt) error {
data, err := json.Marshal(l)
if err != nil {
return err
}
return s.db.Update(func(tx *bolt.Tx) error {
bkt := tx.Bucket(bktKeyLogins)
seq, err := bkt.NextSequence()
if err != nil {
return err
}
key := itob(seq)
return bkt.Put(key, data)
})
}
func (s *BBoltStore) All() (<-chan models.LoginAttempt, error) {
ch := make(chan models.LoginAttempt)
go func() {
_ = s.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(bktKeyLogins)
c := bkt.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
var l models.LoginAttempt
if err := json.Unmarshal(v, &l); err != nil {
close(ch)
panic(err)
}
ch <- l
}
close(ch)
return nil
})
}()
return ch, nil
}
func (s *BBoltStore) Stats(statType LoginStats, limit int) ([]StatsResult, error) {
if statType == LoginStatsTotals {
return s.statTotals()
}
counts := make(map[string]int)
err := s.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(bktKeyLogins)
c := bkt.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
var l models.LoginAttempt
if err := json.Unmarshal(v, &l); err != nil {
return err
}
switch statType {
case LoginStatsPasswords:
counts[l.Password]++
case LoginStatsCountry:
counts[l.Country]++
case LoginStatsIP:
counts[l.RemoteIP.String()]++
case LoginStatsUsername:
counts[l.Username]++
default:
return fmt.Errorf("invalid stat type")
}
}
return nil
})
if err != nil {
return nil, fmt.Errorf("error generating stats: %w", err)
}
if limit < 1 {
return toResults(counts), nil
}
if limit >= len(counts) {
return toResults(counts), nil
}
var si StatItems
for key := range counts {
si = append(si, StatItem{Key: key, Count: counts[key]})
}
sort.Sort(si)
output := make(map[string]int)
for i := len(si) - 1; i > len(si)-limit-1; i-- {
output[si[i].Key] = si[i].Count
}
return toResults(output), nil
}
func (s *BBoltStore) statTotals() ([]StatsResult, error) {
passwords := make(map[string]int)
usernames := make(map[string]int)
ips := make(map[string]int)
countries := make(map[string]int)
var count int
err := s.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(bktKeyLogins)
c := bkt.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
var l models.LoginAttempt
if err := json.Unmarshal(v, &l); err != nil {
return err
}
passwords[l.Password] += 1
usernames[l.Username] += 1
ips[l.RemoteIP.String()] += 1
countries[l.Country] += 1
count++
}
return nil
})
if err != nil {
return nil, err
}
stats := []StatsResult{
{Name: "UniquePasswords", Count: len(passwords)},
{Name: "UniqueUsernames", Count: len(usernames)},
{Name: "UniqueIPs", Count: len(ips)},
{Name: "UniqueCountries", Count: len(countries)},
{Name: "TotalLoginAttempts", Count: count},
}
return stats, nil
}
func (s *BBoltStore) Query(query AttemptQuery) ([]models.LoginAttempt, error) {
var results []models.LoginAttempt
err := s.db.View(func(tx *bolt.Tx) error {
bkt := tx.Bucket(bktKeyLogins)
c := bkt.Cursor()
for k, v := c.First(); k != nil; k, v = c.Next() {
var l models.LoginAttempt
if err := json.Unmarshal(v, &l); err != nil {
return err
}
switch query.QueryType {
case AttemptQueryTypeIP:
if l.RemoteIP.String() == query.Query {
results = append(results, l)
}
case AttemptQueryTypePassword:
if strings.Contains(l.Password, query.Query) {
results = append(results, l)
}
case AttemptQueryTypeUsername:
if strings.Contains(l.Username, query.Query) {
results = append(results, l)
}
}
}
return nil
})
if err != nil {
return nil, err
}
return results, nil
}
func (s *BBoltStore) IsHealthy() error {
// TODO: Do actual healthcheck
return nil
}
func itob(v uint64) []byte {
b := make([]byte, 8)
binary.BigEndian.PutUint64(b, v)
return b
}

View File

@ -1,38 +0,0 @@
package store_test
import (
"os"
"testing"
"git.t-juice.club/torjus/apiary/honeypot/ssh/store"
)
func TestBBoltStore(t *testing.T) {
dir := t.TempDir()
f, err := os.CreateTemp(dir, "apiary-test-bbolt")
if err != nil {
t.Fatal(err)
}
fname := f.Name()
f.Close()
s, err := store.NewBBoltStore(fname)
if err != nil {
t.Fatal(err)
}
testLoginAttemptStore(s, t)
}
func FuzzBBoltStore(f *testing.F) {
dir := f.TempDir()
file, err := os.CreateTemp(dir, "apiary-test-bbolt")
if err != nil {
f.Fatal(err)
}
fname := file.Name()
file.Close()
s, err := store.NewBBoltStore(fname)
if err != nil {
f.Fatal(err)
}
fuzzLoginAttemptStore(s, f)
}

View File

@ -6,7 +6,7 @@ import (
"net" "net"
"git.t-juice.club/torjus/apiary/models" "git.t-juice.club/torjus/apiary/models"
_ "github.com/jackc/pgx/v4/stdlib" _ "github.com/jackc/pgx/v5/stdlib"
) )
var _ LoginAttemptStore = &PostgresStore{} var _ LoginAttemptStore = &PostgresStore{}

View File

@ -7,6 +7,7 @@ import (
"testing" "testing"
"git.t-juice.club/torjus/apiary/honeypot/ssh/store" "git.t-juice.club/torjus/apiary/honeypot/ssh/store"
_ "github.com/jackc/pgx/v5/stdlib"
) )
func TestPostgresStore(t *testing.T) { func TestPostgresStore(t *testing.T) {

View File

@ -1,32 +0,0 @@
package models
import (
"fmt"
"golang.org/x/crypto/bcrypt"
)
var ErrInvalidPassword = fmt.Errorf("invalid password")
type User struct {
Username string `json:"username"`
PasswordHash []byte `json:"passwordHash"`
}
func (u *User) SetPassword(password string) error {
newHash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return fmt.Errorf("error hashing new password: %w", err)
}
u.PasswordHash = newHash
return nil
}
func (u *User) VerifyPassword(password string) error {
err := bcrypt.CompareHashAndPassword([]byte(u.PasswordHash), []byte(password))
if err != nil {
return ErrInvalidPassword
}
return nil
}

View File

@ -6,7 +6,7 @@ import (
) )
var ( var (
Version = "v0.2.1" Version = "v0.2.4"
Build string Build string
) )

View File

@ -40,7 +40,7 @@ func (s *Server) serveIndex(frontendDir string) http.HandlerFunc {
defer f.Close() defer f.Close()
if _, err := io.Copy(w, f); err != nil { if _, err := io.Copy(w, f); err != nil {
s.ServerLogger.Warnw("Error writing frontend to client.", "err", err) s.ServerLogger.Warn("Error writing frontend to client.", "err", err)
} }
} }
return fn return fn

View File

@ -21,7 +21,7 @@ func (s *Server) LoggingMiddleware(next http.Handler) http.Handler {
if s.cfg.AccessLogIgnoreMetrics && r.URL.Path == "/metrics" && ww.Status() == http.StatusOK { if s.cfg.AccessLogIgnoreMetrics && r.URL.Path == "/metrics" && ww.Status() == http.StatusOK {
return return
} }
s.AccessLogger.Infow(r.Method, s.AccessLogger.Info(r.Method,
"path", r.URL.Path, "path", r.URL.Path,
"status", ww.Status(), "status", ww.Status(),
"written", ww.BytesWritten(), "written", ww.BytesWritten(),

View File

@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"log/slog"
"net/http" "net/http"
"strconv" "strconv"
"sync" "sync"
@ -20,7 +21,6 @@ import (
"github.com/go-chi/chi/v5/middleware" "github.com/go-chi/chi/v5/middleware"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/prometheus/client_golang/prometheus/promhttp" "github.com/prometheus/client_golang/prometheus/promhttp"
"go.uber.org/zap"
"golang.org/x/crypto/acme/autocert" "golang.org/x/crypto/acme/autocert"
) )
@ -35,8 +35,8 @@ type Server struct {
honeypotServer *ssh.HoneypotServer honeypotServer *ssh.HoneypotServer
store store.LoginAttemptStore store store.LoginAttemptStore
ServerLogger *zap.SugaredLogger ServerLogger *slog.Logger
AccessLogger *zap.SugaredLogger AccessLogger *slog.Logger
attemptListenersLock sync.RWMutex attemptListenersLock sync.RWMutex
attemptListeners map[string]chan models.LoginAttempt attemptListeners map[string]chan models.LoginAttempt
@ -44,9 +44,10 @@ type Server struct {
} }
func NewServer(cfg config.FrontendConfig, hs *ssh.HoneypotServer, store store.LoginAttemptStore) *Server { func NewServer(cfg config.FrontendConfig, hs *ssh.HoneypotServer, store store.LoginAttemptStore) *Server {
discardLogger := slog.New(slog.NewTextHandler(io.Discard, nil))
s := &Server{ s := &Server{
ServerLogger: zap.NewNop().Sugar(), ServerLogger: discardLogger,
AccessLogger: zap.NewNop().Sugar(), AccessLogger: discardLogger,
store: store, store: store,
cfg: cfg, cfg: cfg,
} }
@ -85,10 +86,12 @@ func NewServer(cfg config.FrontendConfig, hs *ssh.HoneypotServer, store store.Lo
r.Use(middleware.RealIP) r.Use(middleware.RealIP)
r.Use(middleware.RequestID) r.Use(middleware.RequestID)
r.Use(s.LoggingMiddleware) r.Use(s.LoggingMiddleware)
r.Use(NewMetricsMiddleware())
r.Use(middleware.SetHeader("Server", fmt.Sprintf("apiary/%s", apiary.FullVersion()))) r.Use(middleware.SetHeader("Server", fmt.Sprintf("apiary/%s", apiary.FullVersion())))
r.Handle("/metrics", promhttp.Handler()) if cfg.MetricsEnable {
r.Use(NewMetricsMiddleware())
r.Handle("/metrics", promhttp.Handler())
}
r.Route("/", func(r chi.Router) { r.Route("/", func(r chi.Router) {
r.Get("/*", s.IndexHandler("web/vue-frontend/dist")) r.Get("/*", s.IndexHandler("web/vue-frontend/dist"))
r.Get("/stream", s.HandlerAttemptStream) r.Get("/stream", s.HandlerAttemptStream)
@ -125,7 +128,7 @@ func (s *Server) StartServe() error {
s.ServerLogger.Debug("Starting HTTP redirect server") s.ServerLogger.Debug("Starting HTTP redirect server")
go func() { go func() {
if err := s.httpRedirectServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { if err := s.httpRedirectServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
s.ServerLogger.Warnw("HTTP redirect server returned error", "error", err) s.ServerLogger.Warn("HTTP redirect server returned error", "error", err)
} }
}() }()
} }
@ -174,7 +177,7 @@ func (s *Server) HandlerAttemptStream(w http.ResponseWriter, r *http.Request) {
} }
_, err = io.WriteString(w, fmt.Sprintf("data: %s\n\n", string(data))) _, err = io.WriteString(w, fmt.Sprintf("data: %s\n\n", string(data)))
if err != nil { if err != nil {
s.ServerLogger.Warnw("Error writing event", "error", err) s.ServerLogger.Warn("Error writing event", "error", err)
} }
flusher.Flush() flusher.Flush()
ticker.Reset(streamKeepAliveDuration) ticker.Reset(streamKeepAliveDuration)
@ -184,7 +187,7 @@ func (s *Server) HandlerAttemptStream(w http.ResponseWriter, r *http.Request) {
return return
case <-ticker.C: case <-ticker.C:
if _, err := io.WriteString(w, ": keep-alive\n\n"); err != nil { if _, err := io.WriteString(w, ": keep-alive\n\n"); err != nil {
s.ServerLogger.Warnw("Error writing event", "error", err) s.ServerLogger.Warn("Error writing event", "error", err)
} }
flusher.Flush() flusher.Flush()
} }
@ -207,14 +210,14 @@ func (s *Server) HandlerStats(w http.ResponseWriter, r *http.Request) {
stats, err := s.store.Stats(statType, limit) stats, err := s.store.Stats(statType, limit)
if err != nil { if err != nil {
s.ServerLogger.Warnw("Error fetching stats", "error", err) s.ServerLogger.Warn("Error fetching stats", "error", err)
s.WriteAPIError(w, r, http.StatusInternalServerError, "Error fetching stats") s.WriteAPIError(w, r, http.StatusInternalServerError, "Error fetching stats")
return return
} }
encoder := json.NewEncoder(w) encoder := json.NewEncoder(w)
if err := encoder.Encode(stats); err != nil { if err := encoder.Encode(stats); err != nil {
s.ServerLogger.Debugf("Error encoding or writing response", "remote_ip", r.RemoteAddr, "error", err) s.ServerLogger.Debug("Error encoding or writing response", "remote_ip", r.RemoteAddr, "error", err)
} }
} }
@ -241,13 +244,13 @@ func (s *Server) HandlerQuery(w http.ResponseWriter, r *http.Request) {
userResults, err := s.store.Query(uq) userResults, err := s.store.Query(uq)
if err != nil { if err != nil {
s.WriteAPIError(w, r, http.StatusInternalServerError, "Unable to perform query") s.WriteAPIError(w, r, http.StatusInternalServerError, "Unable to perform query")
s.ServerLogger.Warnw("Error performing query", "error", err) s.ServerLogger.Warn("Error performing query", "error", err)
return return
} }
passwordResults, err := s.store.Query(pq) passwordResults, err := s.store.Query(pq)
if err != nil { if err != nil {
s.WriteAPIError(w, r, http.StatusInternalServerError, "Unable to perform query") s.WriteAPIError(w, r, http.StatusInternalServerError, "Unable to perform query")
s.ServerLogger.Warnw("Error performing query", "error", err) s.ServerLogger.Warn("Error performing query", "error", err)
return return
} }
@ -263,7 +266,7 @@ func (s *Server) HandlerQuery(w http.ResponseWriter, r *http.Request) {
queryResults, err := s.store.Query(aq) queryResults, err := s.store.Query(aq)
if err != nil { if err != nil {
s.WriteAPIError(w, r, http.StatusInternalServerError, "Unable to perform query") s.WriteAPIError(w, r, http.StatusInternalServerError, "Unable to perform query")
s.ServerLogger.Warnw("Error performing query", "error", err) s.ServerLogger.Warn("Error performing query", "error", err)
return return
} }
@ -272,7 +275,7 @@ func (s *Server) HandlerQuery(w http.ResponseWriter, r *http.Request) {
encoder := json.NewEncoder(w) encoder := json.NewEncoder(w)
if err := encoder.Encode(&results); err != nil { if err := encoder.Encode(&results); err != nil {
s.ServerLogger.Warnw("Error writing query results", "error", err) s.ServerLogger.Warn("Error writing query results", "error", err)
} }
} }
@ -294,6 +297,6 @@ func (s *Server) WriteAPIError(w http.ResponseWriter, r *http.Request, status in
apiErr := APIErrorResponse{Error: message} apiErr := APIErrorResponse{Error: message}
w.WriteHeader(status) w.WriteHeader(status)
if err := encoder.Encode(&apiErr); err != nil { if err := encoder.Encode(&apiErr); err != nil {
s.ServerLogger.Debugf("Error encoding or writing error response", "remote_ip", r.RemoteAddr, "error", err) s.ServerLogger.Debug("Error encoding or writing error response", "remote_ip", r.RemoteAddr, "error", err)
} }
} }