ipabusedb-docker-ssh: autoload .env via godotenv, document DOCKER_HOST passthrough, surface docker CLI errors
This commit is contained in:
1 parent
21bda80f88
commit
b8c36689ae
6 files changed
+59
-5
No files matched your search
@@ -2,10 +2,13 @@
|
||||
// "docker logs -t", extracts brute-force / invalid-user attempts, and
|
||||
// emits a CSV suitable for the AbuseIPDB bulk-report endpoint.
|
||||
//
|
||||
// Environment:
|
||||
// Environment (a .env file in the working directory is loaded first;
|
||||
// variables already set in the process environment take precedence):
|
||||
//
|
||||
// DOCKER_CONTAINER_NAME container whose logs are read (required)
|
||||
// IPABUSEDB_API_KEY AbuseIPDB API key (required with --submit)
|
||||
// DOCKER_HOST passed through to the docker CLI, so e.g.
|
||||
// ssh://user@host reads logs from a remote daemon
|
||||
//
|
||||
// Flags:
|
||||
//
|
||||
@@ -35,6 +38,8 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/joho/godotenv"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -74,8 +79,13 @@ type parser struct {
|
||||
mu sync.Mutex
|
||||
events map[netip.Addr]*event
|
||||
skipped int
|
||||
// unparsed keeps the last few lines without a valid timestamp; the
|
||||
// docker CLI writes its own errors to stderr without one.
|
||||
unparsed []string
|
||||
}
|
||||
|
||||
const keepUnparsed = 5
|
||||
|
||||
func newParser(cutoff time.Time) *parser {
|
||||
return &parser{cutoff: cutoff, events: map[netip.Addr]*event{}}
|
||||
}
|
||||
@@ -92,6 +102,10 @@ func (p *parser) handleLine(line string) {
|
||||
if err != nil {
|
||||
p.mu.Lock()
|
||||
p.skipped++
|
||||
p.unparsed = append(p.unparsed, line)
|
||||
if len(p.unparsed) > keepUnparsed {
|
||||
p.unparsed = p.unparsed[1:]
|
||||
}
|
||||
p.mu.Unlock()
|
||||
return
|
||||
}
|
||||
@@ -193,7 +207,13 @@ func readDockerLogs(ctx context.Context, p *parser, container string, since time
|
||||
args = append(args, "--since", since.String())
|
||||
}
|
||||
args = append(args, container)
|
||||
// cmd.Env is left nil so the docker CLI inherits the full process
|
||||
// environment, including DOCKER_HOST / DOCKER_CONTEXT / DOCKER_TLS_*
|
||||
// loaded from .env, and can therefore target a remote daemon.
|
||||
cmd := exec.CommandContext(ctx, "docker", args...)
|
||||
if host := os.Getenv("DOCKER_HOST"); host != "" {
|
||||
slog.Info("reading logs via docker", "DOCKER_HOST", host, "container", container)
|
||||
}
|
||||
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@@ -221,6 +241,12 @@ func readDockerLogs(ctx context.Context, p *parser, container string, since time
|
||||
wg.Wait()
|
||||
close(errs)
|
||||
if err := cmd.Wait(); err != nil {
|
||||
p.mu.Lock()
|
||||
detail := strings.Join(p.unparsed, "\n")
|
||||
p.mu.Unlock()
|
||||
if detail != "" {
|
||||
return fmt.Errorf("docker logs %s: %w\n%s", container, err, detail)
|
||||
}
|
||||
return fmt.Errorf("docker logs %s: %w", container, err)
|
||||
}
|
||||
for err := range errs {
|
||||
@@ -286,8 +312,14 @@ func run() error {
|
||||
outPath := flag.String("o", "", "write CSV to this file instead of stdout")
|
||||
inPath := flag.String("input", "", "parse this file (or - for stdin) instead of running docker logs")
|
||||
doSubmit := flag.Bool("submit", false, "POST the CSV to the AbuseIPDB bulk-report endpoint")
|
||||
envFile := flag.String("env", ".env", "env file to autoload (missing file is ignored)")
|
||||
flag.Parse()
|
||||
|
||||
// Load .env without overriding variables already in the environment.
|
||||
if err := godotenv.Load(*envFile); err != nil && !errors.Is(err, os.ErrNotExist) {
|
||||
return fmt.Errorf("loading %s: %w", *envFile, err)
|
||||
}
|
||||
|
||||
apiKey := os.Getenv("IPABUSEDB_API_KEY")
|
||||
container := os.Getenv("DOCKER_CONTAINER_NAME")
|
||||
if *doSubmit && apiKey == "" {
|
||||
|
||||
Reference in new issue
Block a user