-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
40 changed files
with
5,493 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,3 @@ | ||
.idea | ||
*.iml | ||
*.iml | ||
*.sock |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,31 @@ | ||
# Client-Validation | ||
# Berghain | ||
|
||
A validator to ensure your clients are real browsers. | ||
🕺 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](https://github.com/NullDev) and [@arellak](https://github.com/arellak), as they did most of the frontend work. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.