Skip to content

Instantly share code, notes, and snippets.

@geor-g
Forked from robbat2/00-haproxy.cfg
Created December 11, 2018 22:20
Show Gist options
  • Save geor-g/df29ec277654a8a7e4d753f13b3d9c49 to your computer and use it in GitHub Desktop.
Save geor-g/df29ec277654a8a7e4d753f13b3d9c49 to your computer and use it in GitHub Desktop.
rsync haproxy frontend with ratelimiting & deny, uses haproxy-lua
# This is a very very minimal rsync config that gives ratelimit and ban messages
global
lua-load 01-deny-rsync.lua
frontend fe_rsync
mode tcp
bind *:9873
tcp-request content set-var(txn.rsync_deny_message) str("") # Start empty
# Track the backend state
acl rsync_dead nbsrv(be_rsync) lt 1
# Clients will see 'rsync: did not see server greeting'
tcp-request content reject if rsync_dead
# Clients will see 'rsync offline'
tcp-request content set-var(txn.rsync_deny_message) str("rsync offline") if rsync_dead
# Track abuse
stick-table type ip size 200k expire 30s store gpc0,conn_rate(10s),http_req_rate(10s)
acl source_is_abuser src_get_gpc0 gt 0
acl conn_rate_abuse sc1_conn_rate gt 3
acl mark_as_abuser sc1_inc_gpc0 ge 0
tcp-request connection track-sc1 src
tcp-request content set-var(txn.rsync_deny_message) str("BANHAMMER") if conn_rate_abuse mark_as_abuser source_is_abuser
# If the deny message is longer than zero, issue deny message
# Clients will see the message and the rsync client will exit with a SUCCESS state!
tcp-request content use-service lua.deny-rsync if { var(txn.rsync_deny_message) -m len gt 0 }
# Otherwise use the backend
default_backend be_rsync
backend be_rsync
mode tcp
# This gives pure HAProxy check
# Other alternatives would be external-check that execs rsync or very
# bleeding edge lua-check
option tcp-check
tcp-check connect
# Step 1
tcp-check comment "Client Initialization"
tcp-check send "@RSYNCD: 28.0\n"
# Step 2
tcp-check comment "Server Initialization"
tcp-check expect rstring "@RSYNCD: [0-9.]+"
# Step 3
tcp-check comment "Client Query (module name rsync-health)"
tcp-check send "rsync-health\n"
# Step 4
tcp-check comment "Data (module name rsync-health)"
tcp-check expect string "@RSYNCD: OK\n"
# If the directory for the healthcheck does not exist, rsync will generate
# "@ERROR: chroot failed"
# Step 5
tcp-check comment "Cleanup"
tcp-check send "@RSYNCD: EXIT\n"
# This still leads to messy logs in rsync itself
# and rsync doesn't have the original source IP (does it really need it?)
# rsyncd[6562]: connect from localhost (127.0.0.1)
# rsyncd[6562]: rsync: safe_read failed to read 1 bytes [Receiver]: Connection reset by peer (104)
# rsyncd[6562]: rsync error: error in rsync protocol data stream (code 12) at io.c(276) [Receiver=3.1.3]
# This is where we'd put the realservers using discovery
server localhost localhost:873 check
# Service discovery instead would look like:
#server-template gentoo-rsync 50 _gentoo-rsync._tcp.service.consul resolvers consul resolve-prefer ipv4 maxconn 64 check rise 3 fall 2 check
#resolvers consul
# nameserver consul $consul-client-ip:8600
# resolve_retries 30
# timeout retry 2s
# hold valid 120s
# accepted_payload_size 8192
function rsync_deny(applet)
applet:send("@RSYNCD: 29.0\n")
local banmsg = applet:get_var("txn.rsync_deny_message")
if banmsg == nil then
banmsg = "MESSAGE GOES HERE"
end
applet:send(banmsg)
applet:send("\n")
applet:send("@RSYNCD: EXIT\n")
end
core.register_service("deny-rsync", "tcp", rsync_deny)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment