mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-22 16:16:41 +00:00
Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8b72ded756 | |||
| 439860443a | |||
| 33bc4669cb | |||
| fa5ceedf23 | |||
| 49dcdbc49e | |||
| 578f248ce1 | |||
| 298fc0b847 | |||
| 8af9845117 | |||
| 3a6e368179 | |||
| 98afcf8c64 | |||
| 982394ca91 | |||
| 66b7b27aef | |||
| cc07be8a9e | |||
| 04f8b6b148 | |||
| dbd64e0f4f | |||
| 3acf8ee387 | |||
| 57260a562a | |||
| e42a328843 | |||
| c4b26e5a75 | |||
| 1342539a41 | |||
| bd6f654e1f | |||
| d2c3a326af | |||
| c2ed62f51d | |||
| 11c4adc6b4 | |||
| edbfd180b8 | |||
| efde4f0dc7 | |||
| 24857f430f | |||
| e0ece7d333 | |||
| 3eab1d873d | |||
| c7b31d0ca9 | |||
| 3154ff5004 | |||
| 5186d7d3ad | |||
| c6d968874d | |||
| 14a8d0c75e | |||
| 0ea13dcee2 | |||
| a2a4cdebd6 | |||
| 168fe79802 | |||
| 865ba0983e | |||
| 27c994d3ce | |||
| 22412d0e22 | |||
| c5ff5f0f26 | |||
| 78fe07a78f | |||
| 04fef9e033 | |||
| fa518e1b8c | |||
| f38210fd84 |
@@ -1,12 +1,12 @@
|
|||||||
<!--
|
<!--
|
||||||
delete me and describe your change here, give enough context for a maintainer to understand what and why
|
delete me and describe your change here, give enough context for a maintainer to understand what and why
|
||||||
|
|
||||||
See https://anubis.techaro.lol/docs/developer/code-quality for more information
|
See https://github.com/TecharoHQ/anubis/blob/main/CONTRIBUTING.md for more information
|
||||||
-->
|
-->
|
||||||
|
|
||||||
Checklist:
|
Checklist:
|
||||||
|
|
||||||
- [ ] Added a description of the changes to the `[Unreleased]` section of docs/docs/CHANGELOG.md
|
- [ ] Added a description of the changes to the `[Unreleased]` section of docs/docs/CHANGELOG.md
|
||||||
- [ ] Added test cases to [the relevant parts of the codebase](https://anubis.techaro.lol/docs/developer/code-quality)
|
- [ ] Added test cases to [the relevant parts of the codebase](https://github.com/TecharoHQ/anubis/blob/main/CONTRIBUTING.md)
|
||||||
- [ ] Ran integration tests `npm run test:integration` (unsupported on Windows, please use WSL)
|
- [ ] Ran integration tests `npm run test:integration` (unsupported on Windows, please use WSL)
|
||||||
- [ ] All of my commits have [verified signatures](https://anubis.techaro.lol/docs/developer/signed-commits)
|
- [ ] All of my commits have [verified signatures](https://anubis.techaro.lol/docs/developer/signed-commits)
|
||||||
|
|||||||
@@ -26,3 +26,15 @@ blocklists
|
|||||||
rififi
|
rififi
|
||||||
prolocation
|
prolocation
|
||||||
Prolocation
|
Prolocation
|
||||||
|
Necron
|
||||||
|
Stargate
|
||||||
|
FFXIV
|
||||||
|
uvensys
|
||||||
|
de
|
||||||
|
resourced
|
||||||
|
envoyproxy
|
||||||
|
unipromos
|
||||||
|
Samsung
|
||||||
|
wenet
|
||||||
|
qwertiko
|
||||||
|
setuplistener
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ databento
|
|||||||
dayjob
|
dayjob
|
||||||
dco
|
dco
|
||||||
DDOS
|
DDOS
|
||||||
|
ddwrt
|
||||||
Debian
|
Debian
|
||||||
debrpm
|
debrpm
|
||||||
decaymap
|
decaymap
|
||||||
@@ -102,6 +103,7 @@ duckduckbot
|
|||||||
eerror
|
eerror
|
||||||
ellenjoe
|
ellenjoe
|
||||||
emacs
|
emacs
|
||||||
|
embe
|
||||||
enbyware
|
enbyware
|
||||||
etld
|
etld
|
||||||
everyones
|
everyones
|
||||||
@@ -220,7 +222,6 @@ LLU
|
|||||||
loadbalancer
|
loadbalancer
|
||||||
lol
|
lol
|
||||||
lominsa
|
lominsa
|
||||||
maintainership
|
|
||||||
malware
|
malware
|
||||||
mcr
|
mcr
|
||||||
memes
|
memes
|
||||||
@@ -329,12 +330,12 @@ Spambot
|
|||||||
spammer
|
spammer
|
||||||
sparkline
|
sparkline
|
||||||
spyderbot
|
spyderbot
|
||||||
|
srcip
|
||||||
srv
|
srv
|
||||||
stackoverflow
|
stackoverflow
|
||||||
startprecmd
|
startprecmd
|
||||||
stoppostcmd
|
stoppostcmd
|
||||||
storetest
|
storetest
|
||||||
srcip
|
|
||||||
strcmp
|
strcmp
|
||||||
subgrid
|
subgrid
|
||||||
subr
|
subr
|
||||||
|
|||||||
@@ -22,12 +22,12 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "1.25.7"
|
||||||
|
|
||||||
- name: install node deps
|
- name: install node deps
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -26,18 +26,18 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/${{ github.repository }}
|
images: ghcr.io/${{ github.repository }}
|
||||||
|
|
||||||
|
|||||||
@@ -36,17 +36,17 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
||||||
|
|
||||||
- name: Log into registry
|
- name: Log into registry
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
@@ -54,7 +54,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
with:
|
with:
|
||||||
images: ${{ env.IMAGE }}
|
images: ${{ env.IMAGE }}
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ jobs:
|
|||||||
SLOG_LEVEL: debug
|
SLOG_LEVEL: debug
|
||||||
|
|
||||||
- name: Generate artifact attestation
|
- name: Generate artifact attestation
|
||||||
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
|
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
|
||||||
with:
|
with:
|
||||||
subject-name: ${{ env.IMAGE }}
|
subject-name: ${{ env.IMAGE }}
|
||||||
subject-digest: ${{ steps.build.outputs.digest }}
|
subject-digest: ${{ steps.build.outputs.digest }}
|
||||||
|
|||||||
@@ -22,10 +22,10 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
|
|
||||||
- name: Log into registry
|
- name: Log into registry
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: techarohq
|
username: techarohq
|
||||||
@@ -33,7 +33,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/techarohq/anubis/docs
|
images: ghcr.io/techarohq/anubis/docs
|
||||||
tags: |
|
tags: |
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||||
with:
|
with:
|
||||||
context: ./docs
|
context: ./docs
|
||||||
cache-to: type=gha
|
cache-to: type=gha
|
||||||
@@ -53,14 +53,14 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
|
|
||||||
- name: Apply k8s manifests to limsa lominsa
|
- name: Apply k8s manifests to limsa lominsa
|
||||||
uses: actions-hub/kubectl@3ece3793e7a9fe94effe257d03ac834c815ea87d # v1.35.1
|
uses: actions-hub/kubectl@934aaa4354bbbc3d2176ae8d7cae92d515032dff # v1.35.3
|
||||||
env:
|
env:
|
||||||
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
||||||
with:
|
with:
|
||||||
args: apply -k docs/manifest
|
args: apply -k docs/manifest
|
||||||
|
|
||||||
- name: Apply k8s manifests to limsa lominsa
|
- name: Apply k8s manifests to limsa lominsa
|
||||||
uses: actions-hub/kubectl@3ece3793e7a9fe94effe257d03ac834c815ea87d # v1.35.1
|
uses: actions-hub/kubectl@934aaa4354bbbc3d2176ae8d7cae92d515032dff # v1.35.3
|
||||||
env:
|
env:
|
||||||
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -18,11 +18,11 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
|
|
||||||
- name: Docker meta
|
- name: Docker meta
|
||||||
id: meta
|
id: meta
|
||||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6.0.0
|
||||||
with:
|
with:
|
||||||
images: ghcr.io/techarohq/anubis/docs
|
images: ghcr.io/techarohq/anubis/docs
|
||||||
tags: |
|
tags: |
|
||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
|
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||||
with:
|
with:
|
||||||
context: ./docs
|
context: ./docs
|
||||||
cache-to: type=gha
|
cache-to: type=gha
|
||||||
|
|||||||
@@ -17,9 +17,9 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: Check go.mod and go.sum in main directory
|
- name: Check go.mod and go.sum in main directory
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -24,15 +24,15 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
|
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
@@ -55,10 +55,10 @@ jobs:
|
|||||||
run: npm run test
|
run: npm run test
|
||||||
|
|
||||||
- name: Lint with staticcheck
|
- name: Lint with staticcheck
|
||||||
uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1.4.0
|
uses: dominikh/staticcheck-action@9716614d4101e79b4340dd97b10e54d68234e431 # v1.4.1
|
||||||
with:
|
with:
|
||||||
version: "latest"
|
version: "latest"
|
||||||
|
|
||||||
- name: Govulncheck
|
- name: Govulncheck
|
||||||
run: |
|
run: |
|
||||||
go tool govulncheck ./...
|
go tool govulncheck ./... ||:
|
||||||
|
|||||||
@@ -25,12 +25,12 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: install node deps
|
- name: install node deps
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -26,12 +26,12 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: install node deps
|
- name: install node deps
|
||||||
run: |
|
run: |
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
go tool yeet
|
go tool yeet
|
||||||
|
|
||||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: packages
|
name: packages
|
||||||
path: var/*
|
path: var/*
|
||||||
|
|||||||
@@ -34,12 +34,12 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6.2.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
||||||
|
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV
|
run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: ${{ env.ARTIFACT_NAME }}
|
name: ${{ env.ARTIFACT_NAME }}
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- name: check-spelling
|
- name: check-spelling
|
||||||
id: spelling
|
id: spelling
|
||||||
uses: check-spelling/check-spelling@c635c2f3f714eec2fcf27b643a1919b9a811ef2e # v0.0.25
|
uses: check-spelling/check-spelling@cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c # v0.0.26
|
||||||
with:
|
with:
|
||||||
suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }}
|
suppress_push_for_open_pull_request: ${{ github.actor != 'dependabot[bot]' && 1 }}
|
||||||
checkout: true
|
checkout: true
|
||||||
|
|||||||
@@ -24,13 +24,13 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
- name: Log into registry
|
- name: Log into registry
|
||||||
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
||||||
with:
|
with:
|
||||||
registry: ghcr.io
|
registry: ghcr.io
|
||||||
username: ${{ github.repository_owner }}
|
username: ${{ github.repository_owner }}
|
||||||
password: ${{ secrets.GITHUB_TOKEN }}
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
- name: Set up Docker Buildx
|
- name: Set up Docker Buildx
|
||||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
run: |
|
run: |
|
||||||
cd ./test/ssh-ci
|
cd ./test/ssh-ci
|
||||||
|
|||||||
@@ -12,14 +12,15 @@ permissions:
|
|||||||
jobs:
|
jobs:
|
||||||
ssh:
|
ssh:
|
||||||
if: github.repository == 'TecharoHQ/anubis'
|
if: github.repository == 'TecharoHQ/anubis'
|
||||||
runs-on: alrest-techarohq
|
#runs-on: alrest-techarohq
|
||||||
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
host:
|
host:
|
||||||
- riscv64
|
- riscv64
|
||||||
- ppc64le
|
- ppc64le
|
||||||
- aarch64-4k
|
#- aarch64-4k
|
||||||
- aarch64-16k
|
#- aarch64-16k
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout code
|
- name: Checkout code
|
||||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||||
@@ -29,15 +30,15 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install CI target SSH key
|
- name: Install CI target SSH key
|
||||||
uses: shimataro/ssh-key-action@d4fffb50872869abe2d9a9098a6d9c5aa7d16be4 # v2.7.0
|
uses: shimataro/ssh-key-action@87a8f067114a8ce263df83e9ed5c849953548bc3 # v2.8.1
|
||||||
with:
|
with:
|
||||||
key: ${{ secrets.CI_SSH_KEY }}
|
key: ${{ secrets.CI_SSH_KEY }}
|
||||||
name: id_rsa
|
name: id_rsa
|
||||||
known_hosts: ${{ secrets.CI_SSH_KNOWN_HOSTS }}
|
known_hosts: ${{ secrets.CI_SSH_KNOWN_HOSTS }}
|
||||||
|
|
||||||
- uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
with:
|
with:
|
||||||
go-version: "1.25.4"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: Run CI
|
- name: Run CI
|
||||||
run: go run ./utils/cmd/backoff-retry bash test/ssh-ci/rigging.sh ${{ matrix.host }}
|
run: go run ./utils/cmd/backoff-retry bash test/ssh-ci/rigging.sh ${{ matrix.host }}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: astral-sh/setup-uv@eac588ad8def6316056a12d4907a9d4d84ff7a3b # v7.3.0
|
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
||||||
|
|
||||||
- name: Run zizmor 🌈
|
- name: Run zizmor 🌈
|
||||||
run: uvx zizmor --format sarif . > results.sarif
|
run: uvx zizmor --format sarif . > results.sarif
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
category: zizmor
|
category: zizmor
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ build: assets
|
|||||||
lint: assets
|
lint: assets
|
||||||
$(GO) vet ./...
|
$(GO) vet ./...
|
||||||
$(GO) tool staticcheck ./...
|
$(GO) tool staticcheck ./...
|
||||||
$(GO) tool govulncheck ./...
|
|
||||||
|
|
||||||
prebaked-build:
|
prebaked-build:
|
||||||
$(GO) build -o ./var/anubis -ldflags "-X 'github.com/TecharoHQ/anubis.Version=$(VERSION)'" ./cmd/anubis
|
$(GO) build -o ./var/anubis -ldflags "-X 'github.com/TecharoHQ/anubis.Version=$(VERSION)'" ./cmd/anubis
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ Anubis is brought to you by sponsors and donors like:
|
|||||||
|
|
||||||
### Gold Tier
|
### Gold Tier
|
||||||
|
|
||||||
|
<a href="https://www.unipromos.com/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="./docs/static/img/sponsors/unipromos.webp" alt="Unipromos" height="64" />
|
||||||
|
</a>
|
||||||
|
<a href="https://uvensys.de/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="./docs/static/img/sponsors/uvensys.webp" alt="Uvensys" height="64">
|
||||||
|
</a>
|
||||||
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
<img src="./docs/static/img/sponsors/distrust-logo.webp" alt="Distrust" height="64">
|
<img src="./docs/static/img/sponsors/distrust-logo.webp" alt="Distrust" height="64">
|
||||||
</a>
|
</a>
|
||||||
@@ -67,6 +73,15 @@ Anubis is brought to you by sponsors and donors like:
|
|||||||
<a href="https://www.anexia.com/">
|
<a href="https://www.anexia.com/">
|
||||||
<img src="./docs/static/img/sponsors/anexia-cloudsolutions-logo.webp" alt="ANEXIA Cloud Solutions" height="64">
|
<img src="./docs/static/img/sponsors/anexia-cloudsolutions-logo.webp" alt="ANEXIA Cloud Solutions" height="64">
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://dd-wrt.com/">
|
||||||
|
<img src="./docs/static/img/sponsors/ddwrt-logo.webp" alt="embeDD GmbH" height="64">
|
||||||
|
</a>
|
||||||
|
<a href="https://www.qwertiko.de?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="./docs/static/img/sponsors/qwertiko-logo.webp" alt="Qwertiko" height="64">
|
||||||
|
</a>
|
||||||
|
<a href="https://wenet.pl/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="./docs/static/img/sponsors/wenet-logo.webp" alt="Wenet" height="64">
|
||||||
|
</a>
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|||||||
+29
-127
@@ -21,7 +21,6 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"syscall"
|
"syscall"
|
||||||
@@ -32,12 +31,12 @@ import (
|
|||||||
"github.com/TecharoHQ/anubis/internal"
|
"github.com/TecharoHQ/anubis/internal"
|
||||||
libanubis "github.com/TecharoHQ/anubis/lib"
|
libanubis "github.com/TecharoHQ/anubis/lib"
|
||||||
"github.com/TecharoHQ/anubis/lib/config"
|
"github.com/TecharoHQ/anubis/lib/config"
|
||||||
|
"github.com/TecharoHQ/anubis/lib/metrics"
|
||||||
botPolicy "github.com/TecharoHQ/anubis/lib/policy"
|
botPolicy "github.com/TecharoHQ/anubis/lib/policy"
|
||||||
"github.com/TecharoHQ/anubis/lib/thoth"
|
"github.com/TecharoHQ/anubis/lib/thoth"
|
||||||
"github.com/TecharoHQ/anubis/web"
|
"github.com/TecharoHQ/anubis/web"
|
||||||
"github.com/facebookgo/flagenv"
|
"github.com/facebookgo/flagenv"
|
||||||
_ "github.com/joho/godotenv/autoload"
|
_ "github.com/joho/godotenv/autoload"
|
||||||
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
||||||
healthv1 "google.golang.org/grpc/health/grpc_health_v1"
|
healthv1 "google.golang.org/grpc/health/grpc_health_v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -118,33 +117,6 @@ func doHealthCheck() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// parseBindNetFromAddr determine bind network and address based on the given network and address.
|
|
||||||
func parseBindNetFromAddr(address string) (string, string) {
|
|
||||||
defaultScheme := "http://"
|
|
||||||
if !strings.Contains(address, "://") {
|
|
||||||
if strings.HasPrefix(address, ":") {
|
|
||||||
address = defaultScheme + "localhost" + address
|
|
||||||
} else {
|
|
||||||
address = defaultScheme + address
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bindUri, err := url.Parse(address)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(fmt.Errorf("failed to parse bind URL: %w", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
switch bindUri.Scheme {
|
|
||||||
case "unix":
|
|
||||||
return "unix", bindUri.Path
|
|
||||||
case "tcp", "http", "https":
|
|
||||||
return "tcp", bindUri.Host
|
|
||||||
default:
|
|
||||||
log.Fatal(fmt.Errorf("unsupported network scheme %s in address %s", bindUri.Scheme, address))
|
|
||||||
}
|
|
||||||
return "", address
|
|
||||||
}
|
|
||||||
|
|
||||||
func parseSameSite(s string) http.SameSite {
|
func parseSameSite(s string) http.SameSite {
|
||||||
switch strings.ToLower(s) {
|
switch strings.ToLower(s) {
|
||||||
case "none":
|
case "none":
|
||||||
@@ -161,53 +133,6 @@ func parseSameSite(s string) http.SameSite {
|
|||||||
return http.SameSiteDefaultMode
|
return http.SameSiteDefaultMode
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupListener(network string, address string) (net.Listener, string) {
|
|
||||||
formattedAddress := ""
|
|
||||||
|
|
||||||
if network == "" {
|
|
||||||
// keep compatibility
|
|
||||||
network, address = parseBindNetFromAddr(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch network {
|
|
||||||
case "unix":
|
|
||||||
formattedAddress = "unix:" + address
|
|
||||||
case "tcp":
|
|
||||||
if strings.HasPrefix(address, ":") { // assume it's just a port e.g. :4259
|
|
||||||
formattedAddress = "http://localhost" + address
|
|
||||||
} else {
|
|
||||||
formattedAddress = "http://" + address
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
formattedAddress = fmt.Sprintf(`(%s) %s`, network, address)
|
|
||||||
}
|
|
||||||
|
|
||||||
listener, err := net.Listen(network, address)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(fmt.Errorf("failed to bind to %s: %w", formattedAddress, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// additional permission handling for unix sockets
|
|
||||||
if network == "unix" {
|
|
||||||
mode, err := strconv.ParseUint(*socketMode, 8, 0)
|
|
||||||
if err != nil {
|
|
||||||
listener.Close()
|
|
||||||
log.Fatal(fmt.Errorf("could not parse socket mode %s: %w", *socketMode, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
err = os.Chmod(address, os.FileMode(mode))
|
|
||||||
if err != nil {
|
|
||||||
err := listener.Close()
|
|
||||||
if err != nil {
|
|
||||||
log.Printf("failed to close listener: %v", err)
|
|
||||||
}
|
|
||||||
log.Fatal(fmt.Errorf("could not change socket mode: %w", err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return listener, formattedAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeReverseProxy(target string, targetSNI string, targetHost string, insecureSkipVerify bool, targetDisableKeepAlive bool) (http.Handler, error) {
|
func makeReverseProxy(target string, targetSNI string, targetHost string, insecureSkipVerify bool, targetDisableKeepAlive bool) (http.Handler, error) {
|
||||||
targetUri, err := url.Parse(target)
|
targetUri, err := url.Parse(target)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -303,11 +228,6 @@ func main() {
|
|||||||
|
|
||||||
wg := new(sync.WaitGroup)
|
wg := new(sync.WaitGroup)
|
||||||
|
|
||||||
if *metricsBind != "" {
|
|
||||||
wg.Add(1)
|
|
||||||
go metricsServer(ctx, *lg.With("subsystem", "metrics"), wg.Done)
|
|
||||||
}
|
|
||||||
|
|
||||||
var rp http.Handler
|
var rp http.Handler
|
||||||
// when using anubis via Systemd and environment variables, then it is not possible to set targe to an empty string but only to space
|
// when using anubis via Systemd and environment variables, then it is not possible to set targe to an empty string but only to space
|
||||||
if strings.TrimSpace(*target) != "" {
|
if strings.TrimSpace(*target) != "" {
|
||||||
@@ -347,6 +267,26 @@ func main() {
|
|||||||
lg.Debug("swapped to new logger")
|
lg.Debug("swapped to new logger")
|
||||||
slog.SetDefault(lg)
|
slog.SetDefault(lg)
|
||||||
|
|
||||||
|
if *metricsBind != "" || policy.Metrics != nil {
|
||||||
|
wg.Add(1)
|
||||||
|
|
||||||
|
ms := &metrics.Server{
|
||||||
|
Config: policy.Metrics,
|
||||||
|
Log: lg,
|
||||||
|
}
|
||||||
|
|
||||||
|
if policy.Metrics == nil {
|
||||||
|
lg.Debug("migrating flags to metrics config", "bind", *metricsBind, "network", *metricsBindNetwork, "socket-mode", *socketMode)
|
||||||
|
ms.Config = &config.Metrics{
|
||||||
|
Bind: *metricsBind,
|
||||||
|
Network: *metricsBindNetwork,
|
||||||
|
SocketMode: *socketMode,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
go ms.Run(ctx, wg.Done)
|
||||||
|
}
|
||||||
|
|
||||||
// Warn if persistent storage is used without a configured signing key
|
// Warn if persistent storage is used without a configured signing key
|
||||||
if policy.Store.IsPersistent() {
|
if policy.Store.IsPersistent() {
|
||||||
if *hs512Secret == "" && *ed25519PrivateKeyHex == "" && *ed25519PrivateKeyHexFile == "" {
|
if *hs512Secret == "" && *ed25519PrivateKeyHex == "" && *ed25519PrivateKeyHexFile == "" {
|
||||||
@@ -418,8 +358,8 @@ func main() {
|
|||||||
|
|
||||||
var redirectDomainsList []string
|
var redirectDomainsList []string
|
||||||
if *redirectDomains != "" {
|
if *redirectDomains != "" {
|
||||||
domains := strings.Split(*redirectDomains, ",")
|
domains := strings.SplitSeq(*redirectDomains, ",")
|
||||||
for _, domain := range domains {
|
for domain := range domains {
|
||||||
_, err = url.Parse(domain)
|
_, err = url.Parse(domain)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("cannot parse redirect-domain %q: %s", domain, err.Error())
|
log.Fatalf("cannot parse redirect-domain %q: %s", domain, err.Error())
|
||||||
@@ -427,7 +367,7 @@ func main() {
|
|||||||
redirectDomainsList = append(redirectDomainsList, strings.TrimSpace(domain))
|
redirectDomainsList = append(redirectDomainsList, strings.TrimSpace(domain))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
lg.Warn("REDIRECT_DOMAINS is not set, Anubis will only redirect to the same domain a request is coming from, see https://anubis.techaro.lol/docs/admin/configuration/redirect-domains")
|
lg.Warn("REDIRECT_DOMAINS is not set, Anubis will redirect to any domain, see https://anubis.techaro.lol/docs/admin/configuration/redirect-domains")
|
||||||
}
|
}
|
||||||
|
|
||||||
anubis.CookieName = *cookiePrefix + "-auth"
|
anubis.CookieName = *cookiePrefix + "-auth"
|
||||||
@@ -483,7 +423,11 @@ func main() {
|
|||||||
h = internal.JA4H(h)
|
h = internal.JA4H(h)
|
||||||
|
|
||||||
srv := http.Server{Handler: h, ErrorLog: internal.GetFilteredHTTPLogger()}
|
srv := http.Server{Handler: h, ErrorLog: internal.GetFilteredHTTPLogger()}
|
||||||
listener, listenerUrl := setupListener(*bindNetwork, *bind)
|
listener, listenerUrl, err := internal.SetupListener(*bindNetwork, *bind, *socketMode)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("SetupListener(%q, %q, %q): %v", *bindNetwork, *bind, *socketMode, err)
|
||||||
|
}
|
||||||
|
|
||||||
lg.Info(
|
lg.Info(
|
||||||
"listening",
|
"listening",
|
||||||
"url", listenerUrl,
|
"url", listenerUrl,
|
||||||
@@ -518,48 +462,6 @@ func main() {
|
|||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func metricsServer(ctx context.Context, lg slog.Logger, done func()) {
|
|
||||||
defer done()
|
|
||||||
|
|
||||||
mux := http.NewServeMux()
|
|
||||||
mux.Handle("/metrics", promhttp.Handler())
|
|
||||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
st, ok := internal.GetHealth("anubis")
|
|
||||||
if !ok {
|
|
||||||
slog.Error("health service anubis does not exist, file a bug")
|
|
||||||
}
|
|
||||||
|
|
||||||
switch st {
|
|
||||||
case healthv1.HealthCheckResponse_NOT_SERVING:
|
|
||||||
http.Error(w, "NOT OK", http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
case healthv1.HealthCheckResponse_SERVING:
|
|
||||||
fmt.Fprintln(w, "OK")
|
|
||||||
return
|
|
||||||
default:
|
|
||||||
http.Error(w, "UNKNOWN", http.StatusFailedDependency)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
srv := http.Server{Handler: mux, ErrorLog: internal.GetFilteredHTTPLogger()}
|
|
||||||
listener, metricsUrl := setupListener(*metricsBindNetwork, *metricsBind)
|
|
||||||
lg.Debug("listening for metrics", "url", metricsUrl)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
<-ctx.Done()
|
|
||||||
c, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
||||||
defer cancel()
|
|
||||||
if err := srv.Shutdown(c); err != nil {
|
|
||||||
log.Printf("cannot shut down: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if err := srv.Serve(listener); !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func extractEmbedFS(fsys embed.FS, root string, destDir string) error {
|
func extractEmbedFS(fsys embed.FS, root string, destDir string) error {
|
||||||
return fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
|
return fs.WalkDir(fsys, root, func(path string, d fs.DirEntry, err error) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import (
|
|||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis/lib/config"
|
"github.com/TecharoHQ/anubis/lib/config"
|
||||||
@@ -210,11 +211,8 @@ func parseRobotsTxt(input io.Reader) ([]RobotsRule, error) {
|
|||||||
|
|
||||||
// Mark blacklisted user agents (those with "Disallow: /")
|
// Mark blacklisted user agents (those with "Disallow: /")
|
||||||
for i := range rules {
|
for i := range rules {
|
||||||
for _, disallow := range rules[i].Disallows {
|
if slices.Contains(rules[i].Disallows, "/") {
|
||||||
if disallow == "/" {
|
|
||||||
rules[i].IsBlacklist = true
|
rules[i].IsBlacklist = true
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -158,8 +158,8 @@ func TestDataFileConversion(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if strings.ToLower(*outputFormat) == "yaml" {
|
if strings.ToLower(*outputFormat) == "yaml" {
|
||||||
var actualData []interface{}
|
var actualData []any
|
||||||
var expectedData []interface{}
|
var expectedData []any
|
||||||
|
|
||||||
err = yaml.Unmarshal(actualOutput, &actualData)
|
err = yaml.Unmarshal(actualOutput, &actualData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -178,8 +178,8 @@ func TestDataFileConversion(t *testing.T) {
|
|||||||
t.Errorf("Output mismatch for %s\nExpected:\n%s\n\nActual:\n%s", tc.name, expectedStr, actualStr)
|
t.Errorf("Output mismatch for %s\nExpected:\n%s\n\nActual:\n%s", tc.name, expectedStr, actualStr)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var actualData []interface{}
|
var actualData []any
|
||||||
var expectedData []interface{}
|
var expectedData []any
|
||||||
|
|
||||||
err = json.Unmarshal(actualOutput, &actualData)
|
err = json.Unmarshal(actualOutput, &actualData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -419,6 +419,6 @@ Disallow: /`
|
|||||||
|
|
||||||
// compareData performs a deep comparison of two data structures,
|
// compareData performs a deep comparison of two data structures,
|
||||||
// ignoring differences that are semantically equivalent in YAML/JSON
|
// ignoring differences that are semantically equivalent in YAML/JSON
|
||||||
func compareData(actual, expected interface{}) bool {
|
func compareData(actual, expected any) bool {
|
||||||
return reflect.DeepEqual(actual, expected)
|
return reflect.DeepEqual(actual, expected)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -166,6 +166,15 @@ status_codes:
|
|||||||
CHALLENGE: 200
|
CHALLENGE: 200
|
||||||
DENY: 200
|
DENY: 200
|
||||||
|
|
||||||
|
# # Configuration for the metrics server. See the docs for more information:
|
||||||
|
# #
|
||||||
|
# # https://anubis.techaro.lol/docs/admin/policies#metrics-server
|
||||||
|
# #
|
||||||
|
# # This is commented out by default so that command line flags take precedence.
|
||||||
|
# metrics:
|
||||||
|
# bind: ":9090"
|
||||||
|
# network: "tcp"
|
||||||
|
|
||||||
# Anubis can store temporary data in one of a few backends. See the storage
|
# Anubis can store temporary data in one of a few backends. See the storage
|
||||||
# backends section of the docs for more information:
|
# backends section of the docs for more information:
|
||||||
#
|
#
|
||||||
|
|||||||
@@ -8,4 +8,5 @@
|
|||||||
- import: (data)/crawlers/marginalia.yaml
|
- import: (data)/crawlers/marginalia.yaml
|
||||||
- import: (data)/crawlers/mojeekbot.yaml
|
- import: (data)/crawlers/mojeekbot.yaml
|
||||||
- import: (data)/crawlers/commoncrawl.yaml
|
- import: (data)/crawlers/commoncrawl.yaml
|
||||||
|
- import: (data)/crawlers/wikimedia-citoid.yaml
|
||||||
- import: (data)/crawlers/yandexbot.yaml
|
- import: (data)/crawlers/yandexbot.yaml
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Wikimedia Foundation citation services
|
||||||
|
# https://www.mediawiki.org/wiki/Citoid
|
||||||
|
|
||||||
|
- name: wikimedia-citoid
|
||||||
|
user_agent_regex: "Citoid/WMF"
|
||||||
|
action: ALLOW
|
||||||
|
remote_addresses: [
|
||||||
|
"208.80.152.0/22",
|
||||||
|
"2620:0:860::/46",
|
||||||
|
]
|
||||||
|
|
||||||
|
- name: wikimedia-zotero-translation-server
|
||||||
|
user_agent_regex: "ZoteroTranslationServer/WMF"
|
||||||
|
action: ALLOW
|
||||||
|
remote_addresses: [
|
||||||
|
"208.80.152.0/22",
|
||||||
|
"2620:0:860::/46",
|
||||||
|
]
|
||||||
@@ -13,6 +13,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
<!-- This changes the project to: -->
|
<!-- This changes the project to: -->
|
||||||
|
|
||||||
|
- Move metrics server configuration to [the policy file](./admin/policies.mdx#metrics-server).
|
||||||
|
- Expose [pprof endpoints](https://pkg.go.dev/net/http/pprof) on the metrics listener to enable profiling Anubis in production.
|
||||||
|
- fix: prevent nil pointer panic in challenge validation when threshold rules match during PassChallenge (#1463)
|
||||||
|
- Instruct reverse proxies to not cache error pages.
|
||||||
|
- Fixed mixed tab/space indentation in Caddy documentation code block
|
||||||
|
- Improve error messages and fix broken REDIRECT_DOMAINS link in docs ([#1193](https://github.com/TecharoHQ/anubis/issues/1193))
|
||||||
|
- Add Bulgarian locale ([#1394](https://github.com/TecharoHQ/anubis/pull/1394))
|
||||||
|
- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465)).
|
||||||
|
|
||||||
## v1.25.0: Necron
|
## v1.25.0: Necron
|
||||||
|
|
||||||
Hey all,
|
Hey all,
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ This simply enables SSL offloading, sets some useful and required headers and ro
|
|||||||
|
|
||||||
Due to the fact that HAProxy can decode JWT, we are able to verify the Anubis token directly in HAProxy and route the traffic to the specific backends ourselves.
|
Due to the fact that HAProxy can decode JWT, we are able to verify the Anubis token directly in HAProxy and route the traffic to the specific backends ourselves.
|
||||||
|
|
||||||
|
Mind that rule logic to allow Git HTTP and other legit bot traffic to bypass is delegated from Anubis to HAProxy then. If required, you should implement any whitelisting in HAProxy using `acl_anubis_ignore` yourself.
|
||||||
|
|
||||||
In this example are three applications behind one HAProxy frontend. Only App1 and App2 are secured via Anubis; App3 is open for everyone. The path `/excluded/path` can also be accessed by anyone.
|
In this example are three applications behind one HAProxy frontend. Only App1 and App2 are secured via Anubis; App3 is open for everyone. The path `/excluded/path` can also be accessed by anyone.
|
||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
|
|||||||
@@ -130,3 +130,52 @@ Then point your Ingress to the Anubis port:
|
|||||||
# diff-add
|
# diff-add
|
||||||
name: anubis
|
name: anubis
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Envoy Gateway
|
||||||
|
|
||||||
|
If you are using envoy-gateway, the `X-Real-Ip` header is not set by default, but Anubis does require it. You can resolve this by adding the header, either on the specific `HTTPRoute` where Anubis is listening, or on the `ClientTrafficPolicy` to apply it to any number of Gateways:
|
||||||
|
|
||||||
|
HTTPRoute:
|
||||||
|
```yaml
|
||||||
|
apiVersion: gateway.networking.k8s.io/v1
|
||||||
|
kind: HTTPRoute
|
||||||
|
metadata:
|
||||||
|
name: app-route
|
||||||
|
spec:
|
||||||
|
hostnames: ["app.domain.tld"]
|
||||||
|
parentRefs:
|
||||||
|
- name: envoy-external
|
||||||
|
namespace: network
|
||||||
|
sectionName: https
|
||||||
|
rules:
|
||||||
|
- backendRefs:
|
||||||
|
- identifier: *app
|
||||||
|
port: anubis
|
||||||
|
filters:
|
||||||
|
- type: RequestHeaderModifier
|
||||||
|
requestHeaderModifier:
|
||||||
|
set:
|
||||||
|
- name: X-Real-Ip
|
||||||
|
value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
|
||||||
|
```
|
||||||
|
|
||||||
|
Applying to any number of Gateways:
|
||||||
|
```yaml
|
||||||
|
apiVersion: gateway.envoyproxy.io/v1alpha1
|
||||||
|
kind: ClientTrafficPolicy
|
||||||
|
metadata:
|
||||||
|
name: envoy
|
||||||
|
spec:
|
||||||
|
headers:
|
||||||
|
earlyRequestHeaders:
|
||||||
|
set:
|
||||||
|
- name: X-Real-Ip
|
||||||
|
value: "%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%"
|
||||||
|
clientIPDetection:
|
||||||
|
xForwardedFor:
|
||||||
|
trustedCIDRs:
|
||||||
|
- 10.96.0.0/16 # Cluster pod CIDR
|
||||||
|
targetSelectors: # These will apply to all Gateways
|
||||||
|
- group: gateway.networking.k8s.io
|
||||||
|
kind: Gateway
|
||||||
|
```
|
||||||
|
|||||||
@@ -87,15 +87,15 @@ Anubis uses these environment variables for configuration:
|
|||||||
| `ED25519_PRIVATE_KEY_HEX_FILE` | unset | Path to a file containing the hex-encoded ed25519 private key. Only one of this or its sister option may be set. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. |
|
| `ED25519_PRIVATE_KEY_HEX_FILE` | unset | Path to a file containing the hex-encoded ed25519 private key. Only one of this or its sister option may be set. **Required when using persistent storage backends** (like bbolt) to ensure challenges survive service restarts. When running multiple instances on the same base domain, the key must be the same across all instances. |
|
||||||
| `ERROR_TITLE` | unset | <EO /> If set, override the translation stack to show a custom title for error pages such as "Something went wrong!". See [Customizing messages](./botstopper.mdx#customizing-messages) for more details. |
|
| `ERROR_TITLE` | unset | <EO /> If set, override the translation stack to show a custom title for error pages such as "Something went wrong!". See [Customizing messages](./botstopper.mdx#customizing-messages) for more details. |
|
||||||
| `JWT_RESTRICTION_HEADER` | `X-Real-IP` | If set, the JWT is only valid if the current value of this header matches the value when the JWT was created. You can use it e.g. to restrict a JWT to the source IP of the user using `X-Real-IP`. |
|
| `JWT_RESTRICTION_HEADER` | `X-Real-IP` | If set, the JWT is only valid if the current value of this header matches the value when the JWT was created. You can use it e.g. to restrict a JWT to the source IP of the user using `X-Real-IP`. |
|
||||||
| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. |
|
| `METRICS_BIND` | `:9090` | The legacy configuration value for the network address that Anubis serves Prometheus metrics on. Please migrate this to [the policy file](./policies.mdx#metrics-server) as soon as possible. |
|
||||||
| `METRICS_BIND_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. |
|
| `METRICS_BIND_NETWORK` | `tcp` | The legacy configuration value for the address family that Anubis serves Prometheus metrics on. Please migrate this to [the policy file](./policies.mdx#metrics-server) as soon as possible. |
|
||||||
| `OG_EXPIRY_TIME` | `24h` | The expiration time for the Open Graph tag cache. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
| `OG_EXPIRY_TIME` | `24h` | The expiration time for the Open Graph tag cache. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
||||||
| `OG_PASSTHROUGH` | `false` | If set to `true`, Anubis will enable Open Graph tag passthrough. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
| `OG_PASSTHROUGH` | `false` | If set to `true`, Anubis will enable Open Graph tag passthrough. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
||||||
| `OG_CACHE_CONSIDER_HOST` | `false` | If set to `true`, Anubis will consider the host in the Open Graph tag cache key. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
| `OG_CACHE_CONSIDER_HOST` | `false` | If set to `true`, Anubis will consider the host in the Open Graph tag cache key. Prefer using [the policy file](./configuration/open-graph.mdx) to configure the Open Graph subsystem. |
|
||||||
| `OVERLAY_FOLDER` | unset | <EO /> If set, treat the given path as an [overlay folder](./botstopper.mdx#custom-images-and-css), allowing you to customize CSS, fonts, images, and add other assets to BotStopper deployments. |
|
| `OVERLAY_FOLDER` | unset | <EO /> If set, treat the given path as an [overlay folder](./botstopper.mdx#custom-images-and-css), allowing you to customize CSS, fonts, images, and add other assets to BotStopper deployments. |
|
||||||
| `POLICY_FNAME` | unset | The file containing [bot policy configuration](./policies.mdx). See the bot policy documentation for more details. If unset, the default bot policy configuration is used. |
|
| `POLICY_FNAME` | unset | The file containing [bot policy configuration](./policies.mdx). See the bot policy documentation for more details. If unset, the default bot policy configuration is used. |
|
||||||
| `PUBLIC_URL` | unset | The externally accessible URL for this Anubis instance, used for constructing redirect URLs (e.g., for Traefik forwardAuth). Leave it unset when Anubis terminates traffic directly (sidecar/standalone deployments) or redirect building will fail with `redir=null`. |
|
| `PUBLIC_URL` | unset | The externally accessible URL for this Anubis instance, used for constructing redirect URLs (e.g., for Traefik forwardAuth). Leave it unset when Anubis terminates traffic directly (sidecar/standalone deployments) or redirect building will fail with `redir=null`. |
|
||||||
| `REDIRECT_DOMAINS` | unset | Comma-separated list of domain names that Anubis should allow redirects to when passing a challenge. See [Redirect Domain Configuration](./configuration/redirect-domains) for more details. |
|
| `REDIRECT_DOMAINS` | unset | Comma-separated list of domain names that Anubis should allow redirects to when passing a challenge. See [Redirect Domain Configuration](./configuration/redirect-domains.mdx) for more details. |
|
||||||
| `SERVE_ROBOTS_TXT` | `false` | If set `true`, Anubis will serve a default `robots.txt` file that disallows all known AI scrapers by name and then additionally disallows every scraper. This is useful if facts and circumstances make it difficult to change the underlying service to serve such a `robots.txt` file. |
|
| `SERVE_ROBOTS_TXT` | `false` | If set `true`, Anubis will serve a default `robots.txt` file that disallows all known AI scrapers by name and then additionally disallows every scraper. This is useful if facts and circumstances make it difficult to change the underlying service to serve such a `robots.txt` file. |
|
||||||
| `SLOG_LEVEL` | `INFO` | The log level for structured logging. Valid values are `DEBUG`, `INFO`, `WARN`, and `ERROR`. Set to `DEBUG` to see all requests, evaluations, and detailed diagnostic information. |
|
| `SLOG_LEVEL` | `INFO` | The log level for structured logging. Valid values are `DEBUG`, `INFO`, `WARN`, and `ERROR`. Set to `DEBUG` to see all requests, evaluations, and detailed diagnostic information. |
|
||||||
| `SOCKET_MODE` | `0770` | _Only used when at least one of the `*_BIND_NETWORK` variables are set to `unix`._ The socket mode (permissions) for Unix domain sockets. |
|
| `SOCKET_MODE` | `0770` | _Only used when at least one of the `*_BIND_NETWORK` variables are set to `unix`._ The socket mode (permissions) for Unix domain sockets. |
|
||||||
|
|||||||
@@ -117,6 +117,27 @@ remote_addresses:
|
|||||||
- 100.64.0.0/10
|
- 100.64.0.0/10
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Metrics server
|
||||||
|
|
||||||
|
Anubis includes support for [Prometheus-style metrics](https://prometheus.io/docs/introduction/overview/), allowing systems administrators to monitor Anubis' performance and effectiveness. This is a separate HTTP server with metrics, health checking, and debug routes.
|
||||||
|
|
||||||
|
Anubis' metrics server is configured with the `metrics` block in the configuration file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
```
|
||||||
|
|
||||||
|
If you want to bind metrics to a Unix socket, make sure to set the network to `unix` and add a socket mode:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
metrics:
|
||||||
|
bind: "/tmp/anubis_metrics.sock"
|
||||||
|
network: unix
|
||||||
|
socketMode: "0700" # must be a string
|
||||||
|
```
|
||||||
|
|
||||||
## Imprint / Impressum support
|
## Imprint / Impressum support
|
||||||
|
|
||||||
Anubis has support for showing imprint / impressum information. This is defined in the `impressum` block of your configuration. See [Imprint / Impressum configuration](./configuration/impressum.mdx) for more information.
|
Anubis has support for showing imprint / impressum information. This is defined in the `impressum` block of your configuration. See [Imprint / Impressum configuration](./configuration/impressum.mdx) for more information.
|
||||||
@@ -393,6 +414,32 @@ logging:
|
|||||||
|
|
||||||
When files are rotated out, the old files will be named after the rotation timestamp in [RFC 3339 format](https://www.rfc-editor.org/rfc/rfc3339).
|
When files are rotated out, the old files will be named after the rotation timestamp in [RFC 3339 format](https://www.rfc-editor.org/rfc/rfc3339).
|
||||||
|
|
||||||
|
:::note
|
||||||
|
|
||||||
|
If you are running Anubis in systemd via a native package, the default systemd unit settings are very restrictive and will forbid writing to folders in `/var/log`. In order to fix this, please make a [drop-in unit](https://www.flatcar.org/docs/latest/setup/systemd/drop-in-units/) like the following:
|
||||||
|
|
||||||
|
```text
|
||||||
|
# /etc/systemd/anubis@instance-name.service.d/50-var-log-readwrite.conf
|
||||||
|
[Service]
|
||||||
|
ReadWritePaths=/run /var/log/anubis
|
||||||
|
```
|
||||||
|
|
||||||
|
Once you write this to the correct place, reload the systemd configuration:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
```
|
||||||
|
|
||||||
|
And then restart Anubis:
|
||||||
|
|
||||||
|
```text
|
||||||
|
sudo systemctl restart anubis@instance-name
|
||||||
|
```
|
||||||
|
|
||||||
|
You may be required to make drop-ins for each Anubis instance depending on the facts and circumstances of your deployment.
|
||||||
|
|
||||||
|
:::
|
||||||
|
|
||||||
### `stdio` sink
|
### `stdio` sink
|
||||||
|
|
||||||
By default, Anubis logs everything to the standard error stream of its process. This requires no configuration:
|
By default, Anubis logs everything to the standard error stream of its process. This requires no configuration:
|
||||||
|
|||||||
@@ -1,31 +0,0 @@
|
|||||||
---
|
|
||||||
title: Code quality guidelines
|
|
||||||
---
|
|
||||||
|
|
||||||
When submitting code to Anubis, please take the time to consider the fact that this project is security software. If things go bad, bots can pummel sites into oblivion. This is not ideal for uptime.
|
|
||||||
|
|
||||||
As such, code reviews will be a bit more strict than you have seen in other projects. This is not people trying to be mean, this is a side effect of taking the problem seriously.
|
|
||||||
|
|
||||||
When making code changes, try to do the following:
|
|
||||||
|
|
||||||
- If you're submitting a bugfix, add a test case for it
|
|
||||||
- If you're changing the JavaScript, make sure the integration tests pass (`npm run test:integration`)
|
|
||||||
|
|
||||||
## Commit messages
|
|
||||||
|
|
||||||
Anubis follows the Go project's conventions for commit messages. In general, an ideal commit message should read like this:
|
|
||||||
|
|
||||||
```text
|
|
||||||
path/to/folder: brief description of the change
|
|
||||||
|
|
||||||
If the change is subtle, has implementation consequences, or is otherwise
|
|
||||||
not entirely self-describing: take the time to spell out why. If things
|
|
||||||
are very subtle, please also amend the documentation accordingly
|
|
||||||
```
|
|
||||||
|
|
||||||
The subject of a commit message should be the second half of the sentence "This commit changes the Anubis project to:". Here's a few examples:
|
|
||||||
|
|
||||||
- `disable DroneBL by default`
|
|
||||||
- `port the challenge to WebAssembly`
|
|
||||||
|
|
||||||
The extended commit message is also your place to give rationale for a new feature. When maintainers are reviewing your code, they will use this to figure out if the burden from feature maintainership is worth the merge.
|
|
||||||
@@ -35,6 +35,12 @@ Anubis is brought to you by sponsors and donors like:
|
|||||||
|
|
||||||
### Gold Tier
|
### Gold Tier
|
||||||
|
|
||||||
|
<a href="https://www.unipromos.com/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="/img/sponsors/unipromos.webp" alt="Uvensys" height="64" />
|
||||||
|
</a>
|
||||||
|
<a href="https://uvensys.de/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="/img/sponsors/uvensys.webp" alt="Uvensys" height="64" />
|
||||||
|
</a>
|
||||||
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
<a href="https://distrust.co?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
<img src="/img/sponsors/distrust-logo.webp" alt="Distrust" height="64" />
|
<img src="/img/sponsors/distrust-logo.webp" alt="Distrust" height="64" />
|
||||||
</a>
|
</a>
|
||||||
@@ -81,6 +87,15 @@ Anubis is brought to you by sponsors and donors like:
|
|||||||
height="64"
|
height="64"
|
||||||
/>
|
/>
|
||||||
</a>
|
</a>
|
||||||
|
<a href="https://dd-wrt.com/">
|
||||||
|
<img src="/img/sponsors/ddwrt-logo.webp" alt="embeDD GmbH" height="64" />
|
||||||
|
</a>
|
||||||
|
<a href="https://www.qwertiko.de?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="/img/sponsors/qwertiko-logo.webp" alt="Qwertiko" height="64" />
|
||||||
|
</a>
|
||||||
|
<a href="https://wenet.pl/?utm_campaign=github&utm_medium=referral&utm_content=anubis">
|
||||||
|
<img src="/img/sponsors/wenet-logo.webp" alt="Wenet" height="64" />
|
||||||
|
</a>
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
|
|||||||
@@ -22,3 +22,24 @@ If you use a browser extension such as [JShelter](https://jshelter.org/), you wi
|
|||||||
## Does Anubis mine Bitcoin?
|
## Does Anubis mine Bitcoin?
|
||||||
|
|
||||||
No. Anubis does not mine Bitcoin or any other cryptocurrency.
|
No. Anubis does not mine Bitcoin or any other cryptocurrency.
|
||||||
|
|
||||||
|
## I disabled Just-in-time compilation in my browser. Why is Anubis slow?
|
||||||
|
|
||||||
|
Anubis proof-of-work checks run an open source JavaScript program in your browser. These checks do a lot of complicated math and aim to be done quickly, so the execution speed depends on [Just-in-time (JIT) compilation](https://en.wikipedia.org/wiki/Just-in-time_compilation). JIT compiles JavaScript from the Internet into native machine code at runtime. The code produced by the JIT engine is almost as good as if it was written in a native programming language and compiled for your computer in particular. Without JIT, all JavaScript programs on every website you visit run through a slow interpreter.
|
||||||
|
|
||||||
|
This interpreter is much slower than native code because it has to translate each low level JavaScript operation into many dozens of calls to execute. This means that using the interpreter incurs a massive performance hit by its very nature; it takes longer to add numbers than if the CPU just added the numbers directly.
|
||||||
|
|
||||||
|
Some users choose to disable JIT as a hardening measure against theoretical browser exploits. This is a reasonable choice if you face targeted attacks from well-resourced adversaries (such as nation-state actors), but it comes with real performance costs.
|
||||||
|
|
||||||
|
If you've disabled JIT and find Anubis checks slow, re-enabling JIT is the fix. There is no way for Anubis to work around this on our end.
|
||||||
|
|
||||||
|
## What versions of browsers does Anubis support?
|
||||||
|
|
||||||
|
Anubis is written mainly by a single person in a basement in Canada. As such it is impossible for Anubis to support every version of every browser on the planet. As such, here's a few rules of thumb for the browsers that Anubis focuses on supporting:
|
||||||
|
|
||||||
|
- At least the two (2) most recent LTS releases of Firefox and Chrome.
|
||||||
|
- At least the version of Chromium as used by the Samsung Browser on Android.
|
||||||
|
- At least the last version of Chromium and Firefox that are known to run on Windows 7.
|
||||||
|
- At least the version of Safari that runs on the second-to-oldest iPhone model currently on the market.
|
||||||
|
|
||||||
|
We cannot give more cohesive version bounds than this. If you run into problems, please file an issue. Sometimes you may just need to upgrade hardware though.
|
||||||
|
|||||||
@@ -38,10 +38,8 @@ This page contains a non-exhaustive list with all websites using Anubis.
|
|||||||
- https://squirreljme.cc/
|
- https://squirreljme.cc/
|
||||||
- https://superlove.sayitditto.net/
|
- https://superlove.sayitditto.net/
|
||||||
- https://svnweb.freebsd.org/
|
- https://svnweb.freebsd.org/
|
||||||
- https://trac.ffmpeg.org/
|
|
||||||
- https://tumfatig.net/
|
- https://tumfatig.net/
|
||||||
- https://wiki.archlinux.org/
|
- https://wiki.archlinux.org/
|
||||||
- https://wiki.dolphin-emu.org/
|
|
||||||
- https://wiki.freepascal.org/
|
- https://wiki.freepascal.org/
|
||||||
- https://wiki.koha-community.org/
|
- https://wiki.koha-community.org/
|
||||||
- https://www.cfaarchive.org/
|
- https://www.cfaarchive.org/
|
||||||
@@ -53,6 +51,11 @@ This page contains a non-exhaustive list with all websites using Anubis.
|
|||||||
- https://bbs.archlinux32.org/
|
- https://bbs.archlinux32.org/
|
||||||
- https://bugs.archlinux32.org/
|
- https://bugs.archlinux32.org/
|
||||||
</details>
|
</details>
|
||||||
|
- <details>
|
||||||
|
<summary>Dolphin Emulator</summary>
|
||||||
|
- https://forums.dolphin-emu.org/
|
||||||
|
- https://wiki.dolphin-emu.org/
|
||||||
|
</details>
|
||||||
- <details>
|
- <details>
|
||||||
<summary>Duke University</summary>
|
<summary>Duke University</summary>
|
||||||
- https://repository.duke.edu/
|
- https://repository.duke.edu/
|
||||||
@@ -60,6 +63,11 @@ This page contains a non-exhaustive list with all websites using Anubis.
|
|||||||
- https://find.library.duke.edu/
|
- https://find.library.duke.edu/
|
||||||
- https://nicholas.duke.edu/
|
- https://nicholas.duke.edu/
|
||||||
</details>
|
</details>
|
||||||
|
- <details>
|
||||||
|
<summary>FFmpeg</summary>
|
||||||
|
- https://git.ffmpeg.org/
|
||||||
|
- https://trac.ffmpeg.org/
|
||||||
|
</details>
|
||||||
- <details>
|
- <details>
|
||||||
<summary>Forschungszentrum Jülich</summary>
|
<summary>Forschungszentrum Jülich</summary>
|
||||||
- https://juser.fz-juelich.de/
|
- https://juser.fz-juelich.de/
|
||||||
@@ -112,11 +120,8 @@ This page contains a non-exhaustive list with all websites using Anubis.
|
|||||||
- https://git.kernel.org/
|
- https://git.kernel.org/
|
||||||
- https://lore.kernel.org/
|
- https://lore.kernel.org/
|
||||||
</details>
|
</details>
|
||||||
- <details>
|
|
||||||
<summary>The United Nations</summary>
|
|
||||||
- https://policytoolbox.iiep.unesco.org/
|
|
||||||
</details>
|
|
||||||
- <details>
|
- <details>
|
||||||
<summary>Valve Corporation</summary>
|
<summary>Valve Corporation</summary>
|
||||||
- https://developer.valvesoftware.com/wiki/Main_Page
|
- https://developer.valvesoftware.com/wiki/Main_Page
|
||||||
|
- https://wiki.teamfortress.com/wiki/Main_Page
|
||||||
</details>
|
</details>
|
||||||
|
|||||||
BIN
Binary file not shown.
|
After Width: | Height: | Size: 4.9 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 31 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 1.8 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 7.0 KiB |
BIN
Binary file not shown.
|
After Width: | Height: | Size: 5.7 KiB |
@@ -36,7 +36,7 @@ func Glob(pattern, subj string) bool {
|
|||||||
end := len(parts) - 1
|
end := len(parts) - 1
|
||||||
|
|
||||||
// Go over the leading parts and ensure they match.
|
// Go over the leading parts and ensure they match.
|
||||||
for i := 0; i < end; i++ {
|
for i := range end {
|
||||||
idx := strings.Index(subj, parts[i])
|
idx := strings.Index(subj, parts[i])
|
||||||
|
|
||||||
switch i {
|
switch i {
|
||||||
|
|||||||
@@ -184,7 +184,7 @@ func TestHashCollisions(t *testing.T) {
|
|||||||
for _, prefix := range prefixes {
|
for _, prefix := range prefixes {
|
||||||
for _, suffix := range suffixes {
|
for _, suffix := range suffixes {
|
||||||
for _, variation := range variations {
|
for _, variation := range variations {
|
||||||
for i := 0; i < 100; i++ {
|
for i := range 100 {
|
||||||
input := fmt.Sprintf("%s%s%s-%d", prefix, suffix, variation, i)
|
input := fmt.Sprintf("%s%s%s-%d", prefix, suffix, variation, i)
|
||||||
hash := XXHash64sum(input)
|
hash := XXHash64sum(input)
|
||||||
if existing, exists := xxhashHashes[hash]; exists {
|
if existing, exists := xxhashHashes[hash]; exists {
|
||||||
@@ -211,7 +211,7 @@ func TestHashCollisions(t *testing.T) {
|
|||||||
|
|
||||||
seqCount := 0
|
seqCount := 0
|
||||||
for _, pattern := range patterns {
|
for _, pattern := range patterns {
|
||||||
for i := 0; i < 10000; i++ {
|
for i := range 10000 {
|
||||||
input := fmt.Sprintf(pattern, i)
|
input := fmt.Sprintf(pattern, i)
|
||||||
hash := XXHash64sum(input)
|
hash := XXHash64sum(input)
|
||||||
if existing, exists := xxhashHashes[hash]; exists {
|
if existing, exists := xxhashHashes[hash]; exists {
|
||||||
|
|||||||
@@ -120,7 +120,7 @@ func (i *Impl) makeAffirmations() []string {
|
|||||||
count := rand.IntN(5) + 1
|
count := rand.IntN(5) + 1
|
||||||
|
|
||||||
var result []string
|
var result []string
|
||||||
for j := 0; j < count; j++ {
|
for range count {
|
||||||
result = append(result, i.affirmation.Spin())
|
result = append(result, i.affirmation.Spin())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ func (i *Impl) makeSpins() []string {
|
|||||||
count := rand.IntN(5) + 1
|
count := rand.IntN(5) + 1
|
||||||
|
|
||||||
var result []string
|
var result []string
|
||||||
for j := 0; j < count; j++ {
|
for range count {
|
||||||
result = append(result, i.body.Spin())
|
result = append(result, i.body.Spin())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ func (lo *ListOr[T]) UnmarshalJSON(data []byte) error {
|
|||||||
|
|
||||||
// Check if first non-whitespace character is '['
|
// Check if first non-whitespace character is '['
|
||||||
firstChar := data[0]
|
firstChar := data[0]
|
||||||
for i := 0; i < len(data); i++ {
|
for i := range data {
|
||||||
if data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
if data[i] != ' ' && data[i] != '\t' && data[i] != '\n' && data[i] != '\r' {
|
||||||
firstChar = data[i]
|
firstChar = data[i]
|
||||||
break
|
break
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ func TestMemoryUsage(t *testing.T) {
|
|||||||
|
|
||||||
// Run getTarget many times
|
// Run getTarget many times
|
||||||
u, _ := url.Parse("/path/to/resource?query=1&foo=bar&baz=qux")
|
u, _ := url.Parse("/path/to/resource?query=1&foo=bar&baz=qux")
|
||||||
for i := 0; i < 10000; i++ {
|
for range 10000 {
|
||||||
_ = cache.getTarget(u)
|
_ = cache.getTarget(u)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -129,7 +129,7 @@ func TestMemoryUsage(t *testing.T) {
|
|||||||
runtime.GC()
|
runtime.GC()
|
||||||
runtime.ReadMemStats(&m1)
|
runtime.ReadMemStats(&m1)
|
||||||
|
|
||||||
for i := 0; i < 1000; i++ {
|
for range 1000 {
|
||||||
_ = cache.extractOGTags(doc)
|
_ = cache.extractOGTags(doc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package ogtags
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
@@ -78,7 +79,7 @@ func FuzzGetTarget(f *testing.F) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure no memory corruption by calling multiple times
|
// Ensure no memory corruption by calling multiple times
|
||||||
for i := 0; i < 3; i++ {
|
for range 3 {
|
||||||
result2 := cache.getTarget(u)
|
result2 := cache.getTarget(u)
|
||||||
if result != result2 {
|
if result != result2 {
|
||||||
t.Errorf("getTarget not deterministic: %q != %q", result, result2)
|
t.Errorf("getTarget not deterministic: %q != %q", result, result2)
|
||||||
@@ -148,11 +149,8 @@ func FuzzExtractOGTags(f *testing.F) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
for _, tag := range cache.approvedTags {
|
if slices.Contains(cache.approvedTags, property) {
|
||||||
if property == tag {
|
|
||||||
approved = true
|
approved = true
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
@@ -260,11 +258,8 @@ func FuzzExtractMetaTagInfo(f *testing.F) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
for _, tag := range cache.approvedTags {
|
if slices.Contains(cache.approvedTags, property) {
|
||||||
if property == tag {
|
|
||||||
approved = true
|
approved = true
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !approved {
|
if !approved {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package ogtags
|
package ogtags
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/net/html"
|
"golang.org/x/net/html"
|
||||||
@@ -65,11 +66,9 @@ func (c *OGTagCache) extractMetaTagInfo(n *html.Node) (property, content string)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check exact matches
|
// Check exact matches
|
||||||
for _, tag := range c.approvedTags {
|
if slices.Contains(c.approvedTags, propertyKey) {
|
||||||
if propertyKey == tag {
|
|
||||||
return propertyKey, content
|
return propertyKey, content
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return "", content
|
return "", content
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,92 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// parseBindNetFromAddr determine bind network and address based on the given network and address.
|
||||||
|
func parseBindNetFromAddr(address string) (string, string, error) {
|
||||||
|
defaultScheme := "http://"
|
||||||
|
if !strings.Contains(address, "://") {
|
||||||
|
if strings.HasPrefix(address, ":") {
|
||||||
|
address = defaultScheme + "localhost" + address
|
||||||
|
} else {
|
||||||
|
address = defaultScheme + address
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bindUri, err := url.Parse(address)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("failed to parse bind URL: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch bindUri.Scheme {
|
||||||
|
case "unix":
|
||||||
|
return "unix", bindUri.Path, nil
|
||||||
|
case "tcp", "http", "https":
|
||||||
|
return "tcp", bindUri.Host, nil
|
||||||
|
default:
|
||||||
|
return "", "", fmt.Errorf("unsupported network scheme %s in address %s", bindUri.Scheme, address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetupListener sets up a network listener based on the input from configuration
|
||||||
|
// envvars. It returns a network listener and the URL to that listener or an error.
|
||||||
|
func SetupListener(network, address, socketMode string) (net.Listener, string, error) {
|
||||||
|
formattedAddress := ""
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if network == "" {
|
||||||
|
// keep compatibility
|
||||||
|
network, address, err = parseBindNetFromAddr(address)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("can't parse bind and network: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch network {
|
||||||
|
case "unix":
|
||||||
|
formattedAddress = "unix:" + address
|
||||||
|
case "tcp":
|
||||||
|
if strings.HasPrefix(address, ":") { // assume it's just a port e.g. :4259
|
||||||
|
formattedAddress = "http://localhost" + address
|
||||||
|
} else {
|
||||||
|
formattedAddress = "http://" + address
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
formattedAddress = fmt.Sprintf(`(%s) %s`, network, address)
|
||||||
|
}
|
||||||
|
|
||||||
|
ln, err := net.Listen(network, address)
|
||||||
|
if err != nil {
|
||||||
|
return nil, "", fmt.Errorf("failed to bind to %s: %w", formattedAddress, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// additional permission handling for unix sockets
|
||||||
|
if network == "unix" {
|
||||||
|
mode, err := strconv.ParseUint(socketMode, 8, 0)
|
||||||
|
if err != nil {
|
||||||
|
ln.Close()
|
||||||
|
return nil, "", fmt.Errorf("could not parse socket mode %s: %w", socketMode, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.Chmod(address, os.FileMode(mode))
|
||||||
|
if err != nil {
|
||||||
|
err := fmt.Errorf("could not change socket mode: %w", err)
|
||||||
|
clErr := ln.Close()
|
||||||
|
if clErr != nil {
|
||||||
|
return nil, "", errors.Join(err, clErr)
|
||||||
|
}
|
||||||
|
return nil, "", err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return ln, formattedAddress, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,180 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io/fs"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseBindNetFromAddr(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
address string
|
||||||
|
wantErr bool
|
||||||
|
network string
|
||||||
|
bind string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple tcp",
|
||||||
|
address: "localhost:9090",
|
||||||
|
wantErr: false,
|
||||||
|
network: "tcp",
|
||||||
|
bind: "localhost:9090",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "simple unix",
|
||||||
|
address: "unix:///tmp/foo.sock",
|
||||||
|
wantErr: false,
|
||||||
|
network: "unix",
|
||||||
|
bind: "/tmp/foo.sock",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid network",
|
||||||
|
address: "foo:///tmp/bar.sock",
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tcp uri",
|
||||||
|
address: "tcp://[::]:9090",
|
||||||
|
wantErr: false,
|
||||||
|
network: "tcp",
|
||||||
|
bind: "[::]:9090",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "http uri",
|
||||||
|
address: "http://[::]:9090",
|
||||||
|
wantErr: false,
|
||||||
|
network: "tcp",
|
||||||
|
bind: "[::]:9090",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "https uri",
|
||||||
|
address: "https://[::]:9090",
|
||||||
|
wantErr: false,
|
||||||
|
network: "tcp",
|
||||||
|
bind: "[::]:9090",
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
network, bind, err := parseBindNetFromAddr(tt.address)
|
||||||
|
|
||||||
|
switch {
|
||||||
|
case tt.wantErr && err == nil:
|
||||||
|
t.Errorf("parseBindNetFromAddr(%q) should have errored but did not", tt.address)
|
||||||
|
case !tt.wantErr && err != nil:
|
||||||
|
t.Errorf("parseBindNetFromAddr(%q) threw an error: %v", tt.address, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if network != tt.network {
|
||||||
|
t.Errorf("parseBindNetFromAddr(%q) wanted network: %q, got: %q", tt.address, tt.network, network)
|
||||||
|
}
|
||||||
|
|
||||||
|
if bind != tt.bind {
|
||||||
|
t.Errorf("parseBindNetFromAddr(%q) wanted bind: %q, got: %q", tt.address, tt.bind, bind)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetupListener(t *testing.T) {
|
||||||
|
td := t.TempDir()
|
||||||
|
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
network, address, socketMode string
|
||||||
|
wantErr bool
|
||||||
|
socketURLPrefix string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "simple tcp",
|
||||||
|
network: "",
|
||||||
|
address: ":0",
|
||||||
|
wantErr: false,
|
||||||
|
socketURLPrefix: "http://localhost:",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "simple unix",
|
||||||
|
network: "",
|
||||||
|
address: "unix://" + filepath.Join(td, "a"),
|
||||||
|
socketMode: "0770",
|
||||||
|
wantErr: false,
|
||||||
|
socketURLPrefix: "unix:" + filepath.Join(td, "a"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "tcp",
|
||||||
|
network: "tcp",
|
||||||
|
address: ":0",
|
||||||
|
wantErr: false,
|
||||||
|
socketURLPrefix: "http://localhost:",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "udp",
|
||||||
|
network: "udp",
|
||||||
|
address: ":0",
|
||||||
|
wantErr: true,
|
||||||
|
socketURLPrefix: "http://localhost:",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "unix socket",
|
||||||
|
network: "unix",
|
||||||
|
socketMode: "0770",
|
||||||
|
address: filepath.Join(td, "a"),
|
||||||
|
wantErr: false,
|
||||||
|
socketURLPrefix: "unix:" + filepath.Join(td, "a"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid socket mode",
|
||||||
|
network: "unix",
|
||||||
|
socketMode: "taco bell",
|
||||||
|
address: filepath.Join(td, "a"),
|
||||||
|
wantErr: true,
|
||||||
|
socketURLPrefix: "unix:" + filepath.Join(td, "a"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "empty socket mode",
|
||||||
|
network: "unix",
|
||||||
|
socketMode: "",
|
||||||
|
address: filepath.Join(td, "a"),
|
||||||
|
wantErr: true,
|
||||||
|
socketURLPrefix: "unix:" + filepath.Join(td, "a"),
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
ln, socketURL, err := SetupListener(tt.network, tt.address, tt.socketMode)
|
||||||
|
switch {
|
||||||
|
case tt.wantErr && err == nil:
|
||||||
|
t.Errorf("SetupListener(%q, %q, %q) should have errored but did not", tt.network, tt.address, tt.socketMode)
|
||||||
|
case !tt.wantErr && err != nil:
|
||||||
|
t.Fatalf("SetupListener(%q, %q, %q) threw an error: %v", tt.network, tt.address, tt.socketMode, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if ln != nil {
|
||||||
|
defer ln.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
if !tt.wantErr && !strings.HasPrefix(socketURL, tt.socketURLPrefix) {
|
||||||
|
t.Errorf("SetupListener(%q, %q, %q) should have returned a URL with prefix %q but got: %q", tt.network, tt.address, tt.socketMode, tt.socketURLPrefix, socketURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
if tt.socketMode != "" {
|
||||||
|
mode, err := strconv.ParseUint(tt.socketMode, 8, 0)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sockPath := strings.TrimPrefix(socketURL, "unix:")
|
||||||
|
st, err := os.Stat(sockPath)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("can't os.Stat(%q): %v", sockPath, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if st.Mode().Perm() != fs.FileMode(mode) {
|
||||||
|
t.Errorf("file mode of %q should be %s but is actually %s", sockPath, strconv.FormatUint(mode, 8), strconv.FormatUint(uint64(st.Mode()), 8))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -270,7 +270,7 @@ func TestPlaywrightBrowser(t *testing.T) {
|
|||||||
|
|
||||||
var performedAction action
|
var performedAction action
|
||||||
var err error
|
var err error
|
||||||
for i := 0; i < 5; i++ {
|
for i := range 5 {
|
||||||
performedAction, err = executeTestCase(t, tc, typ, anubisURL)
|
performedAction, err = executeTestCase(t, tc, typ, anubisURL)
|
||||||
if performedAction == tc.action {
|
if performedAction == tc.action {
|
||||||
break
|
break
|
||||||
|
|||||||
+23
-4
@@ -81,11 +81,11 @@ type Server struct {
|
|||||||
func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
|
func (s *Server) getTokenKeyfunc() jwt.Keyfunc {
|
||||||
// return ED25519 key if HS512 is not set
|
// return ED25519 key if HS512 is not set
|
||||||
if len(s.hs512Secret) == 0 {
|
if len(s.hs512Secret) == 0 {
|
||||||
return func(token *jwt.Token) (interface{}, error) {
|
return func(token *jwt.Token) (any, error) {
|
||||||
return s.ed25519Priv.Public().(ed25519.PublicKey), nil
|
return s.ed25519Priv.Public().(ed25519.PublicKey), nil
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return func(token *jwt.Token) (interface{}, error) {
|
return func(token *jwt.Token) (any, error) {
|
||||||
return s.hs512Secret, nil
|
return s.hs512Secret, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -106,6 +106,13 @@ func (s *Server) issueChallenge(ctx context.Context, r *http.Request, lg *slog.L
|
|||||||
//return nil, errors.New("[unexpected] this codepath should be impossible, asked to issue a challenge for a non-challenge rule")
|
//return nil, errors.New("[unexpected] this codepath should be impossible, asked to issue a challenge for a non-challenge rule")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rule.Challenge == nil {
|
||||||
|
rule.Challenge = &config.ChallengeRules{
|
||||||
|
Difficulty: s.policy.DefaultDifficulty,
|
||||||
|
Algorithm: config.DefaultAlgorithm,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
id, err := uuid.NewV7()
|
id, err := uuid.NewV7()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -491,7 +498,11 @@ func (s *Server) PassChallenge(w http.ResponseWriter, r *http.Request) {
|
|||||||
chall, err := s.getChallenge(r)
|
chall, err := s.getChallenge(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Error("getChallenge failed", "err", err)
|
lg.Error("getChallenge failed", "err", err)
|
||||||
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm), makeCode(err))
|
algorithm := "unknown"
|
||||||
|
if rule.Challenge != nil {
|
||||||
|
algorithm = rule.Challenge.Algorithm
|
||||||
|
}
|
||||||
|
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -638,8 +649,16 @@ func (s *Server) check(r *http.Request, lg *slog.Logger) (policy.CheckResult, *p
|
|||||||
}
|
}
|
||||||
|
|
||||||
if matches {
|
if matches {
|
||||||
|
challRules := t.Challenge
|
||||||
|
if challRules == nil {
|
||||||
|
// Non-CHALLENGE thresholds (ALLOW/DENY) don't have challenge config.
|
||||||
|
// Use an empty struct so hydrateChallengeRule can fill from stored
|
||||||
|
// challenge data during validation, rather than baking in defaults
|
||||||
|
// that could mismatch the difficulty the client actually solved for.
|
||||||
|
challRules = &config.ChallengeRules{}
|
||||||
|
}
|
||||||
return cr("threshold/"+t.Name, t.Action, weight), &policy.Bot{
|
return cr("threshold/"+t.Name, t.Action, weight), &policy.Bot{
|
||||||
Challenge: t.Challenge,
|
Challenge: challRules,
|
||||||
Rules: &checker.List{},
|
Rules: &checker.List{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -38,8 +38,8 @@ func NewTLogWriter(t *testing.T) io.Writer {
|
|||||||
|
|
||||||
// Write splits input on newlines and logs each line separately.
|
// Write splits input on newlines and logs each line separately.
|
||||||
func (w *TLogWriter) Write(p []byte) (n int, err error) {
|
func (w *TLogWriter) Write(p []byte) (n int, err error) {
|
||||||
lines := strings.Split(string(p), "\n")
|
lines := strings.SplitSeq(string(p), "\n")
|
||||||
for _, line := range lines {
|
for line := range lines {
|
||||||
if line != "" {
|
if line != "" {
|
||||||
w.t.Log(line)
|
w.t.Log(line)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ var (
|
|||||||
ErrFailed = errors.New("challenge: user failed challenge")
|
ErrFailed = errors.New("challenge: user failed challenge")
|
||||||
ErrMissingField = errors.New("challenge: missing field")
|
ErrMissingField = errors.New("challenge: missing field")
|
||||||
ErrInvalidFormat = errors.New("challenge: field has invalid format")
|
ErrInvalidFormat = errors.New("challenge: field has invalid format")
|
||||||
|
ErrInvalidInput = errors.New("challenge: input is nil or missing required fields")
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewError(verb, publicReason string, privateReason error) *Error {
|
func NewError(verb, publicReason string, privateReason error) *Error {
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package challenge
|
package challenge
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"sort"
|
"sort"
|
||||||
@@ -50,12 +51,44 @@ type IssueInput struct {
|
|||||||
Store store.Interface
|
Store store.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (in *IssueInput) Valid() error {
|
||||||
|
if in == nil {
|
||||||
|
return fmt.Errorf("%w: IssueInput is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule == nil {
|
||||||
|
return fmt.Errorf("%w: Rule is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Rule.Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type ValidateInput struct {
|
type ValidateInput struct {
|
||||||
Rule *policy.Bot
|
Rule *policy.Bot
|
||||||
Challenge *Challenge
|
Challenge *Challenge
|
||||||
Store store.Interface
|
Store store.Interface
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (in *ValidateInput) Valid() error {
|
||||||
|
if in == nil {
|
||||||
|
return fmt.Errorf("%w: ValidateInput is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule == nil {
|
||||||
|
return fmt.Errorf("%w: Rule is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Rule.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Rule.Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
if in.Challenge == nil {
|
||||||
|
return fmt.Errorf("%w: Challenge is nil", ErrInvalidInput)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
type Impl interface {
|
type Impl interface {
|
||||||
// Setup registers any additional routes with the Impl for assets or API routes.
|
// Setup registers any additional routes with the Impl for assets or API routes.
|
||||||
Setup(mux *http.ServeMux)
|
Setup(mux *http.ServeMux)
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ type Impl struct{}
|
|||||||
func (i *Impl) Setup(mux *http.ServeMux) {}
|
func (i *Impl) Setup(mux *http.ServeMux) {}
|
||||||
|
|
||||||
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't render page: %w", err)
|
return nil, fmt.Errorf("can't render page: %w", err)
|
||||||
@@ -49,6 +53,10 @@ func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return challenge.NewError("validate", "invalid input", err)
|
||||||
|
}
|
||||||
|
|
||||||
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 800 * time.Millisecond)
|
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 800 * time.Millisecond)
|
||||||
|
|
||||||
if time.Now().Before(wantTime) {
|
if time.Now().Before(wantTime) {
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ type impl struct{}
|
|||||||
func (i *impl) Setup(mux *http.ServeMux) {}
|
func (i *impl) Setup(mux *http.ServeMux) {}
|
||||||
|
|
||||||
func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in *challenge.IssueInput) (templ.Component, error) {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
u, err := r.URL.Parse(anubis.BasePrefix + "/.within.website/x/cmd/anubis/api/pass-challenge")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("can't render page: %w", err)
|
return nil, fmt.Errorf("can't render page: %w", err)
|
||||||
@@ -57,6 +61,10 @@ func (i *impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
func (i *impl) Validate(r *http.Request, lg *slog.Logger, in *challenge.ValidateInput) error {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return challenge.NewError("validate", "invalid input", err)
|
||||||
|
}
|
||||||
|
|
||||||
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 80 * time.Millisecond)
|
wantTime := in.Challenge.IssuedAt.Add(time.Duration(in.Rule.Challenge.Difficulty) * 80 * time.Millisecond)
|
||||||
|
|
||||||
if time.Now().Before(wantTime) {
|
if time.Now().Before(wantTime) {
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ func (i *Impl) Issue(w http.ResponseWriter, r *http.Request, lg *slog.Logger, in
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {
|
func (i *Impl) Validate(r *http.Request, lg *slog.Logger, in *chall.ValidateInput) error {
|
||||||
|
if err := in.Valid(); err != nil {
|
||||||
|
return chall.NewError("validate", "invalid input", err)
|
||||||
|
}
|
||||||
|
|
||||||
rule := in.Rule
|
rule := in.Rule
|
||||||
challenge := in.Challenge.RandomData
|
challenge := in.Challenge.RandomData
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,62 @@ func mkRequest(t *testing.T, values map[string]string) *http.Request {
|
|||||||
return req
|
return req
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestValidateNilRuleChallenge reproduces the panic from
|
||||||
|
// https://github.com/TecharoHQ/anubis/issues/1463
|
||||||
|
//
|
||||||
|
// When a threshold rule matches during PassChallenge, check() can return
|
||||||
|
// a policy.Bot with Challenge == nil. After hydrateChallengeRule fails to
|
||||||
|
// run (or the error path hits before it), Validate dereferences
|
||||||
|
// rule.Challenge.Difficulty and panics.
|
||||||
|
func TestValidateNilRuleChallenge(t *testing.T) {
|
||||||
|
i := &Impl{Algorithm: "fast"}
|
||||||
|
lg := slog.With()
|
||||||
|
|
||||||
|
// This is the exact response for SHA256("hunter" + "0") with 0 leading zeros required.
|
||||||
|
const challengeStr = "hunter"
|
||||||
|
const response = "2652bdba8fb4d2ab39ef28d8534d7694c557a4ae146c1e9237bd8d950280500e"
|
||||||
|
|
||||||
|
req := mkRequest(t, map[string]string{
|
||||||
|
"nonce": "0",
|
||||||
|
"elapsedTime": "69",
|
||||||
|
"response": response,
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
input *challenge.ValidateInput
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "nil-rule-challenge",
|
||||||
|
input: &challenge.ValidateInput{
|
||||||
|
Rule: &policy.Bot{},
|
||||||
|
Challenge: &challenge.Challenge{RandomData: challengeStr},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil-rule",
|
||||||
|
input: &challenge.ValidateInput{
|
||||||
|
Challenge: &challenge.Challenge{RandomData: challengeStr},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil-challenge",
|
||||||
|
input: &challenge.ValidateInput{Rule: &policy.Bot{Challenge: &config.ChallengeRules{Algorithm: "fast"}}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nil-input",
|
||||||
|
input: nil,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
err := i.Validate(req, lg, tc.input)
|
||||||
|
if !errors.Is(err, challenge.ErrInvalidInput) {
|
||||||
|
t.Fatalf("expected ErrInvalidInput, got: %v", err)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestBasic(t *testing.T) {
|
func TestBasic(t *testing.T) {
|
||||||
i := &Impl{Algorithm: "fast"}
|
i := &Impl{Algorithm: "fast"}
|
||||||
bot := &policy.Bot{
|
bot := &policy.Bot{
|
||||||
|
|||||||
+13
-4
@@ -19,7 +19,7 @@ import (
|
|||||||
var (
|
var (
|
||||||
ErrNoBotRulesDefined = errors.New("config: must define at least one (1) bot rule")
|
ErrNoBotRulesDefined = errors.New("config: must define at least one (1) bot rule")
|
||||||
ErrBotMustHaveName = errors.New("config.Bot: must set name")
|
ErrBotMustHaveName = errors.New("config.Bot: must set name")
|
||||||
ErrBotMustHaveUserAgentOrPath = errors.New("config.Bot: must set either user_agent_regex, path_regex, headers_regex, or remote_addresses")
|
ErrBotMustHaveUserAgentOrPath = errors.New("config.Bot: must set one of user_agent_regex, path_regex, headers_regex, remote_addresses, expression, or Thoth keyword")
|
||||||
ErrBotMustHaveUserAgentOrPathNotBoth = errors.New("config.Bot: must set either user_agent_regex, path_regex, and not both")
|
ErrBotMustHaveUserAgentOrPathNotBoth = errors.New("config.Bot: must set either user_agent_regex, path_regex, and not both")
|
||||||
ErrUnknownAction = errors.New("config.Bot: unknown action")
|
ErrUnknownAction = errors.New("config.Bot: unknown action")
|
||||||
ErrInvalidUserAgentRegex = errors.New("config.Bot: invalid user agent regex")
|
ErrInvalidUserAgentRegex = errors.New("config.Bot: invalid user agent regex")
|
||||||
@@ -228,8 +228,8 @@ type ImportStatement struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (is *ImportStatement) open() (fs.File, error) {
|
func (is *ImportStatement) open() (fs.File, error) {
|
||||||
if strings.HasPrefix(is.Import, "(data)/") {
|
if after, ok := strings.CutPrefix(is.Import, "(data)/"); ok {
|
||||||
fname := strings.TrimPrefix(is.Import, "(data)/")
|
fname := after
|
||||||
fin, err := data.BotPolicies.Open(fname)
|
fin, err := data.BotPolicies.Open(fname)
|
||||||
return fin, err
|
return fin, err
|
||||||
}
|
}
|
||||||
@@ -325,7 +325,7 @@ func (sc StatusCodes) Valid() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type fileConfig struct {
|
type fileConfig struct {
|
||||||
OpenGraph openGraphFileConfig `json:"openGraph,omitempty"`
|
OpenGraph openGraphFileConfig `json:"openGraph"`
|
||||||
Impressum *Impressum `json:"impressum,omitempty"`
|
Impressum *Impressum `json:"impressum,omitempty"`
|
||||||
Store *Store `json:"store"`
|
Store *Store `json:"store"`
|
||||||
Bots []BotOrImport `json:"bots"`
|
Bots []BotOrImport `json:"bots"`
|
||||||
@@ -334,6 +334,7 @@ type fileConfig struct {
|
|||||||
DNSBL bool `json:"dnsbl"`
|
DNSBL bool `json:"dnsbl"`
|
||||||
DNSTTL DnsTTL `json:"dns_ttl"`
|
DNSTTL DnsTTL `json:"dns_ttl"`
|
||||||
Logging *Logging `json:"logging"`
|
Logging *Logging `json:"logging"`
|
||||||
|
Metrics *Metrics `json:"metrics,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *fileConfig) Valid() error {
|
func (c *fileConfig) Valid() error {
|
||||||
@@ -375,6 +376,12 @@ func (c *fileConfig) Valid() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.Metrics != nil {
|
||||||
|
if err := c.Metrics.Valid(); err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
return fmt.Errorf("config is not valid:\n%w", errors.Join(errs...))
|
return fmt.Errorf("config is not valid:\n%w", errors.Join(errs...))
|
||||||
}
|
}
|
||||||
@@ -417,6 +424,7 @@ func Load(fin io.Reader, fname string) (*Config, error) {
|
|||||||
StatusCodes: c.StatusCodes,
|
StatusCodes: c.StatusCodes,
|
||||||
Store: c.Store,
|
Store: c.Store,
|
||||||
Logging: c.Logging,
|
Logging: c.Logging,
|
||||||
|
Metrics: c.Metrics,
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.OpenGraph.TimeToLive != "" {
|
if c.OpenGraph.TimeToLive != "" {
|
||||||
@@ -508,6 +516,7 @@ type Config struct {
|
|||||||
Logging *Logging
|
Logging *Logging
|
||||||
DNSBL bool
|
DNSBL bool
|
||||||
DNSTTL DnsTTL
|
DNSTTL DnsTTL
|
||||||
|
Metrics *Metrics
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c Config) Valid() error {
|
func (c Config) Valid() error {
|
||||||
|
|||||||
@@ -188,7 +188,6 @@ func TestBotValid(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, cs := range tests {
|
for _, cs := range tests {
|
||||||
cs := cs
|
|
||||||
t.Run(cs.name, func(t *testing.T) {
|
t.Run(cs.name, func(t *testing.T) {
|
||||||
err := cs.bot.Valid()
|
err := cs.bot.Valid()
|
||||||
if err == nil && cs.err == nil {
|
if err == nil && cs.err == nil {
|
||||||
@@ -216,7 +215,6 @@ func TestConfigValidKnownGood(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
fin, err := os.Open(filepath.Join("testdata", "good", st.Name()))
|
fin, err := os.Open(filepath.Join("testdata", "good", st.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -303,7 +301,6 @@ func TestConfigValidBad(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
fin, err := os.Open(filepath.Join("testdata", "bad", st.Name()))
|
fin, err := os.Open(filepath.Join("testdata", "bad", st.Name()))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrInvalidMetricsConfig = errors.New("config: invalid metrics configuration")
|
||||||
|
ErrNoMetricsBind = errors.New("config.Metrics: must define bind")
|
||||||
|
ErrNoMetricsNetwork = errors.New("config.Metrics: must define network")
|
||||||
|
ErrNoMetricsSocketMode = errors.New("config.Metrics: must define socket mode when using unix sockets")
|
||||||
|
ErrInvalidMetricsSocketMode = errors.New("config.Metrics: invalid unix socket mode")
|
||||||
|
ErrInvalidMetricsNetwork = errors.New("config.Metrics: invalid metrics network")
|
||||||
|
)
|
||||||
|
|
||||||
|
type Metrics struct {
|
||||||
|
Bind string `json:"bind" yaml:"bind"`
|
||||||
|
Network string `json:"network" yaml:"network"`
|
||||||
|
SocketMode string `json:"socketMode" yaml:"socketMode"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *Metrics) Valid() error {
|
||||||
|
var errs []error
|
||||||
|
|
||||||
|
if m.Bind == "" {
|
||||||
|
errs = append(errs, ErrNoMetricsBind)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m.Network == "" {
|
||||||
|
errs = append(errs, ErrNoMetricsNetwork)
|
||||||
|
}
|
||||||
|
|
||||||
|
switch m.Network {
|
||||||
|
case "tcp", "tcp4", "tcp6": // https://pkg.go.dev/net#Listen
|
||||||
|
case "unix":
|
||||||
|
if m.SocketMode == "" {
|
||||||
|
errs = append(errs, ErrNoMetricsSocketMode)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := strconv.ParseUint(m.SocketMode, 8, 0); err != nil {
|
||||||
|
errs = append(errs, fmt.Errorf("%w: %w", ErrInvalidMetricsSocketMode, err))
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
errs = append(errs, ErrInvalidMetricsNetwork)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) != 0 {
|
||||||
|
return errors.Join(ErrInvalidMetricsConfig, errors.Join(errs...))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMetricsValid(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
name string
|
||||||
|
input *Metrics
|
||||||
|
err error
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "basic TCP",
|
||||||
|
input: &Metrics{
|
||||||
|
Bind: ":9090",
|
||||||
|
Network: "tcp",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "basic TCP4",
|
||||||
|
input: &Metrics{
|
||||||
|
Bind: ":9090",
|
||||||
|
Network: "tcp4",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "basic TCP6",
|
||||||
|
input: &Metrics{
|
||||||
|
Bind: ":9090",
|
||||||
|
Network: "tcp6",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "basic unix",
|
||||||
|
input: &Metrics{
|
||||||
|
Bind: "/tmp/anubis-metrics.sock",
|
||||||
|
Network: "unix",
|
||||||
|
SocketMode: "0770",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no bind",
|
||||||
|
input: &Metrics{},
|
||||||
|
err: ErrNoMetricsBind,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no network",
|
||||||
|
input: &Metrics{},
|
||||||
|
err: ErrNoMetricsNetwork,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "no unix socket mode",
|
||||||
|
input: &Metrics{
|
||||||
|
Bind: "/tmp/anubis-metrics.sock",
|
||||||
|
Network: "unix",
|
||||||
|
},
|
||||||
|
err: ErrNoMetricsSocketMode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid unix socket mode",
|
||||||
|
input: &Metrics{
|
||||||
|
Bind: "/tmp/anubis-metrics.sock",
|
||||||
|
Network: "unix",
|
||||||
|
SocketMode: "taco bell",
|
||||||
|
},
|
||||||
|
err: ErrInvalidMetricsSocketMode,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "invalid network",
|
||||||
|
input: &Metrics{
|
||||||
|
Bind: ":9090",
|
||||||
|
Network: "taco",
|
||||||
|
},
|
||||||
|
err: ErrInvalidMetricsNetwork,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if err := tt.input.Valid(); !errors.Is(err, tt.err) {
|
||||||
|
t.Logf("wanted error: %v", tt.err)
|
||||||
|
t.Logf("got error: %v", err)
|
||||||
|
t.Error("validation failed")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: taco
|
||||||
+5
-1
@@ -5,5 +5,9 @@
|
|||||||
"remote_addresses": ["0.0.0.0/0", "::/0"],
|
"remote_addresses": ["0.0.0.0/0", "::/0"],
|
||||||
"action": "ALLOW"
|
"action": "ALLOW"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"metrics": {
|
||||||
|
"bind": ":9090",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,3 +4,7 @@ bots:
|
|||||||
- "0.0.0.0/0"
|
- "0.0.0.0/0"
|
||||||
- "::/0"
|
- "::/0"
|
||||||
action: ALLOW
|
action: ALLOW
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+5
-1
@@ -8,5 +8,9 @@
|
|||||||
"action": "DENY"
|
"action": "DENY"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dnsbl": false
|
"dnsbl": false,
|
||||||
|
"metrics": {
|
||||||
|
"bind": ":9090",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,3 +3,7 @@ bots:
|
|||||||
headers_regex:
|
headers_regex:
|
||||||
CF-Worker: .*
|
CF-Worker: .*
|
||||||
action: DENY
|
action: DENY
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
@@ -4,3 +4,7 @@ bots:
|
|||||||
asns:
|
asns:
|
||||||
match:
|
match:
|
||||||
- 13335 # Cloudflare
|
- 13335 # Cloudflare
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+5
-1
@@ -5,5 +5,9 @@
|
|||||||
"user_agent_regex": "Mozilla",
|
"user_agent_regex": "Mozilla",
|
||||||
"action": "CHALLENGE"
|
"action": "CHALLENGE"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"metrics": {
|
||||||
|
"bind": ":9090",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,7 @@ bots:
|
|||||||
- name: generic-browser
|
- name: generic-browser
|
||||||
user_agent_regex: Mozilla
|
user_agent_regex: Mozilla
|
||||||
action: CHALLENGE
|
action: CHALLENGE
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
@@ -6,3 +6,7 @@ bots:
|
|||||||
- name: "test"
|
- name: "test"
|
||||||
user_agent_regex: ".*"
|
user_agent_regex: ".*"
|
||||||
action: "DENY"
|
action: "DENY"
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+4
@@ -6,3 +6,7 @@ bots:
|
|||||||
- '"Accept" in headers'
|
- '"Accept" in headers'
|
||||||
- headers["Accept"].contains("text/html")
|
- headers["Accept"].contains("text/html")
|
||||||
- randInt(1) == 0
|
- randInt(1) == 0
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+5
-1
@@ -6,5 +6,9 @@
|
|||||||
"action": "DENY"
|
"action": "DENY"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"dnsbl": false
|
"dnsbl": false,
|
||||||
|
"metrics": {
|
||||||
|
"bind": ":9090",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,3 +2,7 @@ bots:
|
|||||||
- name: everything
|
- name: everything
|
||||||
user_agent_regex: .*
|
user_agent_regex: .*
|
||||||
action: DENY
|
action: DENY
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+4
@@ -4,3 +4,7 @@ bots:
|
|||||||
geoip:
|
geoip:
|
||||||
countries:
|
countries:
|
||||||
- US
|
- US
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+5
-1
@@ -10,5 +10,9 @@
|
|||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"metrics": {
|
||||||
|
"bind": ":9090",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+4
@@ -6,3 +6,7 @@ bots:
|
|||||||
- userAgent.startsWith("git/") || userAgent.contains("libgit")
|
- userAgent.startsWith("git/") || userAgent.contains("libgit")
|
||||||
- >
|
- >
|
||||||
"Git-Protocol" in headers && headers["Git-Protocol"] == "version=2"
|
"Git-Protocol" in headers && headers["Git-Protocol"] == "version=2"
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+5
-1
@@ -3,5 +3,9 @@
|
|||||||
{
|
{
|
||||||
"import": "./testdata/hack-test.json"
|
"import": "./testdata/hack-test.json"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"metrics": {
|
||||||
|
"bind": ":9090",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
bots:
|
bots:
|
||||||
- import: ./testdata/hack-test.yaml
|
- import: ./testdata/hack-test.yaml
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
@@ -3,5 +3,9 @@
|
|||||||
{
|
{
|
||||||
"import": "(data)/common/keep-internet-working.yaml"
|
"import": "(data)/common/keep-internet-working.yaml"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"metrics": {
|
||||||
|
"bind": ":9090",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1,6 @@
|
|||||||
bots:
|
bots:
|
||||||
- import: (data)/common/keep-internet-working.yaml
|
- import: (data)/common/keep-internet-working.yaml
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+4
@@ -8,3 +8,7 @@ impressum:
|
|||||||
page:
|
page:
|
||||||
title: Test
|
title: Test
|
||||||
body: <p>This is a test</p>
|
body: <p>This is a test</p>
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+4
@@ -13,3 +13,7 @@ logs:
|
|||||||
oldFileTimeFormat: 2006-01-02T15-04-05 # RFC 3339-ish
|
oldFileTimeFormat: 2006-01-02T15-04-05 # RFC 3339-ish
|
||||||
compress: true
|
compress: true
|
||||||
useLocalTime: false # timezone for rotated files is UTC
|
useLocalTime: false # timezone for rotated files is UTC
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+4
@@ -5,3 +5,7 @@ bots:
|
|||||||
|
|
||||||
logging:
|
logging:
|
||||||
sink: "stdio"
|
sink: "stdio"
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+4
@@ -6,3 +6,7 @@ bots:
|
|||||||
adjust: 5
|
adjust: 5
|
||||||
|
|
||||||
thresholds: []
|
thresholds: []
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+5
-1
@@ -75,5 +75,9 @@
|
|||||||
"user_agent_regex": "Mozilla",
|
"user_agent_regex": "Mozilla",
|
||||||
"action": "CHALLENGE"
|
"action": "CHALLENGE"
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
"metrics": {
|
||||||
|
"bind": ":9090",
|
||||||
|
"network": "tcp"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,3 +10,7 @@ openGraph:
|
|||||||
default:
|
default:
|
||||||
"og:title": "Xe's magic land of fun"
|
"og:title": "Xe's magic land of fun"
|
||||||
"og:description": "We're no strangers to love, you know the rules and so do I"
|
"og:description": "We're no strangers to love, you know the rules and so do I"
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+4
@@ -4,3 +4,7 @@ bots:
|
|||||||
user_agent_regex: Mozilla
|
user_agent_regex: Mozilla
|
||||||
weight:
|
weight:
|
||||||
adjust: 5
|
adjust: 5
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
@@ -9,5 +9,9 @@
|
|||||||
"status_codes": {
|
"status_codes": {
|
||||||
"CHALLENGE": 200,
|
"CHALLENGE": 200,
|
||||||
"DENY": 200
|
"DENY": 200
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"bind": ":9090",
|
||||||
|
"network": "tcp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,3 +6,7 @@ bots:
|
|||||||
status_codes:
|
status_codes:
|
||||||
CHALLENGE: 200
|
CHALLENGE: 200
|
||||||
DENY: 200
|
DENY: 200
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
@@ -9,5 +9,9 @@
|
|||||||
"status_codes": {
|
"status_codes": {
|
||||||
"CHALLENGE": 403,
|
"CHALLENGE": 403,
|
||||||
"DENY": 403
|
"DENY": 403
|
||||||
|
},
|
||||||
|
"metrics": {
|
||||||
|
"bind": ":9090",
|
||||||
|
"network": "tcp"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,3 +6,7 @@ bots:
|
|||||||
status_codes:
|
status_codes:
|
||||||
CHALLENGE: 403
|
CHALLENGE: 403
|
||||||
DENY: 403
|
DENY: 403
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
+4
@@ -33,3 +33,7 @@ thresholds:
|
|||||||
challenge:
|
challenge:
|
||||||
algorithm: fast
|
algorithm: fast
|
||||||
difficulty: 4
|
difficulty: 4
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
@@ -2,3 +2,7 @@ bots:
|
|||||||
- name: weight
|
- name: weight
|
||||||
action: WEIGH
|
action: WEIGH
|
||||||
user_agent_regex: Mozilla
|
user_agent_regex: Mozilla
|
||||||
|
|
||||||
|
metrics:
|
||||||
|
bind: ":9090"
|
||||||
|
network: "tcp"
|
||||||
|
|||||||
@@ -24,7 +24,6 @@ func TestBadConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "bad", st.Name()), anubis.DefaultDifficulty, "info"); err == nil {
|
if _, err := LoadPoliciesOrDefault(t.Context(), filepath.Join("config", "testdata", "bad", st.Name()), anubis.DefaultDifficulty, "info"); err == nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
@@ -42,7 +41,6 @@ func TestGoodConfigs(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, st := range finfos {
|
for _, st := range finfos {
|
||||||
st := st
|
|
||||||
t.Run(st.Name(), func(t *testing.T) {
|
t.Run(st.Name(), func(t *testing.T) {
|
||||||
t.Run("with-thoth", func(t *testing.T) {
|
t.Run("with-thoth", func(t *testing.T) {
|
||||||
ctx := thothmock.WithMockThoth(t)
|
ctx := thothmock.WithMockThoth(t)
|
||||||
|
|||||||
+20
-8
@@ -182,10 +182,7 @@ func makeCode(err error) string {
|
|||||||
enc := base64.StdEncoding.EncodeToString(buf.Bytes())
|
enc := base64.StdEncoding.EncodeToString(buf.Bytes())
|
||||||
var builder strings.Builder
|
var builder strings.Builder
|
||||||
for i := 0; i < len(enc); i += width {
|
for i := 0; i < len(enc); i += width {
|
||||||
end := i + width
|
end := min(i+width, len(enc))
|
||||||
if end > len(enc) {
|
|
||||||
end = len(enc)
|
|
||||||
}
|
|
||||||
builder.WriteString(enc[i:end])
|
builder.WriteString(enc[i:end])
|
||||||
builder.WriteByte('\n')
|
builder.WriteByte('\n')
|
||||||
}
|
}
|
||||||
@@ -222,8 +219,12 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
|||||||
chall, err := s.issueChallenge(r.Context(), r, lg, cr, rule)
|
chall, err := s.issueChallenge(r.Context(), r, lg, cr, rule)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
lg.Error("can't get challenge", "err", err)
|
lg.Error("can't get challenge", "err", err)
|
||||||
|
algorithm := "unknown"
|
||||||
|
if rule.Challenge != nil {
|
||||||
|
algorithm = rule.Challenge.Algorithm
|
||||||
|
}
|
||||||
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
||||||
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm), makeCode(err))
|
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -248,9 +249,13 @@ func (s *Server) RenderIndex(w http.ResponseWriter, r *http.Request, cr policy.C
|
|||||||
|
|
||||||
impl, ok := challenge.Get(chall.Method)
|
impl, ok := challenge.Get(chall.Method)
|
||||||
if !ok {
|
if !ok {
|
||||||
lg.Error("check failed", "err", "can't get algorithm", "algorithm", rule.Challenge.Algorithm)
|
algorithm := "unknown"
|
||||||
|
if rule.Challenge != nil {
|
||||||
|
algorithm = rule.Challenge.Algorithm
|
||||||
|
}
|
||||||
|
lg.Error("check failed", "err", "can't get algorithm", "algorithm", algorithm)
|
||||||
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
s.ClearCookie(w, CookieOpts{Name: anubis.TestCookieName, Host: r.Host})
|
||||||
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), rule.Challenge.Algorithm), makeCode(err))
|
s.respondWithError(w, r, fmt.Sprintf("%s: %s", localizer.T("internal_server_error"), algorithm), makeCode(err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,7 +338,14 @@ func (s *Server) respondWithError(w http.ResponseWriter, r *http.Request, messag
|
|||||||
func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg, code string, status int) {
|
func (s *Server) respondWithStatus(w http.ResponseWriter, r *http.Request, msg, code string, status int) {
|
||||||
localizer := localization.GetLocalizer(r)
|
localizer := localization.GetLocalizer(r)
|
||||||
|
|
||||||
templ.Handler(web.Base(localizer.T("oh_noes"), web.ErrorPage(msg, s.opts.WebmasterEmail, code, localizer), s.policy.Impressum, localizer), templ.WithStatus(status)).ServeHTTP(w, r)
|
component := web.Base(
|
||||||
|
localizer.T("oh_noes"),
|
||||||
|
web.ErrorPage(msg, s.opts.WebmasterEmail, code, localizer),
|
||||||
|
s.policy.Impressum,
|
||||||
|
localizer,
|
||||||
|
)
|
||||||
|
handler := internal.NoStoreCache(templ.Handler(component, templ.WithStatus(status)))
|
||||||
|
handler.ServeHTTP(w, r)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/TecharoHQ/anubis"
|
"github.com/TecharoHQ/anubis"
|
||||||
|
"github.com/TecharoHQ/anubis/internal"
|
||||||
"github.com/TecharoHQ/anubis/lib/policy"
|
"github.com/TecharoHQ/anubis/lib/policy"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -191,3 +192,34 @@ func TestRenderIndexUnauthorized(t *testing.T) {
|
|||||||
t.Errorf("expected body %q, got %q", "Authorization required", body)
|
t.Errorf("expected body %q, got %q", "Authorization required", body)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNoCacheOnError(t *testing.T) {
|
||||||
|
pol := loadPolicies(t, "testdata/useragent.yaml", 0)
|
||||||
|
srv := spawnAnubis(t, Options{Policy: pol})
|
||||||
|
ts := httptest.NewServer(internal.RemoteXRealIP(true, "tcp", srv))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
for userAgent, expectedCacheControl := range map[string]string{
|
||||||
|
"DENY": "no-store",
|
||||||
|
"CHALLENGE": "no-store",
|
||||||
|
"ALLOW": "",
|
||||||
|
} {
|
||||||
|
t.Run(userAgent, func(t *testing.T) {
|
||||||
|
req, err := http.NewRequest(http.MethodGet, ts.URL, nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
req.Header.Set("User-Agent", userAgent)
|
||||||
|
|
||||||
|
resp, err := ts.Client().Do(req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.Header.Get("Cache-Control") != expectedCacheControl {
|
||||||
|
t.Errorf("wanted Cache-Control header %q, got %q", expectedCacheControl, resp.Header.Get("Cache-Control"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
{
|
||||||
|
"loading": "Зареждане...",
|
||||||
|
"why_am_i_seeing": "Защо виждам това?",
|
||||||
|
"protected_by": "Защитено от",
|
||||||
|
"protected_from": "От",
|
||||||
|
"made_with": "Направено с ❤️ в 🇨🇦",
|
||||||
|
"mascot_design": "Дизайн на талисмана от",
|
||||||
|
"ai_companies_explanation": "Виждате това, защото администраторът на този уебсайт е kонфигурирал Anubis, за да защити сървъра от агресивното събиране на данни от компании, занимаващи се с изкуствен интелект. Това може и причинява прекъсвания на уебсайтовете, което прави техните ресурси недостъпни за всички.",
|
||||||
|
"anubis_compromise": "Anubis е компромис. Anubis използва схема за ддоказателство-за-работа по подобие на Hashcash, предложена схема за доказателство-за-работа за намаляване на спама в имейлите. Идеята е, че при индивидуални мащаби допълнителното натоварване е пренебрежимо, но при масов ниво на събиране на данни то се натрупва и прави събирането на данни много по-скъпо.",
|
||||||
|
"hack_purpose": "В крайна сметка, това е временно решение, за да се отдели повече време за идентифициране и разпознаване на безглави браузъри (например чрез това как те рендират шрифтовете), така че страницата за доказателство-за-работа да не се налага да се показва на потребители, които е по-вероятно да са легитимни.",
|
||||||
|
"simplified_explanation": "Това е мярка срещу ботове и злонамерени заявки, подобна на CAPTCHA. Вместо да трябва да правите нещо сами, браузърът ви получава задача за изчисление, която трябва да реши, за да се увери, че е валиден клиент. Тази концепция се нарича схема доказателство-за-работа. Задачата се изчислява за няколко секунди и ви се дава достъп до уебсайта. Благодаря ви за разбирането и търпението.",
|
||||||
|
"jshelter_note": "Моля, имайте предвид, че Anubis изисква използването на модерни функции на JavaScript, сред които и като JShelter ще деактивират. Моля, деактивирайте JShelter или други подобни добавки за този домейн.",
|
||||||
|
"version_info": "Този уебсайт използва версия на Anubis",
|
||||||
|
"try_again": "Опитайте отново",
|
||||||
|
"go_home": "Отидете на началната страница",
|
||||||
|
"contact_webmaster": "или ако смятате, че не трябва да бъдете блокирани, моля свържете се с уебмастъра на",
|
||||||
|
"connection_security": "Моля, изчакайте, докато се уверим в сигурността на връзката ви",
|
||||||
|
"javascript_required": "За съжаление, трябва да включите JavaScript, за да минете през това предизвикателство. Това е необходимо, защото компаниите за изкуствен интелект промениха социалния договор около начина на хостинг на уебсайтове. Решение без JavaScript е в процес на разработка.",
|
||||||
|
"benchmark_requires_js": "За да използвате инструмента за тестване, е необходимо да включите JavaScript.",
|
||||||
|
"difficulty": "Трудност:",
|
||||||
|
"algorithm": "Алгоритъм:",
|
||||||
|
"compare": "Сравни:",
|
||||||
|
"time": "Време",
|
||||||
|
"iters": "Итерации",
|
||||||
|
"time_a": "Време А",
|
||||||
|
"iters_a": "Итерации А",
|
||||||
|
"time_b": "Време Б",
|
||||||
|
"iters_b": "Итерации Б",
|
||||||
|
"static_check_endpoint": "Това е просто краен пункт за проверка, който да използва обратният ви прокси.",
|
||||||
|
"authorization_required": "Изисква се авторизация",
|
||||||
|
"cookies_disabled": "Браузърът ви е настроен да деактивира бисквитките. Anubis изисква бисквитки за законния интерес да се увери, че сте валиден клиент. Моля, включете бисквитките за този домейн",
|
||||||
|
"access_denied": "Достъпът е отказан: код на грешка",
|
||||||
|
"dronebl_entry": "DroneBL докладва запис",
|
||||||
|
"see_dronebl_lookup": "вижте",
|
||||||
|
"internal_server_error": "Вътрешна сървърна грешка: администраторът е грешно конфигурирал Anubis. Моля, свържете се с администратора и ги помолете да проверят логовете около",
|
||||||
|
"invalid_redirect": "Невалидно пренасочване",
|
||||||
|
"redirect_not_parseable": "URL адресът за пренасочване не може да бъде разпознат",
|
||||||
|
"redirect_domain_not_allowed": "Домейнът за пренасочване не е позволен",
|
||||||
|
"missing_required_forwarded_headers": "Липсват необходимите X-Forwarded-* заглавни части",
|
||||||
|
"failed_to_sign_jwt": "неуспешно подписване на JWT",
|
||||||
|
"invalid_invocation": "Невалидно извикване на MakeChallenge",
|
||||||
|
"client_error_browser": "Крешка в клиента: Моля, уверете се, че браузърът ви е актуализиран и опитайте отново по-късно.",
|
||||||
|
"oh_noes": "О, не!",
|
||||||
|
"benchmarking_anubis": "Тестване на Anubis!",
|
||||||
|
"you_are_not_a_bot": "Ти не си бот!",
|
||||||
|
"making_sure_not_bot": "Уверяваме се, че не си бот!",
|
||||||
|
"celphase": "CELPHASE",
|
||||||
|
"js_web_crypto_error": "Браузърът ви няма функциониращ web.crypto елемент. Гледате ли това през сигурен контекст?",
|
||||||
|
"js_web_workers_error": "Браузърът ви не поддържа уеб работници (Anubis използва това, за да избегне замръзване на браузъра ви). Имате ли инсталирана добавка като JShelter?",
|
||||||
|
"js_cookies_error": "Браузърът ви не съхранява бисквитки. Anubis използва бисквитки, за да определи които клиенти са преминали задачите, като съхранява подписан токен в бисквитка. Моля, включете съхраняването на бисквитки за този домейн. Имената на бисквитките, съхранени от Anubis, могат да се променят без предварително уведомление. Имената и стойностите на бисквитките не са част от публичния API.",
|
||||||
|
"js_context_not_secure": "Вашият контекст не е сигурен!",
|
||||||
|
"js_context_not_secure_msg": "Опитайте да се свържете чрез HTTPS или уведомете администратора да kонфигурира HTTPS. За повече информация вижте MDN.",
|
||||||
|
"js_calculating": "Изчисляване...",
|
||||||
|
"js_missing_feature": "Липсваща функция",
|
||||||
|
"js_challenge_error": "Грешка при задачата!",
|
||||||
|
"js_challenge_error_msg": "Неуспешно разрешаване на алгоритъма за проверка. Може би искате да презаредите страницата.",
|
||||||
|
"js_calculating_difficulty": "Изчисляване... Трудност:",
|
||||||
|
"js_speed": "Скорост:",
|
||||||
|
"js_verification_longer": "Проверката отнема повече време от очакваното. Моля, не презареждайте страницата.",
|
||||||
|
"js_success": "Успех!",
|
||||||
|
"js_done_took": "Готово! Отне",
|
||||||
|
"js_iterations": "итерации",
|
||||||
|
"js_finished_reading": "Приключих с четенето, продължете →",
|
||||||
|
"js_calculation_error": "Грешка при изчислението!",
|
||||||
|
"js_calculation_error_msg": "Неуспешно изчисление на задачата:"
|
||||||
|
}
|
||||||
@@ -1,38 +1,38 @@
|
|||||||
{
|
{
|
||||||
"loading": "Ladevorgang...",
|
"loading": "Wird geladen …",
|
||||||
"why_am_i_seeing": "Warum sehe ich diese Seite?",
|
"why_am_i_seeing": "Warum sehe ich diese Seite?",
|
||||||
"protected_by": "Geschützt durch",
|
"protected_by": "Geschützt durch",
|
||||||
"protected_from": "Von",
|
"protected_from": "Von",
|
||||||
"made_with": "Mit ❤️ entwickelt in 🇨🇦",
|
"made_with": "Mit ❤️ entwickelt in 🇨🇦",
|
||||||
"mascot_design": "Maskottchen erstellt von",
|
"mascot_design": "Maskottchen entworfen von",
|
||||||
"ai_companies_explanation": "Diese Seite wird angezeigt, da der Betreiber der Website Anubis eingerichtet hat, um sie vor aggressiven Webcrawlern von KI-Unternehmen zu schützen. Diese können Ausfälle verursachen, wodurch die Website für niemanden erreichbar ist.",
|
"ai_companies_explanation": "Diese Seite wird angezeigt, weil der Betreiber dieser Website Anubis eingerichtet hat, um den Server vor aggressivem Scraping durch KI-Unternehmen zu schützen. Dieses Scraping kann Ausfälle verursachen, wodurch die Website für niemanden erreichbar ist.",
|
||||||
"anubis_compromise": "Anubis stellt einen Kompromiss dar. Es verwendet eine Proof-of-Work-Methode nach dem Hashcash-Prinzip, das ursprünglich zur Bekämpfung von E-Mail-Spam entwickelt wurde. Die Idee dahinter: Für einen einzelnen Besucher ist die Verzögerung vernachlässigbar, aber massenhaftes Scraping wird dadurch aufwändig und teuer.",
|
"anubis_compromise": "Anubis ist ein Kompromiss. Es verwendet ein Proof-of-Work-Verfahren nach dem Vorbild von Hashcash, das ursprünglich zur Reduzierung von E-Mail-Spam entwickelt wurde. Die Idee dahinter ist, dass die zusätzliche Last für einzelne Nutzer vernachlässigbar ist, sich aber auf der Ebene von Massen-Scrapern summiert und das Scraping deutlich teurer macht.",
|
||||||
"hack_purpose": "Letztendlich ist dies eine Übergangslösung, um mehr Zeit für Browser-Fingerprinting und die Identifizierung von Headless-Browsern (z. B. anhand ihrer Schriftwiedergabe) zu gewinnen. So muss die Proof-of-Work-Seite nicht Nutzern angezeigt werden, die sehr wahrscheinlich legitim sind.",
|
"hack_purpose": "Letztlich ist dies eine Übergangslösung, damit mehr Zeit in das Fingerprinting und die Erkennung von Headless-Browsern investiert werden kann (z. B. anhand ihrer Schriftart-Darstellung), sodass die Proof-of-Work-Seite Nutzern, die mit hoher Wahrscheinlichkeit legitim sind, nicht mehr angezeigt werden muss.",
|
||||||
"simplified_explanation": "Dies ist eine Maßnahme gegen Bots und bösartige Anfragen, ähnlich einem CAPTCHA. Anstatt jedoch selbst arbeiten zu müssen, erhält dein Browser eine Rechenaufgabe, um sicherzustellen, dass es sich um einen gültigen Client handelt. Dieses Konzept nennt sich <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Proof of Work</a>. Die Aufgabe wird in wenigen Sekunden berechnet und du erhältst Zugriff auf die Website. Danke für deine Geduld.",
|
"simplified_explanation": "Dies ist eine Schutzmaßnahme gegen Bots und schädliche Anfragen, ähnlich einem CAPTCHA. Anstatt selbst eine Aufgabe lösen zu müssen, bekommt dein Browser eine Rechenaufgabe, die er lösen muss, um sicherzustellen, dass es sich um einen gültigen Client handelt. Dieses Konzept nennt sich <a href=\"https://de.wikipedia.org/wiki/Proof_of_Work\">Proof of Work</a>. Die Aufgabe wird innerhalb weniger Sekunden berechnet und du erhältst Zugang zur Website. Danke für dein Verständnis und deine Geduld.",
|
||||||
"jshelter_note": "Anubis benötigt moderne JavaScript-Features, die von Plugins wie JShelter deaktiviert werden. Bitte deaktiviere JShelter oder ähnliche Plugins für diese Domain.",
|
"jshelter_note": "Anubis benötigt moderne JavaScript-Funktionen, die von Plugins wie JShelter deaktiviert werden. Bitte deaktiviere JShelter oder ähnliche Plugins für diese Domain.",
|
||||||
"version_info": "Diese Website läuft mit Anubis-Version",
|
"version_info": "Diese Website nutzt Anubis Version",
|
||||||
"try_again": "Erneut versuchen",
|
"try_again": "Erneut versuchen",
|
||||||
"go_home": "Zur Startseite",
|
"go_home": "Zur Startseite",
|
||||||
"contact_webmaster": "Falls du glaubst, dass es sich um einen Fehler handelt, kontaktiere bitte den Administrator unter",
|
"contact_webmaster": "oder kontaktiere den Webmaster unter, falls du glaubst, dass du nicht blockiert werden solltest:",
|
||||||
"connection_security": "Bitte warte einen Moment, während wir die Sicherheit deiner Verbindung prüfen.",
|
"connection_security": "Bitte warte einen Moment, während wir die Sicherheit deiner Verbindung überprüfen.",
|
||||||
"javascript_required": "Du musst JavaScript aktivieren, um diese Prüfung durchführen zu können. Dies ist notwendig, da KI-Unternehmen die bisherigen Regeln für das Hosting von Websites nicht mehr respektieren. Eine Lösung ohne JavaScript ist in Entwicklung.",
|
"javascript_required": "Du musst JavaScript aktivieren, um diese Prüfung zu bestehen. Dies ist notwendig, da KI-Unternehmen den Gesellschaftsvertrag rund um Webhosting verändert haben. Eine Lösung ohne JavaScript ist in Arbeit.",
|
||||||
"benchmark_requires_js": "Für die Nutzung des Benchmark-Tools muss JavaScript aktiviert sein.",
|
"benchmark_requires_js": "Für das Benchmark-Tool muss JavaScript aktiviert sein.",
|
||||||
"difficulty": "Schwierigkeit:",
|
"difficulty": "Schwierigkeit:",
|
||||||
"algorithm": "Algorithmus:",
|
"algorithm": "Algorithmus:",
|
||||||
"compare": "Vergleich:",
|
"compare": "Vergleichen:",
|
||||||
"time": "Zeit",
|
"time": "Zeit",
|
||||||
"iters": "Iterationen",
|
"iters": "Iterationen",
|
||||||
"time_a": "Zeit A",
|
"time_a": "Zeit A",
|
||||||
"iters_a": "Iterationen A",
|
"iters_a": "Iterationen A",
|
||||||
"time_b": "Zeit B",
|
"time_b": "Zeit B",
|
||||||
"iters_b": "Iterationen B",
|
"iters_b": "Iterationen B",
|
||||||
"static_check_endpoint": "Dies ist ein Endpunkt zur Prüfung durch einen Reverse-Proxy.",
|
"static_check_endpoint": "Dies ist nur ein Prüf-Endpunkt für deinen Reverse-Proxy.",
|
||||||
"authorization_required": "Autorisierung erforderlich",
|
"authorization_required": "Autorisierung erforderlich",
|
||||||
"cookies_disabled": "Cookies sind in deinem Browser deaktiviert. Anubis benötigt Cookies, um sicherzustellen, dass es sich um einen legitimen Zugriff handelt. Bitte aktiviere Cookies für diese Domain.",
|
"cookies_disabled": "Cookies sind in deinem Browser deaktiviert. Anubis benötigt Cookies im berechtigten Interesse, sicherzustellen, dass es sich um einen gültigen Client handelt. Bitte aktiviere Cookies für diese Domain.",
|
||||||
"access_denied": "Zugriff verweigert – Fehlercode",
|
"access_denied": "Zugriff verweigert: Fehlercode",
|
||||||
"dronebl_entry": "Eintrag in DroneBL",
|
"dronebl_entry": "DroneBL hat einen Eintrag gemeldet",
|
||||||
"see_dronebl_lookup": "anzeigen",
|
"see_dronebl_lookup": "anzeigen",
|
||||||
"internal_server_error": "Interner Serverfehler: Der Administrator hat Anubis fehlerhaft konfiguriert. Bitte kontaktiere den Administrator und bitte ihn, die Logs zu prüfen.",
|
"internal_server_error": "Interner Serverfehler: Der Administrator hat Anubis fehlerhaft konfiguriert. Bitte kontaktiere den Administrator und bitte ihn, die Logs im Zeitraum um folgenden Zeitpunkt zu prüfen:",
|
||||||
"invalid_redirect": "Ungültige Weiterleitung",
|
"invalid_redirect": "Ungültige Weiterleitung",
|
||||||
"redirect_not_parseable": "Weiterleitungs-URL kann nicht verarbeitet werden",
|
"redirect_not_parseable": "Weiterleitungs-URL kann nicht verarbeitet werden",
|
||||||
"redirect_domain_not_allowed": "Weiterleitungs-Domain nicht erlaubt",
|
"redirect_domain_not_allowed": "Weiterleitungs-Domain nicht erlaubt",
|
||||||
@@ -41,26 +41,26 @@
|
|||||||
"invalid_invocation": "Ungültiger Aufruf von MakeChallenge",
|
"invalid_invocation": "Ungültiger Aufruf von MakeChallenge",
|
||||||
"client_error_browser": "Client-Fehler: Bitte stelle sicher, dass dein Browser aktuell ist, und versuche es später erneut.",
|
"client_error_browser": "Client-Fehler: Bitte stelle sicher, dass dein Browser aktuell ist, und versuche es später erneut.",
|
||||||
"oh_noes": "Oh nein!",
|
"oh_noes": "Oh nein!",
|
||||||
"benchmarking_anubis": "Benchmark wird durchgeführt!",
|
"benchmarking_anubis": "Anubis-Benchmark wird durchgeführt!",
|
||||||
"you_are_not_a_bot": "Du bist kein Bot!",
|
"you_are_not_a_bot": "Du bist kein Bot!",
|
||||||
"making_sure_not_bot": "Dein Browser wird geprüft!",
|
"making_sure_not_bot": "Dein Browser wird geprüft!",
|
||||||
"celphase": "CELPHASE",
|
"celphase": "CELPHASE",
|
||||||
"js_web_crypto_error": "Dein Browser verfügt nicht über ein funktionierendes web.crypto-Element. Wird eine sichere Verbindung verwendet?",
|
"js_web_crypto_error": "Dein Browser verfügt nicht über ein funktionierendes web.crypto-Element. Wird diese Seite in einem sicheren Kontext angezeigt?",
|
||||||
"js_web_workers_error": "Dein Browser unterstützt keine Web-Worker (Anubis verwendet diese, damit der Browser nicht einfriert). Ist ein Plugin wie JShelter installiert?",
|
"js_web_workers_error": "Dein Browser unterstützt keine Web Workers (Anubis verwendet diese, damit dein Browser nicht einfriert). Hast du ein Plugin wie JShelter installiert?",
|
||||||
"js_cookies_error": "Dein Browser speichert keine Cookies. Anubis verwendet Cookies, um nach bestandener Prüfung ein signiertes Token abzulegen. Bitte aktiviere Cookies für diese Domain. Die Cookie-Namen von Anubis können sich jederzeit ändern. Cookie-Namen und gespeicherte Werte sind nicht Teil der öffentlichen API.",
|
"js_cookies_error": "Dein Browser speichert keine Cookies. Anubis verwendet Cookies, um nach bestandener Prüfung ein signiertes Token abzulegen. Bitte aktiviere Cookies für diese Domain. Die Cookie-Namen von Anubis können sich jederzeit ohne Vorankündigung ändern. Cookie-Namen und -Werte sind nicht Teil der öffentlichen API.",
|
||||||
"js_context_not_secure": "Diese Verbindung ist nicht sicher!",
|
"js_context_not_secure": "Diese Verbindung ist nicht sicher!",
|
||||||
"js_context_not_secure_msg": "Bitte versuche, dich über HTTPS zu verbinden, oder weise den Administrator darauf hin, HTTPS einzurichten. Mehr Informationen: <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
|
"js_context_not_secure_msg": "Versuche, dich über HTTPS zu verbinden, oder informiere den Administrator, HTTPS einzurichten. Weitere Informationen unter <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
|
||||||
"js_calculating": "Berechnung läuft...",
|
"js_calculating": "Berechnung läuft …",
|
||||||
"js_missing_feature": "Fehlendes Feature",
|
"js_missing_feature": "Fehlendes Feature",
|
||||||
"js_challenge_error": "Prüfung fehlgeschlagen!",
|
"js_challenge_error": "Prüfung fehlgeschlagen!",
|
||||||
"js_challenge_error_msg": "Der Prüf-Algorithmus konnte nicht geladen werden. Bitte lade die Seite neu.",
|
"js_challenge_error_msg": "Der Prüfalgorithmus konnte nicht aufgelöst werden. Bitte lade die Seite neu.",
|
||||||
"js_calculating_difficulty": "Berechnung läuft...<br/>Schwierigkeit:",
|
"js_calculating_difficulty": "Berechnung läuft …<br/>Schwierigkeit:",
|
||||||
"js_speed": "Geschwindigkeit:",
|
"js_speed": "Geschwindigkeit:",
|
||||||
"js_verification_longer": "Die Prüfung dauert länger als erwartet. Bitte warte und lade die Seite nicht neu.",
|
"js_verification_longer": "Die Verifizierung dauert länger als erwartet. Bitte bleibe auf der Seite und lade sie nicht neu.",
|
||||||
"js_success": "Erfolgreich!",
|
"js_success": "Geschafft!",
|
||||||
"js_done_took": "Fertig! Dauer:",
|
"js_done_took": "Fertig! Dauer:",
|
||||||
"js_iterations": "Iterationen",
|
"js_iterations": "Iterationen",
|
||||||
"js_finished_reading": "Fertig gelesen – weiter zur Seite →",
|
"js_finished_reading": "Fertig gelesen, weiter zur Seite →",
|
||||||
"js_calculation_error": "Berechnungsfehler!",
|
"js_calculation_error": "Berechnungsfehler!",
|
||||||
"js_calculation_error_msg": "Fehler bei der Berechnung der Prüfung:"
|
"js_calculation_error_msg": "Fehler bei der Berechnung der Prüfung:"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,66 +1,66 @@
|
|||||||
{
|
{
|
||||||
"loading": "Chargement...",
|
"loading": "Chargement...",
|
||||||
"why_am_i_seeing": "Pourquoi je vois ceci ?",
|
"why_am_i_seeing": "Comment suis-je arrivé·e ici ?",
|
||||||
"protected_by": "Protégé par",
|
"protected_by": "Protégé par",
|
||||||
"protected_from": "From",
|
"protected_from": "de",
|
||||||
"made_with": "Fait avec ❤️ au 🇨🇦",
|
"made_with": "Fait avec ❤️ au 🇨🇦",
|
||||||
"mascot_design": "Design de la mascotte par",
|
"mascot_design": "Design de la mascotte par",
|
||||||
"ai_companies_explanation": "Vous voyez ceci car l'administrateur de ce site web a configuré Anubis pour protéger le serveur contre le fléau des entreprises d'IA qui scrapent agressivement les sites web. Cela peut et cause des temps d'arrêt pour les sites web, ce qui rend leurs ressources inaccessibles pour tout le monde.",
|
"ai_companies_explanation": "Vous voyez cette page car l'administrateur·rice de ce site Web a configuré Anubis pour protéger le serveur contre le fléau des entreprises d'IA qui récupèrent agressivement le contenu des sites Web. Cela perturbe leur fonctionnement et rend leurs ressources inaccessibles pour tout le monde.",
|
||||||
"anubis_compromise": "Anubis est un compromis. Anubis utilise un schéma de Preuve de Travail dans la veine de Hashcash, un schéma de preuve de travail proposé pour réduire le spam par email. L'idée est qu'à l'échelle individuelle, la charge supplémentaire est négligeable, mais à l'échelle des scrapers de masse, cela s'accumule et rend le scraping beaucoup plus coûteux.",
|
"anubis_compromise": "Anubis est un compromis. Anubis utilise un procédé de preuve de travail similaire à Hashcash, un procédé de preuve de travail proposé pour réduire le spam par e-mail. L'idée est qu'à l'échelle individuelle, la charge supplémentaire est négligeable, mais à l'échelle des scrapers de masse, la charge s'accumule et le scraping devient beaucoup plus coûteux.",
|
||||||
"hack_purpose": "En fin de compte, il s'agit d'une solution de substitution afin de consacrer plus de temps à l'identification et à l'empreinte digitale des navigateurs sans tête (par exemple, via leur rendu de police) afin que la page de preuve de travail du défi n'ait pas besoin d'être présentée aux utilisateurs qui sont beaucoup plus susceptibles d'être légitimes.",
|
"hack_purpose": "En fin de compte, il s'agit d'une solution de substitution permettant de consacrer plus de temps à l'identification et à la prise d'empreintes des navigateurs headless (par exemple, en reconnaissant leur rendu des polices), pour que, à terme, la page de défi utilisant la preuve de travail n'ait plus besoin d'être présentée aux utilisateur·rices qui sont beaucoup plus susceptibles d'être légitimes.",
|
||||||
"jshelter_note": "Veuillez noter qu'Anubis nécessite l'utilisation de fonctionnalités JavaScript modernes que des plugins comme JShelter désactiveront. Veuillez désactiver JShelter ou d'autres plugins similaires pour ce domaine.",
|
"jshelter_note": "Veuillez noter qu'Anubis nécessite l'utilisation de fonctionnalités JavaScript modernes qui peuvent être désactivées par des plugins comme JShelter. Veuillez désactiver JShelter ou tout autre plugin similaire pour ce domaine.",
|
||||||
"version_info": "Ce site web utilise Anubis version",
|
"version_info": "Ce site Web utilise Anubis version",
|
||||||
"try_again": "Réessayer",
|
"try_again": "Réessayer",
|
||||||
"go_home": "Accueil",
|
"go_home": "Accueil",
|
||||||
"contact_webmaster": "ou si vous pensez que vous ne devriez pas être bloqué, veuillez contacter le webmaster à",
|
"contact_webmaster": "ou si vous pensez que vous ne devriez pas être bloqué, veuillez contacter le webmaster à l'adresse",
|
||||||
"connection_security": "Veuillez patienter un instant pendant que nous assurons la sécurité de votre connexion.",
|
"connection_security": "Veuillez patienter un instant pendant que nous assurons la sécurité de votre connexion.",
|
||||||
"javascript_required": "Malheureusement, vous devez activer JavaScript pour passer ce défi. Ceci est requis car les entreprises d'IA ont changé le contrat social autour du fonctionnement de l'hébergement de sites web. Une solution sans JS est en cours de développement.",
|
"javascript_required": "Malheureusement, vous devez activer JavaScript pour passer cette page de défi. Cette obligation est imposée par les entreprises d'IA, qui ont décidé de modifier unilatéralement les termes du contrat social régissant l'hébergement de sites Web. Une solution sans JavaScript est en cours de développement.",
|
||||||
"benchmark_requires_js": "L'exécution de l'outil de benchmark nécessite l'activation de JavaScript.",
|
"benchmark_requires_js": "L'exécution de l'outil de benchmark nécessite l'activation de JavaScript.",
|
||||||
"difficulty": "Difficulté :",
|
"difficulty": "Difficulté :",
|
||||||
"algorithm": "Algorithme :",
|
"algorithm": "Algorithme :",
|
||||||
"compare": "Comparer :",
|
"compare": "Comparer :",
|
||||||
"time": "Temps",
|
"time": "Temps",
|
||||||
"iters": "Itérations",
|
"iters": "Itérations",
|
||||||
"time_a": "Temps A",
|
"time_a": "Temps A",
|
||||||
"iters_a": "Itér. A",
|
"iters_a": "Itér. A",
|
||||||
"time_b": "Temps B",
|
"time_b": "Temps B",
|
||||||
"iters_b": "Itér. B",
|
"iters_b": "Itér. B",
|
||||||
"static_check_endpoint": "Ceci est juste un point de terminaison de vérification pour votre proxy inverse à utiliser.",
|
"static_check_endpoint": "Ceci est juste un point de terminaison de vérification à utiliser par votre proxy inverse.",
|
||||||
"authorization_required": "Autorisation requise",
|
"authorization_required": "Autorisation requise",
|
||||||
"cookies_disabled": "Votre navigateur est configuré pour désactiver les cookies. Anubis nécessite des cookies pour l'intérêt légitime de s'assurer que vous êtes un client valide. Veuillez activer les cookies pour ce domaine",
|
"cookies_disabled": "Les cookies sont désactivés dans votre navigateur. Anubis a recours aux cookies pour l'intérêt légitime de s'assurer que vous êtes un client valide. Veuillez activer les cookies pour ce domaine.",
|
||||||
"access_denied": "Accès refusé : code d'erreur",
|
"access_denied": "Accès refusé : code d'erreur",
|
||||||
"dronebl_entry": "DroneBL a signalé une entrée",
|
"dronebl_entry": "DroneBL a rapporté une entrée",
|
||||||
"see_dronebl_lookup": "voir",
|
"see_dronebl_lookup": "voir",
|
||||||
"internal_server_error": "Erreur interne du serveur : l'administrateur a mal configuré Anubis. Veuillez contacter l'administrateur et lui demander de consulter les logs autour de",
|
"internal_server_error": "Erreur interne du serveur : l'administrateur·rice a mal configuré Anubis. Veuillez contacter l'administrateur·rice et lui demander de consulter les logs autour de",
|
||||||
"invalid_redirect": "Redirection invalide",
|
"invalid_redirect": "Redirection invalide",
|
||||||
"redirect_not_parseable": "URL de redirection non analysable",
|
"redirect_not_parseable": "URL de redirection non analysable",
|
||||||
"redirect_domain_not_allowed": "Domaine de redirection non autorisé",
|
"redirect_domain_not_allowed": "Domaine de redirection non autorisé",
|
||||||
"failed_to_sign_jwt": "échec de la signature JWT",
|
"failed_to_sign_jwt": "échec de la signature du JWT",
|
||||||
"invalid_invocation": "Invocation invalide de MakeChallenge",
|
"invalid_invocation": "Invocation invalide de MakeChallenge",
|
||||||
"client_error_browser": "Erreur client : Veuillez vous assurer que votre navigateur est à jour et réessayez plus tard.",
|
"client_error_browser": "Erreur client : Veuillez vous assurer que votre navigateur est à jour et réessayez plus tard.",
|
||||||
"oh_noes": "Oh non !",
|
"oh_noes": "Oh non !",
|
||||||
"benchmarking_anubis": "Test de performance d'Anubis !",
|
"benchmarking_anubis": "Je vérifie les performances d'Anubis !",
|
||||||
"you_are_not_a_bot": "Vous n'êtes pas un robot !",
|
"you_are_not_a_bot": "Vous n'êtes pas un robot !",
|
||||||
"making_sure_not_bot": "Vérification que vous n'êtes pas un robot !",
|
"making_sure_not_bot": "Je m'assure que vous n'êtes pas un robot !",
|
||||||
"celphase": "PHASE de CEL",
|
"celphase": "CELPHASE",
|
||||||
"js_web_crypto_error": "Votre navigateur n'a pas d'élément web.crypto fonctionnel. Consultez-vous cette page dans un contexte sécurisé ?",
|
"js_web_crypto_error": "L'élément web.crypto de votre navigateur n'est pas fonctionnel. Consultez-vous bien cette page dans un contexte sécurisé ?",
|
||||||
"js_web_workers_error": "Votre navigateur ne prend pas en charge les web workers (Anubis les utilise pour éviter de bloquer votre navigateur). Avez-vous un plugin comme JShelter installé ?",
|
"js_web_workers_error": "Votre navigateur ne prend pas en charge les web workers (Anubis les utilise pour éviter de bloquer votre navigateur). Avez-vous installé un plugin comme JShelter ?",
|
||||||
"js_cookies_error": "Votre navigateur ne stocke pas les cookies. Anubis utilise des cookies pour déterminer quels clients ont réussi les défis en stockant un jeton signé dans un cookie. Veuillez activer le stockage des cookies pour ce domaine. Les noms des cookies qu'Anubis stocke peuvent varier sans préavis. Les noms et valeurs des cookies ne font pas partie de l'API publique.",
|
"js_cookies_error": "Votre navigateur ne stocke pas les cookies. Anubis a recours aux cookies pour déterminer quels clients ont réussi les défis en stockant un jeton signé dans un cookie. Veuillez activer le stockage des cookies pour ce domaine. Le nom des cookies stockés par Anubis peut varier à tout moment. Le nom et la valeur des cookies ne font pas partie de l'API publique.",
|
||||||
"js_context_not_secure": "Votre contexte n'est pas sécurisé !",
|
"js_context_not_secure": "Votre contexte n'est pas sécurisé !",
|
||||||
"js_context_not_secure_msg": "Essayez de vous connecter via HTTPS ou informez l'administrateur de configurer HTTPS. Pour plus d'informations, voir <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
|
"js_context_not_secure_msg": "Essayez de vous connecter via HTTPS ou demandez à l'administrateur·rice de configurer HTTPS. Pour plus d'informations, consultez <a href=\"https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts#when_is_a_context_considered_secure\">MDN</a>.",
|
||||||
"js_calculating": "Calcul en cours...",
|
"js_calculating": "Calcul en cours...",
|
||||||
"js_missing_feature": "Fonctionnalité manquante",
|
"js_missing_feature": "Fonctionnalité manquante",
|
||||||
"js_challenge_error": "Erreur de défi !",
|
"js_challenge_error": "Erreur de défi !",
|
||||||
"js_challenge_error_msg": "Échec de la résolution de l'algorithme de vérification. Vous pouvez essayer de recharger la page.",
|
"js_challenge_error_msg": "Échec de la résolution de l'algorithme de vérification. Vous pouvez essayer de recharger la page.",
|
||||||
"js_calculating_difficulty": "Calcul en cours...<br/>Difficulté :",
|
"js_calculating_difficulty": "Calcul en cours...<br/>Difficulté :",
|
||||||
"js_speed": "Vitesse :",
|
"js_speed": "Vitesse :",
|
||||||
"js_verification_longer": "La vérification prend plus de temps que prévu. Veuillez ne pas actualiser la page.",
|
"js_verification_longer": "La vérification prend plus de temps que prévu. Veuillez ne pas actualiser la page.",
|
||||||
"js_success": "Succès !",
|
"js_success": "Vérification réussie !",
|
||||||
"js_done_took": "Terminé ! A pris",
|
"js_done_took": "Terminé ! Cela aura nécessité",
|
||||||
"js_iterations": "itérations",
|
"js_iterations": "itérations",
|
||||||
"js_finished_reading": "J'ai fini de lire, continuer →",
|
"js_finished_reading": "J'ai fini de lire, continuer →",
|
||||||
"js_calculation_error": "Erreur de calcul !",
|
"js_calculation_error": "Erreur de calcul !",
|
||||||
"js_calculation_error_msg": "Échec du calcul du défi :",
|
"js_calculation_error_msg": "Échec du calcul du défi :",
|
||||||
"missing_required_forwarded_headers": "En-têtes X-Forwarded-* requis manquants",
|
"missing_required_forwarded_headers": "En-têtes X-Forwarded-* manquants",
|
||||||
"simplified_explanation": "Il s'agit d'une mesure contre les robots et les requêtes malveillantes similaire à un CAPTCHA. Cependant, au lieu d'avoir à faire le travail vous-même, votre navigateur se voit confier une tâche de calcul qu'il doit résoudre pour s'assurer qu'il est un client valide. Ce concept s'appelle <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Preuve de travail</a>. La tâche est calculée en quelques secondes et vous avez accès au site Web. Merci de votre compréhension et de votre patience."
|
"simplified_explanation": "Ceci est une mesure contre les robots et les requêtes malveillantes, similaire à un CAPTCHA. Cependant, au lieu d'avoir à faire le travail vous-même, votre navigateur se voit confier une tâche de calcul qu'il doit résoudre pour confirmer qu'il est un client valide. Ce concept est nommé <a href=\"https://en.wikipedia.org/wiki/Proof_of_work\">Preuve de travail</a>. La tâche s'effectue en quelques secondes, puis vous avez accès au site Web. Merci pour votre compréhension et votre patience."
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"anubis_compromise": "Anubisは妥協策です。AnubisはHashcashのようなProof-of-Work方式を採用しており、これは元々メールスパムを減らすために提案された仕組みです。個人レベルでは追加の負荷は無視できる程度ですが、大規模なスクレイピングでは負荷が積み重なり、スクレイピングのコストが大幅に増加します。",
|
"anubis_compromise": "Anubisは妥協策です。AnubisはHashcashのようなProof-of-Work方式を採用しており、これは元々メールスパムを減らすために提案された仕組みです。個人レベルでは追加の負荷は無視できる程度ですが、大規模なスクレイピングでは負荷が積み重なり、スクレイピングのコストが大幅に増加します。",
|
||||||
"hack_purpose": "最終的に、これはヘッドレスブラウザのフィンガープリントと識別に時間を費やすためのプレースホルダーソリューションです(例:フォントレンダリングの方法による)。これにより、正当なユーザーにはチャレンジのプルーフオブワークページを提示する必要がなくなります。",
|
"hack_purpose": "最終的に、これはヘッドレスブラウザのフィンガープリントと識別に時間を費やすためのプレースホルダーソリューションです(例:フォントレンダリングの方法による)。これにより、正当なユーザーにはチャレンジのプルーフオブワークページを提示する必要がなくなります。",
|
||||||
"jshelter_note": "Anubisは、JShelterのようなプラグインが無効化する最新のJavaScript機能を必要とします。このドメインではJShelterや同様のプラグインを無効にしてください。",
|
"jshelter_note": "Anubisは、JShelterのようなプラグインが無効化する最新のJavaScript機能を必要とします。このドメインではJShelterや同様のプラグインを無効にしてください。",
|
||||||
"version_info": "このウェブサイトはAnubisバージョンで動作しています",
|
"version_info": "このウェブサイトはAnubisで動作しています バージョン",
|
||||||
"try_again": "再試行",
|
"try_again": "再試行",
|
||||||
"go_home": "ホームに戻る",
|
"go_home": "ホームに戻る",
|
||||||
"contact_webmaster": "もしブロックされるべきでないと思われる場合は、ウェブマスターにご連絡ください:",
|
"contact_webmaster": "もしブロックされるべきでないと思われる場合は、ウェブマスターにご連絡ください:",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user