Skip to content
BadActor.org An in-memory application driven jailer written in Go
Go
Branch: master
Clone or download
Latest commit 66d9391 Aug 20, 2019
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.gitignore fix: removed coverage file Dec 27, 2014
LICENSE enhc: added license file & code example Dec 26, 2014
README.md Update README.md Aug 19, 2019
action.go ench: exposed struct s/actor/Actor/ Jan 12, 2015
action_test.go enhc: removing sleep() from tests Jan 21, 2015
actor.go enhc: added status and message, changed StartReaper signature Jan 21, 2015
actor_test.go enhc: add more tests Jan 20, 2015
benchmark_test.go enhc: refactored functions further Jan 7, 2015
director.go fix: github.com//issues/2 Apr 26, 2017
director_test.go fix: github.com//issues/2 Apr 26, 2017
infraction.go enhc: comments added Jan 2, 2015
infraction_test.go enhc: comments added Jan 2, 2015
jail.go fix: bug in actorExists() causing panic Jan 10, 2015
jail_test.go fix: bug in actorExists() causing panic Jan 10, 2015
message.go enhc: added status and message, changed StartReaper signature Jan 21, 2015
message_test.go enhc: added status and message, changed StartReaper signature Jan 21, 2015
rule.go fix: bug in actorExists() causing panic Jan 10, 2015
rule_test.go initial commit Dec 13, 2014
status.go enhc: added status and message, changed StartReaper signature Jan 21, 2015
status_test.go enhc: added status and message, changed StartReaper signature Jan 21, 2015
studio.go enhc: refactor docs May 30, 2015
studio_bench_test.go enhc: added docs for StartReaper and time.Duration param Jan 21, 2015
studio_test.go enhc: added status and message, changed StartReaper signature Jan 21, 2015

README.md

badactor logo Coverage Status Documentation

BadActor

BadActor is an in-memory, application driven jailer built in the spirit of fail2ban. A middleware with the primary goal to increase the expense for "bad actors" who engage in system probing or attacks.

The BadActor logo is based on Renee French's wonderful gopher. Thanks Renee!

Install

$ go get github.com/jaredfolkins/badactor

Use Case

A common use case for BadActor is jailing an offender who fails to login to your website (N) times as this can signal a bruteforce attempt.

Tutorial

Checkout badactor.org for a tutorial.

Design

  • speed (subsecond response underload and submillisecond with standard operations)
  • no external dependencies
  • solid code coverage and thorough tests

Does It Scale?

BadActor can be included in your go application and ran concurrently. This allows you an easy way to scale up as BadActor's memory footprint is tiny. Because it leverages a light-weight cache with sharding and reaping, it allows most organizations to be confident that BadActor will not be a bottleneck.

Benchmarks

Type Value
Model Name MacBook Pro
Model Identifier MacBookPro11,3
Processor Name Intel Core i7
Processor Speed 2.3 GHz
Number of Processors 1
Total Number of Cores 4
L2 Cache (per Core) 256 KB
L3 Cache 6 MB
Memory 16 GB
1.8.2015
➜  badactor git:(master) ✗ go test -bench=. -cpu=4 -benchmem -benchtime=5s | column -t
PASS
BenchmarkIsJailed-4                50000000                          121       ns/op  0    B/op  0  allocs/op
BenchmarkIsJailedFor-4             50000000                          134       ns/op  0    B/op  0  allocs/op
BenchmarkInfraction-4              5000000                           1390      ns/op  528  B/op  7  allocs/op
BenchmarkInfractionlIsJailed-4     3000000                           2755      ns/op  800  B/op  9  allocs/op
BenchmarkInfractionlIsJailedFor-4  3000000                           2733      ns/op  800  B/op  9  allocs/op
BenchmarkStudioInfraction512-4     3000000                           2215      ns/op  591  B/op  9  allocs/op
BenchmarkStudioInfraction1024-4    3000000                           2357      ns/op  612  B/op  9  allocs/op
BenchmarkStudioInfraction2048-4    5000000                           2617      ns/op  621  B/op  9  allocs/op
BenchmarkStudioInfraction4096-4    5000000                           2566      ns/op  671  B/op  9  allocs/op
BenchmarkStudioInfraction65536-4   3000000                           3309      ns/op  667  B/op  9  allocs/op
BenchmarkStudioInfraction262144-4  2000000                           3644      ns/op  674  B/op  9  allocs/op
ok                                 github.com/jaredfolkins/badactor  178.239s
➜  badactor git:(master) ✗
12.30.2014
➜  badactor git:(master) ✗ go test -benchtime=5s -bench=. -benchmem -cpu=4 | column -t
PASS
BenchmarkIsJailed-4                  50000000                          133        ns/op  0          B/op  0        allocs/op
BenchmarkIsJailedFor-4               50000000                          136        ns/op  0          B/op  0        allocs/op
BenchmarkInfraction-4                10000000                          824        ns/op  116        B/op  5        allocs/op
BenchmarkInfractionMostCostly-4      10000000                          891        ns/op  116        B/op  5        allocs/op
BenchmarkInfractionIsJailed-4        3000000                           2569       ns/op  340        B/op  13       allocs/op
BenchmarkInfractionIsJailedFor-4     3000000                           2611       ns/op  340        B/op  13       allocs/op
Benchmark10000Actors1Infraction-4    1000                              8571335    ns/op  1162931    B/op  50023    allocs/op
Benchmark100000Actors1Infraction-4   100                               87687224   ns/op  11630938   B/op  500248   allocs/op
Benchmark1000000Actors1Infraction-4  10                                841989544  ns/op  116292788  B/op  5002740  allocs/op
Benchmark10000Actors4Infractions-4   200                               30728688   ns/op  4522659    B/op  170013   allocs/op
ok                                   github.com/jaredfolkins/badactor  93.868s
➜  badactor git:(master) ✗
12.24.2014
➜  badactor git:(master) ✗ go test -bench=. -benchtime=5s -benchmem | column -t
PASS
BenchmarkIsJailed                 50000000                          138        ns/op  0         B/op  0       allocs/op
BenchmarkIsJailedFor              50000000                          140        ns/op  0         B/op  0       allocs/op
BenchmarkInfraction               10000000                          943        ns/op  128       B/op  4       allocs/op
BenchmarkInfractionMostCostly     10000000                          1008       ns/op  128       B/op  4       allocs/op
Benchmark10000Actors              100                               140566388  ns/op  13150354  B/op  150598  allocs/op
Benchmark10000Actors4Infractions  50                                241030802  ns/op  17278074  B/op  210614  allocs/op
ok                                github.com/jaredfolkins/badactor  73.592s
➜  badactor git:(master) ✗
12.16.2014

This was before a serious refactoring. I am keeping it here because (a) I'd like to encourage others to benchmark their code and (b) I learned many valuable lessons while doing it.

➜  badactor git:(master) go test -bench=. -benchtime=5s -benchmem | column -t
PASS
BenchmarkInfraction1                  2000                              2679694   ns/op  518  B/op  10  allocs/op
BenchmarkInfraction10                 2000                              3050845   ns/op  516  B/op  10  allocs/op
BenchmarkInfraction100                2000                              3430051   ns/op  516  B/op  10  allocs/op
BenchmarkInfraction1000               2000                              3738125   ns/op  516  B/op  10  allocs/op
BenchmarkInfraction10000              2000                              4004534   ns/op  516  B/op  10  allocs/op
BenchmarkInfractionWithIsJailed1      3000                              1832770   ns/op  193  B/op  3   allocs/op
BenchmarkInfractionWithIsJailed10     3000                              1968030   ns/op  193  B/op  3   allocs/op
BenchmarkInfractionWithIsJailed100    3000                              2120179   ns/op  193  B/op  3   allocs/op
BenchmarkInfractionWithIsJailed1000   3000                              1955656   ns/op  193  B/op  3   allocs/op
BenchmarkInfractionWithIsJailed10000  3000                              1943728   ns/op  193  B/op  3   allocs/op
ok                                    github.com/jaredfolkins/badactor  109.879s
➜  badactor git:(master)

Action Interface

The Action Interface has two primary methods, WhenJailed and WhenTimeServed. An excerpt of an implementation is below. They are called when the actor is jailed for the rule or when the actor has served its time for a particular rule.

type MyAction struct{}

func (ma *MyAction) WhenJailed(a *badactor.Actor, r *badactor.Rule) error {
  // Do something here. Log, email, etc...
	return nil
}

func (ma *MyAction) WhenTimeServed(a *badactor.Actor, r *badactor.Rule) error {
  // Do something here. Log, email, etc...
	return nil
}

And assigned to the rule like so.

// define and add the rule to the stack
ru := &badactor.Rule{
  Name:        "Login",
  Message:     "You have failed to login too many times",
  StrikeLimit: 10,
  ExpireBase:  time.Second * 1,
  Sentence:    time.Second * 10,
  Action:      &MyAction{},
}
st.AddRule(ru)

Httprouter & Negroni Example

package main

import (
	"log"
	"net"
	"net/http"
	"time"

	"github.com/urfave/negroni"
	"github.com/jaredfolkins/badactor"
	"github.com/julienschmidt/httprouter"
)

var st *badactor.Studio

type MyAction struct{}

func (ma *MyAction) WhenJailed(a *badactor.Actor, r *badactor.Rule) error {
	return nil
}

func (ma *MyAction) WhenTimeServed(a *badactor.Actor, r *badactor.Rule) error {
	return nil
}

func main() {

	//runtime.GOMAXPROCS(4)

	// studio capacity
	var sc int32
	// director capacity
	var dc int32

	sc = 1024
	dc = 1024

	// init new Studio
	st = badactor.NewStudio(sc)

	// define and add the rule to the stack
	ru := &badactor.Rule{
		Name:        "Login",
		Message:     "You have failed to login too many times",
		StrikeLimit: 10,
		ExpireBase:  time.Second * 1,
		Sentence:    time.Second * 10,
		Action:      &MyAction{},
	}
	st.AddRule(ru)

	err := st.CreateDirectors(dc)
	if err != nil {
		log.Fatal(err)
	}

	//poll duration
	dur := time.Minute * time.Duration(60)
	// Start the reaper
	st.StartReaper(dur)

	// router
	router := httprouter.New()
	router.POST("/login", LoginHandler)

	// middleware
	n := negroni.Classic()
	n.Use(NewBadActorMiddleware())
	n.UseHandler(router)
	n.Run(":9999")

}

//
// HANDLER
//

// this is a niave login function for example purposes
func LoginHandler(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {

	var err error

	un := r.FormValue("username")
	pw := r.FormValue("password")

	// snag the IP for use as the actor's name
	an, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		panic(err)
	}

	// mock authentication
	if un == "example_user" && pw == "example_pass" {
		http.Redirect(w, r, "", http.StatusOK)
		return
	}

	// auth fails, increment infraction
	err = st.Infraction(an, "Login")
	if err != nil {
		log.Printf("[%v] has err %v", an, err)
	}

	// auth fails, increment infraction
	i, err := st.Strikes(an, "Login")
	log.Printf("[%v] has %v Strikes %v", an, i, err)

	http.Redirect(w, r, "", http.StatusUnauthorized)
	return
}

//
// MIDDLEWARE
//
type BadActorMiddleware struct {
	negroni.Handler
}

func NewBadActorMiddleware() *BadActorMiddleware {
	return &BadActorMiddleware{}
}

func (bam *BadActorMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request, next http.HandlerFunc) {

	// snag the IP for use as the actor's name
	an, _, err := net.SplitHostPort(r.RemoteAddr)
	if err != nil {
		panic(err)
	}

	// if the Actor is jailed, send them StatusUnauthorized
	if st.IsJailed(an) {
		http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
		return
	}

	// call the next middleware in the chain
	next(w, r)
}
You can’t perform that action at this time.