commit
This commit is contained in:
parent
16116a7b68
commit
f1ffc2f107
4 changed files with 219 additions and 1 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -8,7 +8,7 @@
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
logdebarker
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|
||||||
|
|
83
README.md
83
README.md
|
@ -1,2 +1,85 @@
|
||||||
# logdebarker
|
# logdebarker
|
||||||
|
|
||||||
|
`logdebarker` is a UNIX-style log sanitizer tool written in Go. It censors sensitive information from logs, config files, and other text streams based on user-defined patterns. This is useful for cleaning data before exporting logs or sharing files.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
* Accepts input from STDIN or a file
|
||||||
|
* Outputs to STDOUT or a specified output file
|
||||||
|
* Uses `$HOME/.blocked_words.txt` as a pattern file for matching sensitive strings
|
||||||
|
* Enforces strict file permissions on the config file (`0700` only)
|
||||||
|
* Supports custom redaction strings (e.g. `redaction: <string>`)
|
||||||
|
* Ignores comment lines beginning with `#` in both input and config
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Pipe logs through `logdebarker`:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo cat /var/log/someprogram/log.log | logdebarker | tee cleaned_log.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Sanitize a file to a new output file:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo logdebarker /etc/nginx/conf.d/somesite.conf output.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
`logdebarker` reads its blocklist from:
|
||||||
|
|
||||||
|
```
|
||||||
|
$HOME/.blocked_words.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
Each line should be a literal string to redact. Lines starting with `#` are ignored. You may optionally define one redaction string:
|
||||||
|
|
||||||
|
```
|
||||||
|
redaction: [your-censor-string]
|
||||||
|
```
|
||||||
|
|
||||||
|
Example `.blocked_words.txt`:
|
||||||
|
|
||||||
|
```
|
||||||
|
# block API keys
|
||||||
|
redaction: <removed>
|
||||||
|
ABC123XYZ456
|
||||||
|
supersecretpassword
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
To protect your sensitive word list, `logdebarker` will refuse to run if `$HOME/.blocked_words.txt` is not `chmod 700`.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
chmod 700 ~/.blocked_words.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
1. Clone and build:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
git clone https://your.git.repo/logdebarker.git
|
||||||
|
cd logdebarker
|
||||||
|
go build -o logdebarker
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Move the binary to your PATH:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
sudo mv logdebarker /usr/local/bin/
|
||||||
|
```
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
`go install archuser.org/logdebarker`
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
This project is released under the GPLv3 License.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
For bug reports, suggestions, or contributions, open an issue or submit a pull request.
|
||||||
|
|
3
go.mod
Normal file
3
go.mod
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module archuser.org/logdebarker
|
||||||
|
|
||||||
|
go 1.24.3
|
132
main.go
Normal file
132
main.go
Normal file
|
@ -0,0 +1,132 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"os/user"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
blockedWords []*regexp.Regexp
|
||||||
|
redaction = "redacted"
|
||||||
|
)
|
||||||
|
|
||||||
|
func checkBlockedWordsFilePerm(path string) {
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to stat %s: %v", path, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
mode := info.Mode().Perm()
|
||||||
|
if mode != 0o700 {
|
||||||
|
log.Fatalf("Permission on %s must be 700, found %o", path, mode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadBlockedWords(path string) {
|
||||||
|
file, err := os.Open(path)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to open %s: %v", path, err)
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
scanner := bufio.NewScanner(file)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := strings.TrimSpace(scanner.Text())
|
||||||
|
if line == "" || strings.HasPrefix(line, "#") {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if strings.HasPrefix(line, "redaction:") {
|
||||||
|
if redaction != "redacted" {
|
||||||
|
log.Fatalf("Multiple redaction definitions found in %s", path)
|
||||||
|
}
|
||||||
|
redaction = strings.TrimSpace(strings.TrimPrefix(line, "redaction:"))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
blockedWords = append(blockedWords, regexp.MustCompile(regexp.QuoteMeta(line)))
|
||||||
|
}
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
log.Fatalf("Error reading %s: %v", path, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func censorLine(line string) string {
|
||||||
|
for _, re := range blockedWords {
|
||||||
|
line = re.ReplaceAllString(line, redaction)
|
||||||
|
}
|
||||||
|
return line
|
||||||
|
}
|
||||||
|
|
||||||
|
func process(input io.Reader, output io.Writer) {
|
||||||
|
scanner := bufio.NewScanner(input)
|
||||||
|
writer := bufio.NewWriter(output)
|
||||||
|
for scanner.Scan() {
|
||||||
|
line := scanner.Text()
|
||||||
|
if strings.HasPrefix(strings.TrimSpace(line), "#") {
|
||||||
|
fmt.Fprintln(writer, line)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
censored := censorLine(line)
|
||||||
|
fmt.Fprintln(writer, censored)
|
||||||
|
}
|
||||||
|
writer.Flush()
|
||||||
|
if err := scanner.Err(); err != nil {
|
||||||
|
log.Fatalf("Error reading input: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if len(os.Args) == 1 && isInputFromTerminal() {
|
||||||
|
fmt.Fprintln(os.Stderr, "No input provided. Exiting.")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
usr, err := user.Current()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Unable to determine current user: %v", err)
|
||||||
|
}
|
||||||
|
confPath := filepath.Join(usr.HomeDir, ".blocked_words.txt")
|
||||||
|
checkBlockedWordsFilePerm(confPath)
|
||||||
|
loadBlockedWords(confPath)
|
||||||
|
|
||||||
|
switch len(os.Args) {
|
||||||
|
case 1:
|
||||||
|
process(os.Stdin, os.Stdout)
|
||||||
|
case 2:
|
||||||
|
inFile, err := os.Open(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Cannot open input file: %v", err)
|
||||||
|
}
|
||||||
|
defer inFile.Close()
|
||||||
|
process(inFile, os.Stdout)
|
||||||
|
case 3:
|
||||||
|
inFile, err := os.Open(os.Args[1])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Cannot open input file: %v", err)
|
||||||
|
}
|
||||||
|
defer inFile.Close()
|
||||||
|
outFile, err := os.Create(os.Args[2])
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Cannot create output file: %v", err)
|
||||||
|
}
|
||||||
|
defer outFile.Close()
|
||||||
|
process(inFile, outFile)
|
||||||
|
default:
|
||||||
|
log.Fatalf("Usage: %s [input_file] [output_file]", os.Args[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func isInputFromTerminal() bool {
|
||||||
|
fileInfo, err := os.Stdin.Stat()
|
||||||
|
if err != nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return (fileInfo.Mode() & os.ModeCharDevice) != 0
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue