Skip to content

Commit

Permalink
Merge pull request #61 from deflect-ca/feature/block-ip
Browse files Browse the repository at this point in the history
Support Baskerville Command block_ip
  • Loading branch information
jeremy5189 authored Jun 4, 2024
2 parents a3dd343 + b237808 commit 214650a
Show file tree
Hide file tree
Showing 4 changed files with 106 additions and 63 deletions.
2 changes: 2 additions & 0 deletions banjax-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -121,3 +121,5 @@ use_user_agent_in_cookie:
# difficulty of sha-inv page, setting above 10 might cause solving to fail
sha_inv_expected_zero_bits: 10
session_cookie_not_verify: true
block_ip_ttl_seconds: 10
block_session_ttl_seconds: 10
11 changes: 7 additions & 4 deletions internal/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ type Config struct {
SitesToPasswordCookieTtlSeconds map[string]int `yaml:"password_persite_cookie_ttl_seconds"`
SitesToUseUserAgentInCookie map[string]bool `yaml:"use_user_agent_in_cookie"`
ExpiringDecisionTtlSeconds int `yaml:"expiring_decision_ttl_seconds"`
BlockIPTtlSeconds int `yaml:"block_ip_ttl_seconds"`
BlockSessionTtlSeconds int `yaml:"block_session_ttl_seconds"`
SitesToBlockIPTtlSeconds map[string]int `yaml:"sites_to_block_ip_ttl_seconds"`
SitesToBlockSessionTtlSeconds map[string]int `yaml:"sites_to_block_session_ttl_seconds"`
TooManyFailedChallengesIntervalSeconds int `yaml:"too_many_failed_challenges_interval_seconds"`
TooManyFailedChallengesThreshold int `yaml:"too_many_failed_challenges_threshold"`
PasswordCookieTtlSeconds int `yaml:"password_cookie_ttl_seconds"`
Expand Down Expand Up @@ -477,7 +481,7 @@ func updateExpiringDecisionLists(
ip string,
decisionListsMutex *sync.Mutex,
decisionLists *DecisionLists,
now time.Time,
expires time.Time,
newDecision Decision,
fromBaskerville bool,
domain string,
Expand All @@ -501,7 +505,6 @@ func updateExpiringDecisionLists(

// XXX We are not using nginx to banjax cache feature yet
// purgeNginxAuthCacheForIp(ip)
expires := now.Add(time.Duration(config.ExpiringDecisionTtlSeconds) * time.Second)
(*decisionLists).ExpiringDecisionLists[ip] = ExpiringDecision{
newDecision, expires, ip, fromBaskerville, domain}
}
Expand All @@ -512,7 +515,7 @@ func updateExpiringDecisionListsSessionId(
sessionId string,
decisionListsMutex *sync.Mutex,
decisionLists *DecisionLists,
now time.Time,
expires time.Time,
newDecision Decision,
fromBaskerville bool,
domain string,
Expand All @@ -531,7 +534,7 @@ func updateExpiringDecisionListsSessionId(
log.Printf("updateExpiringDecisionListsSessionId: Update session id decision with IP %s, session id %s, existing and new: %v, %v\n",
ip, sessionId, existingExpiringDecision.Decision, newDecision)
}
expires := now.Add(time.Duration(config.ExpiringDecisionTtlSeconds) * time.Second)

(*decisionLists).ExpiringDecisionListsSessionId[sessionId] = ExpiringDecision{
newDecision, expires, ip, fromBaskerville, domain}
}
Expand Down
3 changes: 2 additions & 1 deletion internal/iptables.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,12 +279,13 @@ func (b Banner) BanOrChallengeIp(
) {
log.Println("IPTABLES: BanOrChallengeIp", ip, decision)

expires := time.Now().Add(time.Duration(config.ExpiringDecisionTtlSeconds) * time.Second)
updateExpiringDecisionLists(
config,
ip,
&(*b.DecisionListsMutex),
&(*b.DecisionLists),
time.Now(),
expires,
decision,
false, // not from baskerville
domain,
Expand Down
153 changes: 95 additions & 58 deletions internal/kafka.go
Original file line number Diff line number Diff line change
Expand Up @@ -137,79 +137,116 @@ func RunKafkaReader(
}
}

func getBlockIpTtl(config *Config, host string) (blockIpTtl int) {
blockIpTtl = config.BlockSessionTtlSeconds
if ttl, ok := config.SitesToBlockIPTtlSeconds[host]; ok {
log.Printf("KAFKA: found site-specific block_ip ttl %s %d\n", host, ttl)
blockIpTtl = ttl
}
return
}

func getBlockSessionTtl(config *Config, host string) (blockSessionTtl int) {
blockSessionTtl = config.BlockIPTtlSeconds
if ttl, ok := config.SitesToBlockSessionTtlSeconds[host]; ok {
log.Printf("KAFKA: found site-specific block_session ttl %s %d\n", host, ttl)
blockSessionTtl = ttl
}
return
}

func handleCommand(
config *Config,
command commandMessage,
decisionListsMutex *sync.Mutex,
decisionLists *DecisionLists,
) {
// exempt a site from baskerville according to config
if _, disabled := config.SitesToDisableBaskerville[command.Host]; disabled {
log.Printf("KAFKA: %s disabled baskerville, skipping %s\n", command.Host, command.Name)
return
}

// handle commands
switch command.Name {
case "challenge_ip":
// exempt a site from challenge according to config
_, disabled := config.SitesToDisableBaskerville[command.Host]

// XXX do a real valid IP check?
if len(command.Value) > 4 && !disabled {
updateExpiringDecisionLists(
config,
command.Value,
decisionListsMutex,
decisionLists,
time.Now(),
Challenge,
true, // from baskerville, provide to http_server to distinguish from regex
command.Host,
)
log.Printf("KAFKA: challenge_ip: %s\n", command.Value)
} else if disabled {
log.Printf("KAFKA: DIS-BASK: not challenge %s, site %s disabled baskerville\n", command.Value, command.Host)
} else {
log.Printf("KAFKA: command value looks malformed: %s\n", command.Value)
}
handleIPCommand(config, command, decisionListsMutex, decisionLists, Challenge, config.ExpiringDecisionTtlSeconds)
break
case "challenge_session", "block_session":
if command.SessionId == "" {
log.Printf("KAFKA: session_id is EMPTY, break\n")
break
}
// exempt a site from challenge according to config
_, disabled := config.SitesToDisableBaskerville[command.Host]

if !disabled {
// gin does urldecode or cookie, so we decode any possible urlencoded session id from kafka
sessionIdDecoded, decodeErr := url.QueryUnescape(command.SessionId)
if decodeErr != nil {
log.Printf("KAFKA: fail to urldecode session_id %s, break\n", command.SessionId)
break
}
var decision Decision
if command.Name == "block_session" {
log.Printf("KAFKA: %s block_session: %s\n", command.Host, sessionIdDecoded)
decision = NginxBlock
} else {
log.Printf("KAFKA: %s challenge_session: %s\n", command.Host, sessionIdDecoded)
decision = Challenge
}
updateExpiringDecisionListsSessionId(
config,
command.Value,
sessionIdDecoded,
decisionListsMutex,
decisionLists,
time.Now(),
decision,
true, // from baskerville, provide to http_server to distinguish from regex
command.Host,
)
} else {
log.Printf("KAFKA: DIS-BASK: no action on %s, site %s disabled baskerville\n", command.Value, command.Host)
}
case "block_ip":
ttl := getBlockIpTtl(config, command.Host)
handleIPCommand(config, command, decisionListsMutex, decisionLists, NginxBlock, ttl)
break
case "challenge_session":
handleSessionCommand(config, command, decisionListsMutex, decisionLists, Challenge, config.ExpiringDecisionTtlSeconds)
break
case "block_session":
ttl := getBlockSessionTtl(config, command.Host)
handleSessionCommand(config, command, decisionListsMutex, decisionLists, NginxBlock, ttl)
break
default:
log.Printf("KAFKA: unrecognized command name: %s\n", command.Name)
}
}

func handleIPCommand(
config *Config,
command commandMessage,
decisionListsMutex *sync.Mutex,
decisionLists *DecisionLists,
decision Decision,
expireDuration int,
) {
if len(command.Value) <= 4 {
log.Printf("KAFKA: command value looks malformed: %s\n", command.Value)
return
}

log.Printf("KAFKA: handleIPCommand %s %s %s %d\n",
command.Host, command.Value, decision, expireDuration)

updateExpiringDecisionLists(
config,
command.Value,
decisionListsMutex,
decisionLists,
time.Now().Add(time.Duration(expireDuration)*time.Second),
decision,
true, // from baskerville, provide to http_server to distinguish from regex
command.Host,
)
}

func handleSessionCommand(
config *Config,
command commandMessage,
decisionListsMutex *sync.Mutex,
decisionLists *DecisionLists,
decision Decision,
expireDuration int,
) {
// gin does urldecode on cookie, so we decode any possible urlencoded session id from kafka
sessionIdDecoded, decodeErr := url.QueryUnescape(command.SessionId)
if decodeErr != nil {
log.Printf("KAFKA: fail to urldecode session_id %s, skip command\n", command.SessionId)
return
}

log.Printf("KAFKA: handleSessionCommand %s %s %s %s %d\n",
command.Host, command.Value, sessionIdDecoded, decision, expireDuration)

updateExpiringDecisionListsSessionId(
config,
command.Value,
sessionIdDecoded,
decisionListsMutex,
decisionLists,
time.Now().Add(time.Duration(expireDuration)*time.Second),
decision,
true, // from baskerville, provide to http_server to distinguish from regex
command.Host,
)
}

type StatusMessage struct {
Id string `json:"id"`
Name string `json:"name"`
Expand Down

0 comments on commit 214650a

Please sign in to comment.