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
@@ -0,0 +1,5 @@
|
||||
# Copy to .env (gitignored). Variables already set in your shell win.
|
||||
IPABUSEDB_API_KEY=
|
||||
DOCKER_CONTAINER_NAME=sshd
|
||||
# Point the docker CLI at a remote daemon; credentials above stay local.
|
||||
DOCKER_HOST=ssh://user@sshd-host.example.com
|
||||
@@ -1,2 +1,3 @@
|
||||
/ipabusedb-docker-ssh
|
||||
/reports.csv
|
||||
/.env
|
||||
@@ -21,10 +21,21 @@ Categories are `18,22` (Brute-Force, SSH).
|
||||
|
||||
## Configuration
|
||||
|
||||
| Variable | Purpose |
|
||||
| ----------------------- | ------------------------------------------------ |
|
||||
| `DOCKER_CONTAINER_NAME` | container whose logs are read (required) |
|
||||
| `IPABUSEDB_API_KEY` | AbuseIPDB API key (required only with `--submit`) |
|
||||
A `.env` file in the working directory is loaded at startup (see
|
||||
`.env.example`; `.env` is gitignored). Variables already set in the
|
||||
shell take precedence over the file.
|
||||
|
||||
| Variable | Purpose |
|
||||
| ----------------------- | -------------------------------------------------------- |
|
||||
| `DOCKER_CONTAINER_NAME` | container whose logs are read (required) |
|
||||
| `IPABUSEDB_API_KEY` | AbuseIPDB API key (required only with `--submit`) |
|
||||
| `DOCKER_HOST` | passed through to the `docker` CLI to target a remote daemon |
|
||||
|
||||
Because the program shells out to the `docker` CLI with its own
|
||||
environment, `DOCKER_HOST` (and `DOCKER_CONTEXT`, `DOCKER_TLS_VERIFY`,
|
||||
`DOCKER_CERT_PATH`) work exactly as they do for `docker` itself. Set
|
||||
`DOCKER_HOST=ssh://user@host` in `.env` to read logs from a remote
|
||||
daemon while the AbuseIPDB key never leaves the local machine.
|
||||
|
||||
Flags:
|
||||
|
||||
@@ -34,6 +45,7 @@ Flags:
|
||||
| `-o FILE` | write the CSV to `FILE` instead of stdout |
|
||||
| `--input FILE` | parse `FILE` (or `-` for stdin) instead of `docker logs` |
|
||||
| `--submit` | POST the CSV to the AbuseIPDB bulk-report endpoint |
|
||||
| `--env FILE` | env file to autoload (default `.env`; missing is ignored) |
|
||||
|
||||
## Usage
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
module sneak.berlin/go/hacks/ipabusedb-docker-ssh
|
||||
|
||||
go 1.26.5
|
||||
|
||||
require github.com/joho/godotenv v1.5.1
|
||||
@@ -0,0 +1,2 @@
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
@@ -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