BadActor.org An in-memory application driven jailer written in Go

Related tags

Security badactor
Overview

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)
}
Issues
  • SIGSEGV in badactor.Director.maintenance()

    SIGSEGV in badactor.Director.maintenance()

    I'm seeing the following crash after my application is running for some time.

    >panic: runtime error: invalid memory address or nil pointer dereference
    >[signal SIGSEGV: segmentation violation code=0x1 addr=0x20 pc=0x8c3fc2]
    >
    >goroutine 9 [running]:
    [PATH]/vendor/github.com/jaredfolkins/badactor.(*Director).maintenance(0xc420283710, 0xc436b3f2b0, 0xc)
           [PATH]/vendor/github.com/jaredfolkins/badactor/director.go:181 +0x72
    [PATH]/vendor/github.com/jaredfolkins/badactor.(*Director).lMaintenance(0xc420283710)
            [PATH]/vendor/github.com/jaredfolkins/badactor/director.go:38 +0xad
    [PATH]/vendor/github.com/jaredfolkins/badactor.(*Studio).StartReaper.func1(0xc4201f26c0, 0xc4202448a0)
            [PATH]/vendor/github.com/jaredfolkins/badactor/studio.go:118 +0x132
    created by [PATH]/vendor/github.com/jaredfolkins/badactor.(*Studio).StartReaper
            [PATH]/vendor/github.com/jaredfolkins/badactor/studio.go:127 +0x57
    
    
    opened by mhilbrunner 10
  • Make 'Name' field accessible

    Make 'Name' field accessible

    The Problem

    In the Action interface, both attached functions receive an Actor and a Rule. While this is useful for things like contextual logging, there's no way to extract the actor name since it's not publicly exported.

    The Solution

    In this PR, the Actor.name field is renamed to Name. That way it becomes gettable for structs that implement Action interface.

    If we really want to keep the name private, we can instead add a GetName getter function to the struct that just returns a copy of the name. However, that seems like overkill.

    Related Issues

    Closes #6

    opened by terrabitz 6
  • Crash when reaping for the first time

    Crash when reaping for the first time

    I think the crash occurs when reaping for the first time.

    Stack trace:

    goroutine 8 [running]:
    github.com/jaredfolkins/badactor.(*Director).maintenance(0xc4200fcab0, 0xc42017a720, 0xc)
            /Users/chris/dev/go-workspace/src/github.com/jaredfolkins/badactor/director.go:181 +0x72
    github.com/jaredfolkins/badactor.(*Director).lMaintenance(0xc4200fcab0)
            /Users/chris/dev/go-workspace/src/github.com/jaredfolkins/badactor/director.go:38 +0xad
    github.com/jaredfolkins/badactor.(*Studio).StartReaper.func1(0xc42001c380, 0xc4200e0e70)
            /Users/chris/dev/go-workspace/src/github.com/jaredfolkins/badactor/studio.go:118 +0x132
    created by github.com/jaredfolkins/badactor.(*Studio).StartReaper
            /Users/chris/dev/go-workspace/src/github.com/jaredfolkins/badactor/studio.go:127 +0x57
    

    Configuration:

    	st := badactor.NewStudio(256)
    
    	ru := &badactor.Rule{
    		Name:        "RequestLimit",
    		Message:     "Too many requests",
    		StrikeLimit: rpts,
    		ExpireBase:  time.Second * 10,
    		Sentence:    time.Minute * 5,
    	}
    
    	st.AddRule(ru)
    
    	err := st.CreateDirectors(256)
    	if err != nil {
    		fmt.Print(err)
    	}
    
    	st.StartReaper(time.Minute * time.Duration(60))
    
    bug 
    opened by cwimberger 4
  • Add function to clear infractions

    Add function to clear infractions

    The problem

    In many cases, it's perfectly acceptable to increase infractions monotonically. However, it may be desirable to clear a bad actor's infraction record on certain events before they're jailed. For example, I would like to be able to reset a requestor's infractions on a successful login, since there may be overriding reasons why infractions were made despite the client having the correct password.

    This is also, for example, how Windows domain login works to determine account lockout.

    Recommended Solution

    A new function should be exposed on the Studio called ClearInfraction which allows clearing all infractions for a specific actor/rule combo.

    opened by terrabitz 2
  • Horizontal scaling

    Horizontal scaling

    Cool project!

    We are running multiple machines behind a load balancer. Ideally infractions would be counted across the cluster, rather than per machine.

    At first sight I don't see any storage. Is a thing like this possible?

    enhancement help wanted 
    opened by rubenv 2
  • No documentation on rule fields

    No documentation on rule fields

    While several fields in the Rule struct are fairly self-explanatory, they're not explicitly documented. In particular, I'm confused as to the difference between ExpireBase and Sentence. In most of the tests, they seem to be set to the same value.

    opened by terrabitz 1
  • Action.WhenJailed: Get actor name

    Action.WhenJailed: Get actor name

    Hello Jared,

    very nice library, thanks for your effort!

    I would like to use Action.WhenJailed to report violations via email. For this I would need the name of the blocked actor. Is there a way to read this from *Actor? It would be very convenient.

    Thank you very much and best regards!

    opened by f9a 1
  • Interest in integrating with Caddy v2 as a plugin?

    Interest in integrating with Caddy v2 as a plugin?

    I was just wondering if you might have interest in writing a Caddy plugin layer for this lib? Apparently you know @mholt! :smile:

    We've had some users ask for guides/instructions/help with integrating Caddy with fail2ban, but our log formats don't necessarily lend well to "legacy" tools that typically read apache/nginx style logs.

    Since this seems to work as a ServeHTTP middleware, it should be pretty easy to integrate into Caddy as a request handler! I'm sure many users could find this useful :grin:

    opened by francislavoie 0
Secure software enclave for storage of sensitive information in memory.

MemGuard Software enclave for storage of sensitive information in memory. This package attempts to reduce the likelihood of sensitive data being expos

Awn 2.2k Jun 24, 2022
A Flask-based HTTP(S) command and control (C2) framework with a web frontend. Malleable agents written in Go and scripts written in bash.

▄▄▄▄ ██▓ █████▒██▀███ ▒█████ ██████ ▄▄▄█████▓ ▓█████▄ ▓██▒▓██ ▒▓██ ▒ ██▒▒██▒ ██▒▒██ ▒ ▓ ██▒ ▓▒ ▒██▒ ▄██▒██▒▒████ ░▓██ ░▄█ ▒▒██░ ██▒░

Ashley Nikirk 21 Jun 16, 2022
A rest application to update firewalld rules on a linux server

Firewalld-rest A REST application to dynamically update firewalld rules on a linux server. Firewalld is a firewall management tool for Linux operating

Prashant Gupta 316 May 3, 2022
Open Source Web Application Firewall

DEPRECATED This repository started as a good idea but I didn't have enough time or desire to work on it. So, it's left here for historical / education

Ahmet Salih 182 Jun 16, 2022
Sqreen's Application Security Management for the Go language

Sqreen's Application Security Management for Go After performance monitoring (APM), error and log monitoring it’s time to add a security component int

Sqreen 160 Jun 18, 2022
A web-based testing platform for WAF (Web Application Firewall)'s correctness

WAFLab ?? WAFLab is a web-based platform for testing WAFs. Live Demo https://waflab.org/ Architecture WAFLab contains 2 parts: Name Description Langua

Microsoft 23 Jun 22, 2022
A vulnerable graphQL application, for testing purposes

Vulnerable-GoQL Vulnerable-GoQL is an web API which implements main security breach.

Escape 3 Jul 31, 2021
Coraza WAF is a golang modsecurity compatible web application firewall library

Coraza Web Application Firewall, this project is a Golang port of ModSecurity with the goal to become the first enterprise-grade Open Source Web Application Firewall, flexible and powerful enough to serve as the baseline for many projects.

Juan Pablo Tosso 540 Jun 24, 2022
2FA (Two-Factor Authentication) application for CLI terminal with support to import/export andOTP files.

zauth zauth is a 2FA (Two-Factor Authentication) application for terminal written in Go. Features Supports both TOTP and HOTP codes. Add new entries d

Rijul Gulati 76 Jun 15, 2022
Example mini project golang scanner application

Golang Scanner Contoh pembuatan aplikasi Java menggunakan BlueJ cek disini, tetapi berikut ini adalah versi rebuild dari Java ke Golang, dengan menggu

Restu Wahyu Saputra 5 Jun 1, 2022
Search and store the best cryptos for the best scalable and modern application development.

Invst Hunt Search and store the best cryptos for the best scalable and modern application development. Layout Creating... Project Challenge The Techni

Fábio Morais 1 Nov 12, 2021
Application trying to detect processes vulnerable to log4j JNDI exploit

Log4j JNDI Jar Detector Purpose This application is able to detect jars used by

Criteo 4 Jan 25, 2022
null 0 Feb 2, 2022
Bhojpur Consulting 0 Jan 1, 2022
Password generator written in Go

go-generate-password Password generator written in Go. Use as a library or as a CLI. Usage CLI go-generate-password can be used on the cli, just insta

Miles Croxford 41 Jun 14, 2022
Let's Encrypt client and ACME library written in Go

Let's Encrypt client and ACME library written in Go. Features ACME v2 RFC 8555 Register with CA Obtain certificates, both from scratch or with an exis

null 5.4k Jun 24, 2022
Let's Encrypt client and ACME library written in Go

Let's Encrypt client and ACME library written in Go. Features ACME v2 RFC 8555 Register with CA Obtain certificates, both from scratch or with an exis

null 5.4k Jun 26, 2022
IIS shortname scanner written in Go

sns IIS shortname scanner written in Go Installation Make sure you've a recent version of the Go compiler installed on your system. Then just run: GO1

null 108 Jun 21, 2022