mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-11 19:18:46 +00:00
Compare commits
9 Commits
Xe/missing
...
Xe/smoke-t
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b12c5945b3 | ||
|
|
b320af79ee | ||
|
|
cf510eeace | ||
|
|
a47e781d91 | ||
|
|
06122fcd89 | ||
|
|
950ec24cf1 | ||
|
|
7cfefcd882 | ||
|
|
eacc001d69 | ||
|
|
7ca45f72e3 |
@@ -21,7 +21,8 @@
|
|||||||
"golang.go",
|
"golang.go",
|
||||||
"unifiedjs.vscode-mdx",
|
"unifiedjs.vscode-mdx",
|
||||||
"a-h.templ",
|
"a-h.templ",
|
||||||
"redhat.vscode-yaml"
|
"redhat.vscode-yaml",
|
||||||
|
"matthewpi.caddyfile-support"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,11 @@ services:
|
|||||||
dockerfile: .devcontainer/Dockerfile
|
dockerfile: .devcontainer/Dockerfile
|
||||||
volumes:
|
volumes:
|
||||||
- ../:/workspace/anubis:cached
|
- ../:/workspace/anubis:cached
|
||||||
|
- node_modules:/workspace/anubis/node_modules
|
||||||
environment:
|
environment:
|
||||||
VALKEY_URL: redis://valkey:6379/0
|
VALKEY_URL: redis://valkey:6379/0
|
||||||
#entrypoint: ["/usr/bin/sleep", "infinity"]
|
#entrypoint: ["/usr/bin/sleep", "infinity"]
|
||||||
user: vscode
|
user: vscode
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
node_modules:
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
pwd
|
sudo chown -R vscode:vscode ./node_modules
|
||||||
|
|
||||||
npm ci &
|
npm ci &
|
||||||
go mod download &
|
go mod download &
|
||||||
go install ./utils/cmd/... &
|
go install ./utils/cmd/... &
|
||||||
|
go install mvdan.cc/sh/v3/cmd/shfmt@latest &
|
||||||
|
|
||||||
wait
|
wait
|
||||||
|
|||||||
3
.github/actions/spelling/expect.txt
vendored
3
.github/actions/spelling/expect.txt
vendored
@@ -32,7 +32,7 @@ byteslice
|
|||||||
Bytespider
|
Bytespider
|
||||||
cachebuster
|
cachebuster
|
||||||
cachediptoasn
|
cachediptoasn
|
||||||
Caddyfile
|
caddyfile
|
||||||
caninetools
|
caninetools
|
||||||
Cardyb
|
Cardyb
|
||||||
celchecker
|
celchecker
|
||||||
@@ -181,6 +181,7 @@ lol
|
|||||||
lominsa
|
lominsa
|
||||||
maintainership
|
maintainership
|
||||||
malware
|
malware
|
||||||
|
matthewpi
|
||||||
mcr
|
mcr
|
||||||
memes
|
memes
|
||||||
metarefresh
|
metarefresh
|
||||||
|
|||||||
2
.github/workflows/smoke-tests.yml
vendored
2
.github/workflows/smoke-tests.yml
vendored
@@ -14,10 +14,12 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
test:
|
test:
|
||||||
|
- caddy
|
||||||
- git-clone
|
- git-clone
|
||||||
- git-push
|
- git-push
|
||||||
- healthcheck
|
- healthcheck
|
||||||
- i18n
|
- i18n
|
||||||
|
- unix-socket-xff
|
||||||
runs-on: ubuntu-24.04
|
runs-on: ubuntu-24.04
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
|
|||||||
3
.vscode/extensions.json
vendored
3
.vscode/extensions.json
vendored
@@ -5,6 +5,7 @@
|
|||||||
"golang.go",
|
"golang.go",
|
||||||
"unifiedjs.vscode-mdx",
|
"unifiedjs.vscode-mdx",
|
||||||
"a-h.templ",
|
"a-h.templ",
|
||||||
"redhat.vscode-yaml"
|
"redhat.vscode-yaml",
|
||||||
|
"matthewpi.caddyfile-support"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -166,19 +166,19 @@ func setupListener(network string, address string) (net.Listener, string) {
|
|||||||
|
|
||||||
// additional permission handling for unix sockets
|
// additional permission handling for unix sockets
|
||||||
if network == "unix" {
|
if network == "unix" {
|
||||||
|
slog.Debug("parsing socket mode", "mode_string", *socketMode, "address", address)
|
||||||
|
|
||||||
mode, err := strconv.ParseUint(*socketMode, 8, 0)
|
mode, err := strconv.ParseUint(*socketMode, 8, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
listener.Close()
|
listener.Close()
|
||||||
log.Fatal(fmt.Errorf("could not parse socket mode %s: %w", *socketMode, err))
|
slog.Error("could not parse socket mode", "mode", *socketMode, "err", err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.Chmod(address, os.FileMode(mode))
|
if err := os.Chmod(address, os.FileMode(mode)); err != nil {
|
||||||
if err != nil {
|
// Ignore chmod errors on Unix domain sockets - this is expected behavior
|
||||||
err := listener.Close()
|
// on many systems/containers where socket permissions cannot be changed
|
||||||
if err != nil {
|
slog.Debug("chmod failed on socket (ignoring)", "path", address, "err", err)
|
||||||
log.Printf("failed to close listener: %v", err)
|
|
||||||
}
|
|
||||||
log.Fatal(fmt.Errorf("could not change socket mode: %w", err))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
<!-- This changes the project to: -->
|
<!-- This changes the project to: -->
|
||||||
|
|
||||||
- Expired records are now properly removed from bbolt databases ([#848](https://github.com/TecharoHQ/anubis/pull/848)).
|
- Expired records are now properly removed from bbolt databases ([#848](https://github.com/TecharoHQ/anubis/pull/848)).
|
||||||
|
|
||||||
- Fix hanging on service restart ([#853](https://github.com/TecharoHQ/anubis/issues/853))
|
- Fix hanging on service restart ([#853](https://github.com/TecharoHQ/anubis/issues/853))
|
||||||
@@ -23,8 +22,6 @@ Anubis now supports these new languages:
|
|||||||
|
|
||||||
- [Czech](https://github.com/TecharoHQ/anubis/pull/849)
|
- [Czech](https://github.com/TecharoHQ/anubis/pull/849)
|
||||||
|
|
||||||
Anubis now supports the [`missingHeader`](./admin/configuration/expressions.mdx#missingHeader) to assert the absence of headers in requests.
|
|
||||||
|
|
||||||
## v1.21.0: Minfilia Warde
|
## v1.21.0: Minfilia Warde
|
||||||
|
|
||||||
> Please, be at ease. You are among friends here.
|
> Please, be at ease. You are among friends here.
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ For example, consider this rule:
|
|||||||
|
|
||||||
For this rule, if a request comes in from `8.8.8.8` or `1.1.1.1`, Anubis will deny the request and return an error page.
|
For this rule, if a request comes in from `8.8.8.8` or `1.1.1.1`, Anubis will deny the request and return an error page.
|
||||||
|
|
||||||
### `all` blocks
|
#### `all` blocks
|
||||||
|
|
||||||
An `all` block that contains a list of expressions. If all expressions in the list return `true`, then the action specified in the rule will be taken. If any of the expressions in the list returns `false`, Anubis will move on to the next rule.
|
An `all` block that contains a list of expressions. If all expressions in the list return `true`, then the action specified in the rule will be taken. If any of the expressions in the list returns `false`, Anubis will move on to the next rule.
|
||||||
|
|
||||||
@@ -186,32 +186,8 @@ Also keep in mind that this does not account for other kinds of latency like I/O
|
|||||||
|
|
||||||
Anubis expressions can be augmented with the following functions:
|
Anubis expressions can be augmented with the following functions:
|
||||||
|
|
||||||
### `missingHeader`
|
|
||||||
|
|
||||||
Available in `bot` expressions.
|
|
||||||
|
|
||||||
```ts
|
|
||||||
function missingHeader(headers: Record<string, string>, key: string) bool
|
|
||||||
```
|
|
||||||
|
|
||||||
`missingHeader` returns `true` if the request does not contain a header. This is useful when you are trying to assert behavior such as:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
# Adds weight to old versions of Chrome
|
|
||||||
- name: old-chrome
|
|
||||||
action: WEIGH
|
|
||||||
weight:
|
|
||||||
adjust: 10
|
|
||||||
expression:
|
|
||||||
all:
|
|
||||||
- userAgent.matches("Chrome/[1-9][0-9]?\\.0\\.0\\.0")
|
|
||||||
- missingHeader(headers, "Sec-Ch-Ua")
|
|
||||||
```
|
|
||||||
|
|
||||||
### `randInt`
|
### `randInt`
|
||||||
|
|
||||||
Available in all expressions.
|
|
||||||
|
|
||||||
```ts
|
```ts
|
||||||
function randInt(n: int): int;
|
function randInt(n: int): int;
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import (
|
|||||||
"github.com/google/cel-go/cel"
|
"github.com/google/cel-go/cel"
|
||||||
"github.com/google/cel-go/common/types"
|
"github.com/google/cel-go/common/types"
|
||||||
"github.com/google/cel-go/common/types/ref"
|
"github.com/google/cel-go/common/types/ref"
|
||||||
"github.com/google/cel-go/common/types/traits"
|
|
||||||
"github.com/google/cel-go/ext"
|
"github.com/google/cel-go/ext"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -27,33 +26,6 @@ func BotEnvironment() (*cel.Env, error) {
|
|||||||
cel.Variable("load_1m", cel.DoubleType),
|
cel.Variable("load_1m", cel.DoubleType),
|
||||||
cel.Variable("load_5m", cel.DoubleType),
|
cel.Variable("load_5m", cel.DoubleType),
|
||||||
cel.Variable("load_15m", cel.DoubleType),
|
cel.Variable("load_15m", cel.DoubleType),
|
||||||
|
|
||||||
// Bot-specific functions:
|
|
||||||
cel.Function("missingHeader",
|
|
||||||
cel.Overload("missingHeader_map_string_string_string",
|
|
||||||
[]*cel.Type{cel.MapType(cel.StringType, cel.StringType), cel.StringType},
|
|
||||||
cel.BoolType,
|
|
||||||
cel.BinaryBinding(func(headers, key ref.Val) ref.Val {
|
|
||||||
// Convert headers to a trait that supports Find
|
|
||||||
headersMap, ok := headers.(traits.Indexer)
|
|
||||||
if !ok {
|
|
||||||
return types.ValOrErr(headers, "headers is not a map, but is %T", headers)
|
|
||||||
}
|
|
||||||
|
|
||||||
keyStr, ok := key.(types.String)
|
|
||||||
if !ok {
|
|
||||||
return types.ValOrErr(key, "key is not a string, but is %T", key)
|
|
||||||
}
|
|
||||||
|
|
||||||
val := headersMap.Get(keyStr)
|
|
||||||
// Check if the key is missing by testing for an error
|
|
||||||
if types.IsError(val) {
|
|
||||||
return types.Bool(true) // header is missing
|
|
||||||
}
|
|
||||||
return types.Bool(false) // header is present
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,269 +0,0 @@
|
|||||||
package expressions
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/google/cel-go/common/types"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBotEnvironment(t *testing.T) {
|
|
||||||
env, err := BotEnvironment()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create bot environment: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expression string
|
|
||||||
headers map[string]string
|
|
||||||
expected types.Bool
|
|
||||||
description string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "missing-header",
|
|
||||||
expression: `missingHeader(headers, "Missing-Header")`,
|
|
||||||
headers: map[string]string{
|
|
||||||
"User-Agent": "test-agent",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
expected: types.Bool(true),
|
|
||||||
description: "should return true when header is missing",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "existing-header",
|
|
||||||
expression: `missingHeader(headers, "User-Agent")`,
|
|
||||||
headers: map[string]string{
|
|
||||||
"User-Agent": "test-agent",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
expected: types.Bool(false),
|
|
||||||
description: "should return false when header exists",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "case-sensitive",
|
|
||||||
expression: `missingHeader(headers, "user-agent")`,
|
|
||||||
headers: map[string]string{
|
|
||||||
"User-Agent": "test-agent",
|
|
||||||
},
|
|
||||||
expected: types.Bool(true),
|
|
||||||
description: "should be case-sensitive (user-agent != User-Agent)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty-headers",
|
|
||||||
expression: `missingHeader(headers, "Any-Header")`,
|
|
||||||
headers: map[string]string{},
|
|
||||||
expected: types.Bool(true),
|
|
||||||
description: "should return true for any header when map is empty",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "real-world-sec-ch-ua",
|
|
||||||
expression: `missingHeader(headers, "Sec-Ch-Ua")`,
|
|
||||||
headers: map[string]string{
|
|
||||||
"User-Agent": "curl/7.68.0",
|
|
||||||
"Accept": "*/*",
|
|
||||||
"Host": "example.com",
|
|
||||||
},
|
|
||||||
expected: types.Bool(true),
|
|
||||||
description: "should detect missing browser-specific headers from bots",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "browser-with-sec-ch-ua",
|
|
||||||
expression: `missingHeader(headers, "Sec-Ch-Ua")`,
|
|
||||||
headers: map[string]string{
|
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
|
|
||||||
"Sec-Ch-Ua": `"Chrome"; v="91", "Not A Brand"; v="99"`,
|
|
||||||
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
|
|
||||||
},
|
|
||||||
expected: types.Bool(false),
|
|
||||||
description: "should return false when browser sends Sec-Ch-Ua header",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
prog, err := Compile(env, tt.expression)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, _, err := prog.Eval(map[string]interface{}{
|
|
||||||
"headers": tt.headers,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result != tt.expected {
|
|
||||||
t.Errorf("%s: expected %v, got %v", tt.description, tt.expected, result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
t.Run("function-compilation", func(t *testing.T) {
|
|
||||||
src := `missingHeader(headers, "Test-Header")`
|
|
||||||
_, err := Compile(env, src)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to compile missingHeader expression: %v", err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestThresholdEnvironment(t *testing.T) {
|
|
||||||
env, err := ThresholdEnvironment()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create threshold environment: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expression string
|
|
||||||
variables map[string]interface{}
|
|
||||||
expected types.Bool
|
|
||||||
description string
|
|
||||||
shouldCompile bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "weight-variable-available",
|
|
||||||
expression: `weight > 100`,
|
|
||||||
variables: map[string]interface{}{"weight": 150},
|
|
||||||
expected: types.Bool(true),
|
|
||||||
description: "should support weight variable in expressions",
|
|
||||||
shouldCompile: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "weight-variable-false-case",
|
|
||||||
expression: `weight > 100`,
|
|
||||||
variables: map[string]interface{}{"weight": 50},
|
|
||||||
expected: types.Bool(false),
|
|
||||||
description: "should correctly evaluate weight comparisons",
|
|
||||||
shouldCompile: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missingHeader-not-available",
|
|
||||||
expression: `missingHeader(headers, "Test")`,
|
|
||||||
variables: map[string]interface{}{},
|
|
||||||
expected: types.Bool(false), // not used
|
|
||||||
description: "should not have missingHeader function available",
|
|
||||||
shouldCompile: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
prog, err := Compile(env, tt.expression)
|
|
||||||
|
|
||||||
if !tt.shouldCompile {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("%s: expected compilation to fail but it succeeded", tt.description)
|
|
||||||
}
|
|
||||||
return // Test passed - compilation failed as expected
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
result, _, err := prog.Eval(tt.variables)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result != tt.expected {
|
|
||||||
t.Errorf("%s: expected %v, got %v", tt.description, tt.expected, result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestNewEnvironment(t *testing.T) {
|
|
||||||
env, err := New()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to create new environment: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
expression string
|
|
||||||
variables map[string]interface{}
|
|
||||||
expectBool *bool // nil if we just want to test compilation or non-bool result
|
|
||||||
description string
|
|
||||||
shouldCompile bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "randInt-function-compilation",
|
|
||||||
expression: `randInt(10)`,
|
|
||||||
variables: map[string]interface{}{},
|
|
||||||
expectBool: nil, // Don't check result, just compilation
|
|
||||||
description: "should compile randInt function",
|
|
||||||
shouldCompile: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "randInt-range-validation",
|
|
||||||
expression: `randInt(10) >= 0 && randInt(10) < 10`,
|
|
||||||
variables: map[string]interface{}{},
|
|
||||||
expectBool: boolPtr(true),
|
|
||||||
description: "should return values in correct range",
|
|
||||||
shouldCompile: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "strings-extension-size",
|
|
||||||
expression: `"hello".size() == 5`,
|
|
||||||
variables: map[string]interface{}{},
|
|
||||||
expectBool: boolPtr(true),
|
|
||||||
description: "should support string extension functions",
|
|
||||||
shouldCompile: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "strings-extension-contains",
|
|
||||||
expression: `"hello world".contains("world")`,
|
|
||||||
variables: map[string]interface{}{},
|
|
||||||
expectBool: boolPtr(true),
|
|
||||||
description: "should support string contains function",
|
|
||||||
shouldCompile: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "strings-extension-startsWith",
|
|
||||||
expression: `"hello world".startsWith("hello")`,
|
|
||||||
variables: map[string]interface{}{},
|
|
||||||
expectBool: boolPtr(true),
|
|
||||||
description: "should support string startsWith function",
|
|
||||||
shouldCompile: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
prog, err := Compile(env, tt.expression)
|
|
||||||
|
|
||||||
if !tt.shouldCompile {
|
|
||||||
if err == nil {
|
|
||||||
t.Fatalf("%s: expected compilation to fail but it succeeded", tt.description)
|
|
||||||
}
|
|
||||||
return // Test passed - compilation failed as expected
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// If we only want to test compilation, skip evaluation
|
|
||||||
if tt.expectBool == nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
result, _, err := prog.Eval(tt.variables)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("failed to evaluate expression %q: %v", tt.expression, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if result != types.Bool(*tt.expectBool) {
|
|
||||||
t.Errorf("%s: expected %v, got %v", tt.description, *tt.expectBool, result)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper function to create bool pointers
|
|
||||||
func boolPtr(b bool) *bool {
|
|
||||||
return &b
|
|
||||||
}
|
|
||||||
10
test/anubis_configs/less_paranoid.yaml
Normal file
10
test/anubis_configs/less_paranoid.yaml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
bots:
|
||||||
|
- name: challenge
|
||||||
|
user_agent_regex: Mozilla
|
||||||
|
action: WEIGH
|
||||||
|
weight:
|
||||||
|
adjust: 10
|
||||||
|
|
||||||
|
status_codes:
|
||||||
|
CHALLENGE: 401
|
||||||
|
DENY: 403
|
||||||
@@ -1,12 +1,5 @@
|
|||||||
:80 {
|
caddy.local.cetacean.club {
|
||||||
reverse_proxy http://anubis:3000 {
|
tls internal
|
||||||
header_up X-Real-Ip {remote_host}
|
|
||||||
header_up X-Http-Version {http.request.proto}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:443 {
|
|
||||||
tls /etc/techaro/pki/caddy.local.cetacean.club/cert.pem /etc/techaro/pki/caddy.local.cetacean.club/key.pem
|
|
||||||
|
|
||||||
reverse_proxy http://anubis:3000 {
|
reverse_proxy http://anubis:3000 {
|
||||||
header_up X-Real-Ip {remote_host}
|
header_up X-Real-Ip {remote_host}
|
||||||
|
|||||||
@@ -5,18 +5,16 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- 8080:80
|
- 8080:80
|
||||||
- 8443:443
|
- 8443:443
|
||||||
volumes:
|
|
||||||
- "../pki/caddy.local.cetacean.club:/etc/techaro/pki/caddy.local.cetacean.club/"
|
|
||||||
|
|
||||||
anubis:
|
anubis:
|
||||||
image: ghcr.io/techarohq/anubis:main
|
image: ghcr.io/techarohq/anubis
|
||||||
environment:
|
environment:
|
||||||
BIND: ":3000"
|
BIND: ":3000"
|
||||||
TARGET: http://httpdebug:3000
|
TARGET: http://httpdebug:3000
|
||||||
POLICY_FNAME: /etc/techaro/anubis/less_paranoid.yaml
|
POLICY_FNAME: /cfg/less_paranoid.yaml
|
||||||
|
SLOG_LEVEL: DEBUG
|
||||||
volumes:
|
volumes:
|
||||||
- ../anubis_configs:/etc/techaro/anubis
|
- ../anubis_configs:/cfg
|
||||||
|
|
||||||
httpdebug:
|
httpdebug:
|
||||||
image: ghcr.io/xe/x/httpdebug
|
image: ghcr.io/xe/x/httpdebug
|
||||||
pull_policy: always
|
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
# If the transient local TLS certificate doesn't exist, mint a new one
|
|
||||||
if [ ! -f ../pki/caddy.local.cetacean.club/cert.pem ]; then
|
|
||||||
# Subshell to contain the directory change
|
|
||||||
(
|
|
||||||
cd ../pki \
|
|
||||||
&& mkdir -p caddy.local.cetacean.club \
|
|
||||||
&& \
|
|
||||||
# Try using https://github.com/FiloSottile/mkcert for better DevEx,
|
|
||||||
# but fall back to using https://github.com/jsha/minica in case
|
|
||||||
# you don't have that installed.
|
|
||||||
(
|
|
||||||
mkcert \
|
|
||||||
--cert-file ./caddy.local.cetacean.club/cert.pem \
|
|
||||||
--key-file ./caddy.local.cetacean.club/key.pem caddy.local.cetacean.club \
|
|
||||||
|| go tool minica -domains caddy.local.cetacean.club
|
|
||||||
)
|
|
||||||
)
|
|
||||||
fi
|
|
||||||
|
|
||||||
docker compose up --build
|
|
||||||
27
test/caddy/test.mjs
Normal file
27
test/caddy/test.mjs
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
async function testWithUserAgent(userAgent) {
|
||||||
|
const statusCode =
|
||||||
|
await fetch("https://caddy.local.cetacean.club:8443/reqmeta", {
|
||||||
|
headers: {
|
||||||
|
"User-Agent": userAgent,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then(resp => resp.status);
|
||||||
|
return statusCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const codes = {
|
||||||
|
Mozilla: await testWithUserAgent("Mozilla"),
|
||||||
|
curl: await testWithUserAgent("curl"),
|
||||||
|
}
|
||||||
|
|
||||||
|
const expected = {
|
||||||
|
Mozilla: 401,
|
||||||
|
curl: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log("Mozilla:", codes.Mozilla);
|
||||||
|
console.log("curl: ", codes.curl);
|
||||||
|
|
||||||
|
if (JSON.stringify(codes) !== JSON.stringify(expected)) {
|
||||||
|
throw new Error(`wanted ${JSON.stringify(expected)}, got: ${JSON.stringify(codes)}`);
|
||||||
|
}
|
||||||
17
test/caddy/test.sh
Executable file
17
test/caddy/test.sh
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -x
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source ../lib/lib.sh
|
||||||
|
|
||||||
|
build_anubis_ko
|
||||||
|
|
||||||
|
docker compose up -d --build
|
||||||
|
|
||||||
|
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||||
|
|
||||||
|
sleep 2
|
||||||
|
|
||||||
|
backoff-retry node test.mjs
|
||||||
54
test/lib/lib.sh
Normal file
54
test/lib/lib.sh
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||||||
|
(cd $REPO_ROOT && go install ./utils/cmd/...)
|
||||||
|
|
||||||
|
function cleanup() {
|
||||||
|
pkill -P $$
|
||||||
|
|
||||||
|
if [ -f "docker-compose.yaml" ]; then
|
||||||
|
docker compose down
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
trap cleanup EXIT SIGINT
|
||||||
|
|
||||||
|
function build_anubis_ko() {
|
||||||
|
(
|
||||||
|
cd ../.. &&
|
||||||
|
VERSION=devel ko build \
|
||||||
|
--platform=all \
|
||||||
|
--base-import-paths \
|
||||||
|
--tags="latest" \
|
||||||
|
--image-user=1000 \
|
||||||
|
--image-annotation="" \
|
||||||
|
--image-label="" \
|
||||||
|
./cmd/anubis \
|
||||||
|
--local
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function mint_cert() {
|
||||||
|
if [ "$#" -ne 1 ]; then
|
||||||
|
echo "Usage: mint_cert <domain.name>"
|
||||||
|
fi
|
||||||
|
|
||||||
|
domainName="$1"
|
||||||
|
|
||||||
|
# If the transient local TLS certificate doesn't exist, mint a new one
|
||||||
|
if [ ! -f "../pki/${domainName}/cert.pem" ]; then
|
||||||
|
# Subshell to contain the directory change
|
||||||
|
(
|
||||||
|
cd ../pki &&
|
||||||
|
mkdir -p "${domainName}" &&
|
||||||
|
# Try using https://github.com/FiloSottile/mkcert for better DevEx,
|
||||||
|
# but fall back to using https://github.com/jsha/minica in case
|
||||||
|
# you don't have that installed.
|
||||||
|
(
|
||||||
|
mkcert \
|
||||||
|
--cert-file ./"${domainName}"/cert.pem \
|
||||||
|
--key-file ./"${domainName}"/key.pem \
|
||||||
|
"${domainName}" ||
|
||||||
|
go tool minica -domains "${domainName}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
fi
|
||||||
|
}
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# Remove lingering .sock files, relayd and unixhttpd will do that too but
|
|
||||||
# measure twice, cut once.
|
|
||||||
rm *.sock ||:
|
|
||||||
|
|
||||||
# If the transient local TLS certificate doesn't exist, mint a new one
|
|
||||||
if [ ! -f ../pki/relayd.local.cetacean.club/cert.pem ]; then
|
|
||||||
# Subshell to contain the directory change
|
|
||||||
(
|
|
||||||
cd ../pki \
|
|
||||||
&& mkdir -p relayd.local.cetacean.club \
|
|
||||||
&& \
|
|
||||||
# Try using https://github.com/FiloSottile/mkcert for better DevEx,
|
|
||||||
# but fall back to using https://github.com/jsha/minica in case
|
|
||||||
# you don't have that installed.
|
|
||||||
(
|
|
||||||
mkcert \
|
|
||||||
--cert-file ./relayd.local.cetacean.club/cert.pem \
|
|
||||||
--key-file ./relayd.local.cetacean.club/key.pem relayd.local.cetacean.club \
|
|
||||||
|| go tool minica -domains relayd.local.cetacean.club
|
|
||||||
)
|
|
||||||
)
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Build static assets
|
|
||||||
(cd ../.. && npm ci && npm run assets)
|
|
||||||
|
|
||||||
# Spawn three jobs:
|
|
||||||
|
|
||||||
# HTTP daemon that listens over a unix socket (implicitly ./unixhttpd.sock)
|
|
||||||
go run ../cmd/unixhttpd &
|
|
||||||
|
|
||||||
# A copy of Anubis, specifically for the current Git checkout
|
|
||||||
go tool anubis \
|
|
||||||
--bind=./anubis.sock \
|
|
||||||
--bind-network=unix \
|
|
||||||
--policy-fname=../anubis_configs/aggressive_403.yaml \
|
|
||||||
--target=unix://$(pwd)/unixhttpd.sock &
|
|
||||||
|
|
||||||
# A simple TLS terminator that forwards to Anubis, which will forward to
|
|
||||||
# unixhttpd
|
|
||||||
go run ../cmd/relayd \
|
|
||||||
--proxy-to=unix://./anubis.sock \
|
|
||||||
--cert-dir=../pki/relayd.local.cetacean.club &
|
|
||||||
|
|
||||||
# When you press control c, kill all the child processes to clean things up
|
|
||||||
trap 'echo signal received!; kill $(jobs -p); wait' SIGINT SIGTERM
|
|
||||||
|
|
||||||
echo "open https://relayd.local.cetacean.club:3004/reqmeta"
|
|
||||||
|
|
||||||
# Wait for all child processes to exit
|
|
||||||
wait
|
|
||||||
33
test/unix-socket-xff/test.sh
Executable file
33
test/unix-socket-xff/test.sh
Executable file
@@ -0,0 +1,33 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
source ../lib/lib.sh
|
||||||
|
|
||||||
|
mint_cert "relayd.local.cetacean.club"
|
||||||
|
|
||||||
|
# Build static assets
|
||||||
|
(cd ../.. && npm ci && npm run assets)
|
||||||
|
|
||||||
|
# Spawn three jobs:
|
||||||
|
|
||||||
|
# HTTP daemon that listens over a unix socket (implicitly ./unixhttpd.sock)
|
||||||
|
go run ../cmd/unixhttpd &
|
||||||
|
|
||||||
|
# A copy of Anubis, specifically for the current Git checkout
|
||||||
|
go tool anubis \
|
||||||
|
--bind=./anubis.sock \
|
||||||
|
--bind-network=unix \
|
||||||
|
--socket-mode=0700 \
|
||||||
|
--policy-fname=../anubis_configs/aggressive_403.yaml \
|
||||||
|
--target=unix://$(pwd)/unixhttpd.sock &
|
||||||
|
|
||||||
|
# A simple TLS terminator that forwards to Anubis, which will forward to
|
||||||
|
# unixhttpd
|
||||||
|
go run ../cmd/relayd \
|
||||||
|
--proxy-to=unix://./anubis.sock \
|
||||||
|
--cert-dir=../pki/relayd.local.cetacean.club &
|
||||||
|
|
||||||
|
export NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||||
|
|
||||||
|
backoff-retry node test.mjs
|
||||||
Reference in New Issue
Block a user