Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
fionera committed Nov 20, 2023
0 parents commit 14889bf
Show file tree
Hide file tree
Showing 40 changed files with 5,496 additions and 0 deletions.
65 changes: 65 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
name: Go

on: [push]

jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'

- uses: dominikh/[email protected]
with:
install-go: false

build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'

- name: Build
run: go build -v ./...

test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'

- name: Test
run: go test -v ./...

e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'

- name: setup environment
run: |
sudo apt-get install -y software-properties-common
sudo add-apt-repository -y ppa:vbernat/haproxy-2.8
sudo apt-get update
sudo apt-get install -y haproxy
haproxy -vv
- name: Test E2E
run: go test -v ./... --tags=e2e
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.idea
*.iml
*.sock
69 changes: 69 additions & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# Berghain Design Doc

Berghain has two central structures on which the validation and verification works.

Every Request starts by being loaded into an Identity containing the SrcAddr, Host, Frontend and Level. This Identity
can be used to ask the validator if the given cookie is valid.

If it is not valid, a response is sent to indicate that the client should be redirected to berghain itself. Berghain
exposes a http server handling all the logic required to create a new cookie for the user.

## Hetzner

https://accounts.hetzner.com/_ray/pow

Types:

1. Check if cookie gets set
2. slowdown with js sleep, value set by cookie

Cookie

heray-clearance=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiI5NmNkMmRmZS1kYjRhLTRkMDUtODkxZi0xMjU5Mjc1N2Q4M2UifQ.lZpSBjKXFZFJcssyHZGi_msS0O3sj-q4mBJJ8KyhzjY; PHPSESSID=bbbf6067fd3fb1236a079a30858f8e01

< set-cookie:
heray-clearance=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1aWQiOiJhZjllNmNkMC03YzQzLTQzMjktYjQxNy1jN2QyZTlmNmJkMWIifQ.4M3tnPkQCbec4o4oCYu9EuLRRnD48n6cnL61wsZj0YA;
Domain=accounts.hetzner.com; Path=/; Secure; HttpOnly; SameSite=Strict
{
"alg": "HS256",
"typ": "JWT"
}
{
"uid": "af9e6cd0-7c43-4329-b417-c7d2e9f6bd1b"
}

< set-cookie:
heray-user-session=P8Q1Q3egBWhufKTNu-9jRQ|1695692792|5JP4YXgr6SGrIjpYLgb5KrzE3dozcRZQlYucH1DSeqMacoALweYogJ7g0xMutjwwRHuZazpoem01oPy4V-hGDQ|BJQic3cIHqIwkIUexiH2Vyrp1sk;
Path=/; SameSite=Lax; Secure; HttpOnly
unknown|time|unknown|unknown

< set-cookie: HERay_WaitFor=62; Domain=accounts.hetzner.com; Path=/; SameSite=Strict

## Babiel

https://babiel.com/.enodia/challenge

Types:

1. Cookie with POW Challenge

Runtime:

1. Website contains first challenge
2. Send for validation
3. Check for new Challenge

Cookie

enodia=eyJleHAiOjE2OTI4MzUzMzEsImNvbnRlbnQiOnRydWUsImF1ZCI6ImF1dGgiLCJIb3N0Ijoid3d3LmJ1bmRlc3RhZy5kZSIsIlNvdXJjZUlQIjoiOTEuMC4yOS40MiIsIkNvbmZpZ0lEIjoiOGRhZGNlMTI1ZmQyYzM5MzJiOTQzYjUyZTlkMmNkNjUwNTc1NGUxNjIyMTJhMmNlMWJiNWFmMTVjMGQ0YmJmZSJ9.knObOtKZgLPnFIEEW9AYq2nAAeTFqk295D0mqteZ8uA=

## Cloudflare

https://challenges.cloudflare.com/turnstile/v0/g/313d8a27/api.js?onload=URXdVe4&render=explicit

https://challenges.cloudflare.com/cdn-cgi/challenge-platform/h/g/orchestrate/chl_api/v1?ray=7fb72b3cba9bc4a4


## HAProxy-Protection

https://gitgud.io/fatchan/haproxy-protection/-/blob/a6f3613b6a4e41860f4916de508de80e47e2ee98/src/js/worker.js
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Berghain

🕺 Welcome to Berghain: Where Only Valid Browsers Get the Backend Party Started! 🎉

Berghain is your trusty SPOE-Agent, guarding the entrance to the backend like a seasoned bouncer. This Go and
HAProxy-powered tool ensures that only the coolest and most valid browsers can access the exclusive party happening on
the other side.

With Berghain in charge, you can be confident that your backend is reserved for the true VIPs of the internet, keeping
out any uninvited guests. It's like the bouncer of the web world, ensuring that your resources are reserved for the
browsers that really know how to dance!

## Supported CAPTCHAs
- None (Simple JS execute)
- POW

## Planned support
- Simple Captcha (Including Sound)
- [hCaptcha](https://www.hcaptcha.com/)
- [reCatpcha](https://developers.google.com/recaptcha?hl=de)
- [Turnstile](https://developers.cloudflare.com/turnstile/)

## Example setup with HAProxy
To start berghain locally you can follow these easy steps:

1. Run `npm run build` inside `web/`
2. Run `haproxy -f examples/haproxy/haproxy.cfg`
3. Run `go run ./cmd/spop/. -config cmd/spop/config.yaml`

## Attributions
Thanks to [@NullDev][NullDev] and [@arellak][arellak], as they did most of the frontend work.
59 changes: 59 additions & 0 deletions berghain.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package berghain

import (
"crypto/hmac"
"crypto/sha256"
"hash"
"log"
"sync"
"time"
)

type LevelConfig struct {
Duration time.Duration
Type ValidationType
}

type Berghain struct {
Levels []*LevelConfig

secret []byte
hmac sync.Pool
}

var hashAlgo = sha256.New

func NewBerghain(secret []byte) *Berghain {
return &Berghain{
secret: secret,
hmac: sync.Pool{
New: func() any {
return NewZeroHasher(hmac.New(hashAlgo, secret))
},
},
}
}

func (b *Berghain) acquireHMAC() hash.Hash {
return b.hmac.Get().(hash.Hash)
}

func (b *Berghain) releaseHMAC(h hash.Hash) {
h.Reset()
b.hmac.Put(h)
}

func (b *Berghain) LevelConfig(level uint8) *LevelConfig {

if level == 0 {
log.Println("level cannot be zero. correcting to 1")
}

if level > uint8(len(b.Levels)) {
log.Printf("level too high. correcting to %d", len(b.Levels))
}

level = min(uint8(len(b.Levels)), max(1, level))

return b.Levels[level-1]
}
9 changes: 9 additions & 0 deletions berghain.iml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="WEB_MODULE" version="4">
<component name="Go" enabled="true" />
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>
47 changes: 47 additions & 0 deletions berghain_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package berghain

import (
"crypto/rand"
"net/netip"
"testing"
"time"
)

func generateSecret(tb testing.TB) []byte {
tb.Helper()
b := make([]byte, 32)
_, err := rand.Read(b)
if err != nil {
tb.Fatal(err)
}
return b
}

func TestBerghain(t *testing.T) {
bh := NewBerghain(generateSecret(t))
bh.Levels = []*LevelConfig{
{
Duration: time.Minute,
Type: ValidationTypeNone,
},
}

req := AcquireValidatorRequest()
req.Identifier = &RequestIdentifier{
SrcAddr: netip.MustParseAddr("1.2.3.4"),
Host: []byte("example.com"),
Level: 1,
}
req.Method = "GET"

resp := AcquireValidatorResponse()
err := bh.LevelConfig(req.Identifier.Level).Type.RunValidator(bh, req, resp)
if err != nil {
t.Errorf("validator failed: %v", err)
}

err = bh.IsValidCookie(*req.Identifier, resp.Token.ReadBytes())
if err != nil {
t.Errorf("invalid cookie: %v", err)
}
}
79 changes: 79 additions & 0 deletions cmd/spop/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package main

import (
"encoding/base64"
"log"
"os"
"time"

"gopkg.in/yaml.v3"

"github.com/fionera/berghain"
)

type Config struct {
Secret Secret `yaml:"secret"`
Default FrontendConfig `yaml:"default"`
Frontend map[string]FrontendConfig `yaml:"frontend"`
}

type Secret []byte

func (s *Secret) UnmarshalYAML(node *yaml.Node) error {
ba, err := base64.StdEncoding.DecodeString(node.Value)
if err != nil {
return err
}
*s = ba
return nil
}

type FrontendConfig []LevelConfig

func (fc FrontendConfig) AsBerghain(s []byte) *berghain.Berghain {
b := berghain.NewBerghain(s)
for _, c := range fc {
b.Levels = append(b.Levels, c.AsLevelConfig())
}
return b
}

type LevelConfig struct {
Duration time.Duration `yaml:"duration"`
Type string `yaml:"type"`
}

func (c LevelConfig) AsLevelConfig() *berghain.LevelConfig {
var lc berghain.LevelConfig

lc.Duration = c.Duration

switch c.Type {
case "none":
lc.Type = berghain.ValidationTypeNone
case "pow":
lc.Type = berghain.ValidationTypePOW
default:
log.Fatalf("unknown validation type: %s", c.Type)
}

return &lc
}

func loadConfig() Config {
if configPath == "" {
log.Fatal("missing config path")
}

f, err := os.Open(configPath)
if err != nil {
log.Fatalf("failed opening config: %v", err)
}

var c Config
if err := yaml.NewDecoder(f).Decode(&c); err != nil {
log.Fatalf("failed reading config: %v", err)
}

return c
}
18 changes: 18 additions & 0 deletions cmd/spop/config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
secret: JMal0XJRROOMsMdPqggG2tR56CTkpgN3r47GgUN/WSQ=

default:
- duration: 24h
type: none
- duration: 24h
type: fingerprint
- duration: 30m
type: pow

frontend:
my_fancy_frontend:
- duration: 30s
type: none
- duration: 20s
type: pow
- duration: 10s
type: pow
Loading

0 comments on commit 14889bf

Please sign in to comment.