From 9b46138489035fe0fb48634e286e88f5e8b07b86 Mon Sep 17 00:00:00 2001 From: wgroeneveld Date: Sun, 11 Apr 2021 20:37:26 +0200 Subject: [PATCH] added more readme, forward IP header checks in limiter --- README.md | 57 +++++++++++++++++++++++++++++++++++++++++++++++--- app/limiter.go | 15 ++++++++++++- app/server.go | 2 +- main.go | 13 ++++++++++-- 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index dd17d06..8c32a53 100644 --- a/README.md +++ b/README.md @@ -28,19 +28,59 @@ Well, that's easy! 3. ??? 4. Profit! +It's very much a fire-and-forget thing. Put it behind a reverse proxy such as nginx using something like this: + +``` +server { + listen 443 ssl http2; + listen [::]:443 ssl http2; + + server_name [your-domain]; + + location / { + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $remote_addr; + proxy_set_header Host $host; + proxy_pass http://127.0.0.1:[your-port]; + } + ssl_certificate /etc/letsencrypt/live/[your-domain]/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/[your-domain]/privkey.pem; +} +``` + +Create a very simple Linux system service that fires up the jam: + +``` +[Unit] +Description=Go-Jamming +After=network.target + +[Service] +User=[myuser] +WorkingDirectory=/var/www/gojamming +ExecStart=/var/www/gojamming/go-jamming +SuccessExitStatus=0 + +[Install] +WantedBy=multi-user.target +``` + +Now install using `sudo systemctl enable/install gojamming` and you're done! + ## Configuration -Place a `config.json` file in the same directory that looks like this: +Place a `config.json` file in the same directory that looks like this: (below are the default values) ```json { "port": 1337, "host": "localhost", - "token": "sometoken", + "token": "miauwkes", "dataPath": "data", "utcOffset": 60, "allowedWebmentionSources": [ - "blah.com" + "brainbaking.com", + "jefklakscodex.com" ], "disallowedWebmentionDomains": [ "youtube.com" @@ -55,6 +95,8 @@ Place a `config.json` file in the same directory that looks like this: If a config file is missing, or required keys are missing, a warning will be generated and default values will be used instead. See `common/config.go`. +--- + ## What's in it? ### 1. Webmentions @@ -131,3 +173,12 @@ Will result in a `200 OK` - that returns XML according to [The W3 pingback XML-R Happens automatically through `PUT /webmention/:domain/:token`! Links that are discovered as `rel="pingback"` that **do not** already have a webmention link will be processed as XML-RPC requests to be send. +## Troubleshooting + +Run in verbose mode: use `-versbose`. This also logs debug info. Structured JSON is outputted through os.Stderr - which is usually `/var/log/syslog`. + +If rolling files in a separate location is required, [lumberjack](https://github.com/natefinch/lumberjack) could be added in `main.go`. + +There's a **rate limiting** system implemented with a rate limit of 5 requests per second and a maximum burst rate of 10. +That's pretty flexible. I have not taken the trouble to put this into the config, it should do in most cases. If you get a `429 too many requests`, you've hit the limiter. +A separate goroutine cleans up ips each 2 minutes, the TTL is 5 minuts. See `limiter.go`. \ No newline at end of file diff --git a/app/limiter.go b/app/limiter.go index 4238b42..55fa326 100644 --- a/app/limiter.go +++ b/app/limiter.go @@ -6,6 +6,7 @@ import ( "github.com/rs/zerolog/log" "golang.org/x/time/rate" "net/http" + "strings" "sync" "time" ) @@ -76,7 +77,7 @@ func (rl *RateLimiter) cleanupVisitors() { // with the help of https://www.alexedwards.net/blog/how-to-rate-limit-http-requests, TY! func (rl *RateLimiter) limiterMiddleware(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - ip := r.RemoteAddr // also contains port, but don't care + ip := rl.guessIp(r) limiter := rl.getVisitor(ip) if limiter.Allow() == false { @@ -88,3 +89,15 @@ func (rl *RateLimiter) limiterMiddleware(next http.Handler) http.Handler { next.ServeHTTP(w, r) }) } + +func (rl *RateLimiter) guessIp(r *http.Request) string { + realIp := r.Header.Get("X-Real-IP") + forwardedFor := r.Header.Get("X-Forwarded-For") + if realIp != "" { // in case of proxy. is IP itself + return realIp + } + if forwardedFor != "" { // in case of proxy. Could be: clientip, proxy1, proxy2, ... + return strings.Split(forwardedFor, ",")[0] + } + return r.RemoteAddr // also contains port, but don't care +} diff --git a/app/server.go b/app/server.go index 466cdeb..3e7ab5d 100644 --- a/app/server.go +++ b/app/server.go @@ -43,5 +43,5 @@ func Start() { r.Use(NewRateLimiter(5, 10).Middleware) log.Info().Int("port", server.conf.Port).Msg("Serving...") - http.ListenAndServe(":"+strconv.Itoa(server.conf.Port), nil) + log.Fatal().Err(http.ListenAndServe(":"+strconv.Itoa(server.conf.Port), nil)) } diff --git a/main.go b/main.go index ecb4499..04a95b1 100644 --- a/main.go +++ b/main.go @@ -1,6 +1,7 @@ package main import ( + "flag" "os" "brainbaking.com/go-jamming/app" @@ -11,8 +12,16 @@ import ( func main() { zerolog.TimeFieldFormat = zerolog.TimeFormatUnix - // TODO this should only be enabled in local mode. Fix with config? - log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + + verboseFlag := flag.Bool("verbose", false, "Verbose mode (pretty print log, debug level)") + flag.Parse() + + // logs by default to Stderr (/var/log/syslog). Rolling files possible via lumberjack. + zerolog.SetGlobalLevel(zerolog.InfoLevel) + if *verboseFlag == true { + log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr}) + zerolog.SetGlobalLevel(zerolog.DebugLevel) + } log.Debug().Msg("Let's a go!") app.Start()