mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-05 16:28:17 +00:00
Compare commits
196 Commits
Xe-patch-1
...
revert-137
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b44b29013 | ||
|
|
ebad69a4e1 | ||
|
|
71147b4857 | ||
|
|
cee7871ef8 | ||
|
|
26d258fb94 | ||
|
|
80a8e0a8ae | ||
|
|
359613f35a | ||
|
|
1d8e98c5ec | ||
|
|
880020095c | ||
|
|
f5728e96a1 | ||
|
|
bcf525dbcf | ||
|
|
d748dc9da8 | ||
|
|
9b210d795e | ||
|
|
e084e5011e | ||
|
|
2532478abd | ||
|
|
6d9c0abe74 | ||
|
|
a37068a423 | ||
|
|
9d9be61c24 | ||
|
|
535ed74b17 | ||
|
|
ba8a1b7caf | ||
|
|
40afc13d7f | ||
|
|
122e4bc072 | ||
|
|
cb91145352 | ||
|
|
5c97d693c1 | ||
|
|
988906bb79 | ||
|
|
9c54aa852f | ||
|
|
cb689ee55b | ||
|
|
071b836741 | ||
|
|
bbdeee00f7 | ||
|
|
21d7753b1c | ||
|
|
43b8658bfd | ||
|
|
00fa939acf | ||
|
|
4ead3ed16e | ||
|
|
1f9c2272e6 | ||
|
|
b11d8132dd | ||
|
|
f032d5d0ac | ||
|
|
a709a2b2da | ||
|
|
18d2b4ffff | ||
|
|
02989f03d0 | ||
|
|
69e9023cbb | ||
|
|
1d91bc99f2 | ||
|
|
c70b939651 | ||
|
|
b5c5e07fc2 | ||
|
|
26fd86bb9a | ||
|
|
0258f6b59c | ||
|
|
56170e4af5 | ||
|
|
9dd4de6f1f | ||
|
|
da1890380e | ||
|
|
6c8629e3ac | ||
|
|
f6bf98fa28 | ||
|
|
97ba84e26d | ||
|
|
68fcc0c44f | ||
|
|
6a7f80e6f5 | ||
|
|
a5bb6d2751 | ||
|
|
1e298f5d0e | ||
|
|
a4770956a8 | ||
|
|
316905bf1d | ||
|
|
1a12171d74 | ||
|
|
4f50d3245e | ||
|
|
49c9333359 | ||
|
|
c7e4cd1032 | ||
|
|
3f81076743 | ||
|
|
115f24c33d | ||
|
|
b836506785 | ||
|
|
cb67c54ac5 | ||
|
|
b5ead0a68c | ||
|
|
df217d61c8 | ||
|
|
cc1d79aec6 | ||
|
|
4d1d7c39eb | ||
|
|
83a83e9691 | ||
|
|
531e1dd7f4 | ||
|
|
59f1e36167 | ||
|
|
62c1b80189 | ||
|
|
7ed1753fcc | ||
|
|
3dab060bfa | ||
|
|
ab8b91fc0c | ||
|
|
168e72088f | ||
|
|
6b1cd6120f | ||
|
|
d7459de941 | ||
|
|
c96c229b68 | ||
|
|
b384ad03cb | ||
|
|
a4efcef1c9 | ||
|
|
2fc3765340 | ||
|
|
ec2981bf6f | ||
|
|
e3d3195bf2 | ||
|
|
25d677cbba | ||
|
|
00261d049e | ||
|
|
a12b4bb755 | ||
|
|
4dfc73abd1 | ||
|
|
ffbbdce3da | ||
|
|
c09c86778d | ||
|
|
9c47c180d0 | ||
|
|
d51d32726c | ||
|
|
ff33982ee9 | ||
|
|
ec90a8b87d | ||
|
|
5731477e0a | ||
|
|
714c85dbc4 | ||
|
|
75ea1b60d5 | ||
|
|
1cf03535a5 | ||
|
|
c3ed405dbc | ||
|
|
8cdf58c9e6 | ||
|
|
1c170988c8 | ||
|
|
9439466ff2 | ||
|
|
4787aeca51 | ||
|
|
fb3637df95 | ||
|
|
26076b8520 | ||
|
|
edb84f03b7 | ||
|
|
b2d525bba4 | ||
|
|
00679aed66 | ||
|
|
03299024c5 | ||
|
|
f745d37d90 | ||
|
|
d12993e31d | ||
|
|
88b3e457ee | ||
|
|
bb2b113b63 | ||
|
|
6c283d0cd9 | ||
|
|
0037e214a1 | ||
|
|
29ae2a4b87 | ||
|
|
401e18f29f | ||
|
|
63591866aa | ||
|
|
f79d36d21e | ||
|
|
f5b5243b5e | ||
|
|
2011b83a44 | ||
|
|
8ed89a6c6e | ||
|
|
9430d0e6a5 | ||
|
|
8b9dafac51 | ||
|
|
9997130a7c | ||
|
|
e239083944 | ||
|
|
abf6c8de57 | ||
|
|
7e1b5d9951 | ||
|
|
98945fb56f | ||
|
|
82099d9e05 | ||
|
|
87c2f1e0e6 | ||
|
|
f0199d014f | ||
|
|
75109f6b73 | ||
|
|
c43d7ca686 | ||
|
|
5d5c39e123 | ||
|
|
d35e47c655 | ||
|
|
48b49a0190 | ||
|
|
de94139789 | ||
|
|
fd011d19e2 | ||
|
|
489abb6b4d | ||
|
|
8da0771647 | ||
|
|
f0bcbe43af | ||
|
|
f6e077c907 | ||
|
|
2704ba95d0 | ||
|
|
f6a578787f | ||
|
|
31a654ecb6 | ||
|
|
1a4b5cadcb | ||
|
|
d5cdd21631 | ||
|
|
0e0847cbeb | ||
|
|
00afa72c4b | ||
|
|
eb50f59351 | ||
|
|
01f55cf552 | ||
|
|
99bd06b8c3 | ||
|
|
d6f1f24e1b | ||
|
|
6a5485fde9 | ||
|
|
582181f9b9 | ||
|
|
44264981b5 | ||
|
|
21c3e0c469 | ||
|
|
9ddc1eb840 | ||
|
|
c661bc37d1 | ||
|
|
fb8ce508ee | ||
|
|
573b0079fb | ||
|
|
d1d631a18a | ||
|
|
f3cd6c9ca4 | ||
|
|
23772fd3cb | ||
|
|
a7a61690fc | ||
|
|
f5afe8b6c8 | ||
|
|
61682e4987 | ||
|
|
b0fa256e3e | ||
|
|
ee55d857eb | ||
|
|
993ea8da1b | ||
|
|
6e4e471792 | ||
|
|
e8dfff6350 | ||
|
|
237a6a98e2 | ||
|
|
e43999f30c | ||
|
|
29d038835f | ||
|
|
39215457e4 | ||
|
|
ff691dfee8 | ||
|
|
83503525f2 | ||
|
|
a8b7b2ad7b | ||
|
|
87651f9506 | ||
|
|
100005ce70 | ||
|
|
0a68415c2e | ||
|
|
b3886752a1 | ||
|
|
0e9f831201 | ||
|
|
22ee227f20 | ||
|
|
adda60c163 | ||
|
|
e0a15bf4dc | ||
|
|
f6481b81a2 | ||
|
|
790bcbe773 | ||
|
|
7c80c23e90 | ||
|
|
2d8e942377 | ||
|
|
d5f01dbdb9 | ||
|
|
70bf58cc63 | ||
|
|
0dccf2e009 |
@@ -23,7 +23,12 @@
|
||||
"a-h.templ",
|
||||
"redhat.vscode-yaml",
|
||||
"streetsidesoftware.code-spell-checker"
|
||||
]
|
||||
],
|
||||
"settings": {
|
||||
"chat.instructionsFilesLocations": {
|
||||
".github/copilot-instructions.md": true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
3
.github/FUNDING.yml
vendored
3
.github/FUNDING.yml
vendored
@@ -1,2 +1,3 @@
|
||||
patreon: cadey
|
||||
github: xe
|
||||
github: xe
|
||||
liberapay: Xe
|
||||
|
||||
61
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
61
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
name: Bug report
|
||||
description: Create a report to help us improve
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
id: description-of-bug
|
||||
attributes:
|
||||
label: Describe the bug
|
||||
description: A clear and concise description of what the bug is.
|
||||
placeholder: I can reliably get an error when...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: steps-to-reproduce
|
||||
attributes:
|
||||
label: Steps to reproduce
|
||||
description: |
|
||||
Steps to reproduce the behavior.
|
||||
placeholder: |
|
||||
1. Go to the following url...
|
||||
2. Click on...
|
||||
3. You get the following error: ...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: expected-behavior
|
||||
attributes:
|
||||
label: Expected behavior
|
||||
description: |
|
||||
A clear and concise description of what you expected to happen.
|
||||
Ideally also describe *why* you expect it to happen.
|
||||
placeholder: Instead of displaying an error, it would...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version-os
|
||||
attributes:
|
||||
label: Your operating system and its version.
|
||||
description: Unsure? Visit https://whatsmyos.com/
|
||||
placeholder: Android 13
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: input
|
||||
id: version-browser
|
||||
attributes:
|
||||
label: Your browser and its version.
|
||||
description: Unsure? Visit https://www.whatsmybrowser.org/
|
||||
placeholder: Firefox 142
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context about the problem here.
|
||||
|
||||
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
5
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
blank_issues_enabled: false
|
||||
contact_links:
|
||||
- name: Security
|
||||
url: https://techaro.lol/contact
|
||||
about: Do not file security reports here. Email security@techaro.lol.
|
||||
39
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
39
.github/ISSUE_TEMPLATE/feature_request.yaml
vendored
Normal file
@@ -0,0 +1,39 @@
|
||||
name: Feature request
|
||||
description: Suggest an idea for this project
|
||||
title: '[Feature request] '
|
||||
|
||||
body:
|
||||
- type: textarea
|
||||
id: description-of-bug
|
||||
attributes:
|
||||
label: Is your feature request related to a problem? Please describe.
|
||||
description: A clear and concise description of what the problem is that made you submit this report.
|
||||
placeholder: I am always frustrated, when...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: description-of-solution
|
||||
attributes:
|
||||
label: Solution you would like.
|
||||
description: A clear and concise description of what you want to happen.
|
||||
placeholder: Instead of behaving like this, there should be...
|
||||
validations:
|
||||
required: true
|
||||
|
||||
- type: textarea
|
||||
id: alternatives
|
||||
attributes:
|
||||
label: Describe alternatives you have considered.
|
||||
description: A clear and concise description of any alternative solutions or features you have considered.
|
||||
placeholder: Another workaround that would work, is...
|
||||
validations:
|
||||
required: false
|
||||
|
||||
- type: textarea
|
||||
id: additional-context
|
||||
attributes:
|
||||
label: Additional context
|
||||
description: Add any other context (such as mock-ups, proof of concepts or screenshots) about the feature request here.
|
||||
validations:
|
||||
required: false
|
||||
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
1
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -9,3 +9,4 @@ Checklist:
|
||||
- [ ] 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)
|
||||
- [ ] 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)
|
||||
|
||||
20
.github/actions/spelling/allow.txt
vendored
20
.github/actions/spelling/allow.txt
vendored
@@ -4,4 +4,22 @@ ssh
|
||||
ubuntu
|
||||
workarounds
|
||||
rjack
|
||||
msgbox
|
||||
msgbox
|
||||
xeact
|
||||
ABee
|
||||
tencent
|
||||
maintnotifications
|
||||
azurediamond
|
||||
cooldown
|
||||
verifyfcrdns
|
||||
Spintax
|
||||
spintax
|
||||
clampip
|
||||
pseudoprofound
|
||||
reimagining
|
||||
iocaine
|
||||
admins
|
||||
fout
|
||||
iplist
|
||||
NArg
|
||||
blocklists
|
||||
|
||||
11
.github/actions/spelling/excludes.txt
vendored
11
.github/actions/spelling/excludes.txt
vendored
@@ -87,9 +87,14 @@
|
||||
^docs/docs/user/known-instances.md$
|
||||
^docs/manifest/.*$
|
||||
^docs/static/\.nojekyll$
|
||||
^internal/glob/glob_test.go$
|
||||
^internal/honeypot/naive/affirmations\.txt$
|
||||
^internal/honeypot/naive/spintext\.txt$
|
||||
^internal/honeypot/naive/titles\.txt$
|
||||
^lib/config/testdata/bad/unparseable\.json$
|
||||
^lib/localization/.*_test.go$
|
||||
^lib/localization/locales/.*\.json$
|
||||
^lib/policy/config/testdata/bad/unparseable\.json$
|
||||
^test/.*$
|
||||
ignore$
|
||||
robots.txt
|
||||
^lib/localization/locales/.*\.json$
|
||||
^lib/localization/.*_test.go$
|
||||
^test/.*$
|
||||
|
||||
75
.github/actions/spelling/expect.txt
vendored
75
.github/actions/spelling/expect.txt
vendored
@@ -1,12 +1,19 @@
|
||||
acs
|
||||
Actorified
|
||||
actorifiedstore
|
||||
actorify
|
||||
Aibrew
|
||||
alibaba
|
||||
alrest
|
||||
amazonbot
|
||||
anthro
|
||||
anubis
|
||||
anubistest
|
||||
apnic
|
||||
APNICRANDNETAU
|
||||
Applebot
|
||||
archlinux
|
||||
arpa
|
||||
asnc
|
||||
asnchecker
|
||||
asns
|
||||
@@ -17,6 +24,7 @@ badregexes
|
||||
bbolt
|
||||
bdba
|
||||
berr
|
||||
bezier
|
||||
bingbot
|
||||
Bitcoin
|
||||
bitrate
|
||||
@@ -29,6 +37,7 @@ botstopper
|
||||
BPort
|
||||
Brightbot
|
||||
broked
|
||||
buildah
|
||||
byteslice
|
||||
Bytespider
|
||||
cachebuster
|
||||
@@ -52,18 +61,20 @@ checkresult
|
||||
chibi
|
||||
cidranger
|
||||
ckie
|
||||
ckies
|
||||
cloudflare
|
||||
Codespaces
|
||||
confd
|
||||
connnection
|
||||
containerbuild
|
||||
containerregistry
|
||||
coreutils
|
||||
Cotoyogi
|
||||
Cromite
|
||||
crt
|
||||
Cscript
|
||||
daemonizing
|
||||
databento
|
||||
dayjob
|
||||
DDOS
|
||||
Debian
|
||||
debrpm
|
||||
@@ -76,11 +87,13 @@ distros
|
||||
dnf
|
||||
dnsbl
|
||||
dnserr
|
||||
DNSTTL
|
||||
domainhere
|
||||
dracula
|
||||
dronebl
|
||||
droneblresponse
|
||||
dropin
|
||||
dsilence
|
||||
duckduckbot
|
||||
eerror
|
||||
ellenjoe
|
||||
@@ -96,14 +109,19 @@ externalfetcher
|
||||
extldflags
|
||||
facebookgo
|
||||
Factset
|
||||
fahedouch
|
||||
fastcgi
|
||||
FCr
|
||||
fcrdns
|
||||
fediverse
|
||||
ffprobe
|
||||
financials
|
||||
finfos
|
||||
Firecrawl
|
||||
flagenv
|
||||
Fordola
|
||||
forgejo
|
||||
forwardauth
|
||||
fsys
|
||||
fullchain
|
||||
gaissmai
|
||||
@@ -112,8 +130,10 @@ geoip
|
||||
geoipchecker
|
||||
gha
|
||||
GHSA
|
||||
Ghz
|
||||
gipc
|
||||
gitea
|
||||
GLM
|
||||
godotenv
|
||||
goland
|
||||
gomod
|
||||
@@ -125,27 +145,35 @@ goyaml
|
||||
GPG
|
||||
GPT
|
||||
gptbot
|
||||
Graphene
|
||||
grpcprom
|
||||
grw
|
||||
gzw
|
||||
Hashcash
|
||||
hashrate
|
||||
headermap
|
||||
healthcheck
|
||||
healthz
|
||||
hec
|
||||
helpdesk
|
||||
Hetzner
|
||||
hmc
|
||||
homelab
|
||||
hostable
|
||||
htmlc
|
||||
htmx
|
||||
httpdebug
|
||||
Huawei
|
||||
huawei
|
||||
hypertext
|
||||
iaskspider
|
||||
iaso
|
||||
iat
|
||||
ifm
|
||||
Imagesift
|
||||
imgproxy
|
||||
impressum
|
||||
inbox
|
||||
ingressed
|
||||
inp
|
||||
internets
|
||||
IPTo
|
||||
@@ -172,13 +200,14 @@ lcj
|
||||
ldflags
|
||||
letsencrypt
|
||||
Lexentale
|
||||
lfc
|
||||
lgbt
|
||||
licend
|
||||
licstart
|
||||
lightpanda
|
||||
limsa
|
||||
Linting
|
||||
linuxbrew
|
||||
listor
|
||||
LLU
|
||||
loadbalancer
|
||||
lol
|
||||
@@ -192,26 +221,37 @@ metrix
|
||||
mimi
|
||||
Minfilia
|
||||
mistralai
|
||||
mnt
|
||||
Mojeek
|
||||
mojeekbot
|
||||
mozilla
|
||||
myclient
|
||||
mymaster
|
||||
mypass
|
||||
myuser
|
||||
nbf
|
||||
nepeat
|
||||
netsurf
|
||||
nginx
|
||||
nicksnyder
|
||||
nikandfor
|
||||
nobots
|
||||
NONINFRINGEMENT
|
||||
nosleep
|
||||
nullglob
|
||||
oci
|
||||
OCOB
|
||||
ogtag
|
||||
oklch
|
||||
omgili
|
||||
omgilibot
|
||||
openai
|
||||
opendns
|
||||
opengraph
|
||||
openrc
|
||||
oswald
|
||||
pag
|
||||
pagegen
|
||||
palemoon
|
||||
Pangu
|
||||
parseable
|
||||
@@ -225,6 +265,7 @@ pipefail
|
||||
pki
|
||||
podkova
|
||||
podman
|
||||
Postgre
|
||||
poststart
|
||||
prebaked
|
||||
privkey
|
||||
@@ -232,6 +273,7 @@ promauto
|
||||
promhttp
|
||||
proofofwork
|
||||
publicsuffix
|
||||
purejs
|
||||
pwcmd
|
||||
pwuser
|
||||
qualys
|
||||
@@ -240,20 +282,23 @@ qwantbot
|
||||
rac
|
||||
rawler
|
||||
rcvar
|
||||
rdb
|
||||
redhat
|
||||
redir
|
||||
redirectscheme
|
||||
refactors
|
||||
remoteip
|
||||
reputational
|
||||
Rhul
|
||||
risc
|
||||
ruleset
|
||||
runlevels
|
||||
RUnlock
|
||||
runtimedir
|
||||
runtimedirectory
|
||||
Ryzen
|
||||
sas
|
||||
sasl
|
||||
screenshots
|
||||
searchbot
|
||||
searx
|
||||
sebest
|
||||
@@ -263,12 +308,16 @@ Seo
|
||||
setsebool
|
||||
shellcheck
|
||||
shirou
|
||||
shoneypot
|
||||
shopt
|
||||
Sidetrade
|
||||
simprint
|
||||
sitemap
|
||||
sls
|
||||
sni
|
||||
snipster
|
||||
Spambot
|
||||
spammer
|
||||
sparkline
|
||||
spyderbot
|
||||
srv
|
||||
@@ -283,10 +332,12 @@ SVCNAME
|
||||
tagline
|
||||
tarballs
|
||||
tarrif
|
||||
taviso
|
||||
tbn
|
||||
tbr
|
||||
techaro
|
||||
techarohq
|
||||
telegrambot
|
||||
templ
|
||||
templruntime
|
||||
testarea
|
||||
@@ -295,7 +346,9 @@ thoth
|
||||
thothmock
|
||||
Tik
|
||||
Timpibot
|
||||
TLog
|
||||
traefik
|
||||
trunc
|
||||
uberspace
|
||||
Unbreak
|
||||
unbreakdocker
|
||||
@@ -309,24 +362,26 @@ Varis
|
||||
Velen
|
||||
vendored
|
||||
vhosts
|
||||
videotest
|
||||
vkbot
|
||||
VKE
|
||||
vnd
|
||||
VPS
|
||||
Vultr
|
||||
waitloop
|
||||
weblate
|
||||
webmaster
|
||||
webpage
|
||||
websecure
|
||||
websites
|
||||
Webzio
|
||||
whois
|
||||
wildbase
|
||||
withthothmock
|
||||
wolfbeast
|
||||
wordpress
|
||||
Workaround
|
||||
workaround
|
||||
workdir
|
||||
wpbot
|
||||
Xeact
|
||||
XCircle
|
||||
xeiaso
|
||||
xeserv
|
||||
xesite
|
||||
@@ -335,13 +390,15 @@ xff
|
||||
XForwarded
|
||||
XNG
|
||||
XOB
|
||||
XOriginal
|
||||
XReal
|
||||
Y'shtola
|
||||
yae
|
||||
YAMLTo
|
||||
Yda
|
||||
yeet
|
||||
yeetfile
|
||||
yourdomain
|
||||
yoursite
|
||||
yyz
|
||||
Zenos
|
||||
zizmor
|
||||
|
||||
6
.github/dependabot.yml
vendored
6
.github/dependabot.yml
vendored
@@ -8,6 +8,8 @@ updates:
|
||||
github-actions:
|
||||
patterns:
|
||||
- "*"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: gomod
|
||||
directory: /
|
||||
@@ -17,6 +19,8 @@ updates:
|
||||
gomod:
|
||||
patterns:
|
||||
- "*"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
- package-ecosystem: npm
|
||||
directory: /
|
||||
@@ -26,3 +30,5 @@ updates:
|
||||
npm:
|
||||
patterns:
|
||||
- "*"
|
||||
cooldown:
|
||||
default-days: 7
|
||||
|
||||
72
.github/workflows/asset-verification.yml
vendored
Normal file
72
.github/workflows/asset-verification.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: Asset Build Verification
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
asset_verification:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: build essential
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: '24.11.0'
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version: '1.25.4'
|
||||
|
||||
- name: install node deps
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Check for uncommitted changes before asset build
|
||||
id: check-changes-before
|
||||
run: |
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Fail if there are uncommitted changes before build
|
||||
if: steps.check-changes-before.outputs.has_changes == 'true'
|
||||
run: |
|
||||
echo "There are uncommitted changes before running npm run assets"
|
||||
git status
|
||||
exit 1
|
||||
|
||||
- name: Run asset build
|
||||
run: |
|
||||
npm run assets
|
||||
|
||||
- name: Check for uncommitted changes after asset build
|
||||
id: check-changes-after
|
||||
run: |
|
||||
if [[ -n $(git status --porcelain) ]]; then
|
||||
echo "has_changes=true" >> $GITHUB_OUTPUT
|
||||
else
|
||||
echo "has_changes=false" >> $GITHUB_OUTPUT
|
||||
fi
|
||||
|
||||
- name: Fail if assets generated changes
|
||||
if: steps.check-changes-after.outputs.has_changes == 'true'
|
||||
run: |
|
||||
echo "npm run assets generated uncommitted changes. This indicates the repository has outdated generated files."
|
||||
echo "Please run 'npm run assets' locally and commit the changes."
|
||||
git status
|
||||
git diff
|
||||
exit 1
|
||||
40
.github/workflows/docker-pr.yml
vendored
40
.github/workflows/docker-pr.yml
vendored
@@ -2,7 +2,7 @@ name: Docker image builds (pull requests)
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
|
||||
env:
|
||||
DOCKER_METADATA_SET_OUTPUT_ENV: "true"
|
||||
@@ -15,39 +15,29 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
/home/linuxbrew/.linuxbrew/bin
|
||||
/home/linuxbrew/.linuxbrew/etc
|
||||
/home/linuxbrew/.linuxbrew/include
|
||||
/home/linuxbrew/.linuxbrew/lib
|
||||
/home/linuxbrew/.linuxbrew/opt
|
||||
/home/linuxbrew/.linuxbrew/sbin
|
||||
/home/linuxbrew/.linuxbrew/share
|
||||
/home/linuxbrew/.linuxbrew/var
|
||||
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-homebrew-cellar-
|
||||
|
||||
- name: Install Brew dependencies
|
||||
- name: build essential
|
||||
run: |
|
||||
brew bundle
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: '24.11.0'
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version: '1.25.4'
|
||||
|
||||
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
|
||||
|
||||
40
.github/workflows/docker.yml
vendored
40
.github/workflows/docker.yml
vendored
@@ -21,42 +21,32 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: build essential
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- name: Set lowercase image name
|
||||
run: |
|
||||
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
/home/linuxbrew/.linuxbrew/bin
|
||||
/home/linuxbrew/.linuxbrew/etc
|
||||
/home/linuxbrew/.linuxbrew/include
|
||||
/home/linuxbrew/.linuxbrew/lib
|
||||
/home/linuxbrew/.linuxbrew/opt
|
||||
/home/linuxbrew/.linuxbrew/sbin
|
||||
/home/linuxbrew/.linuxbrew/share
|
||||
/home/linuxbrew/.linuxbrew/var
|
||||
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-homebrew-cellar-
|
||||
node-version: '24.11.0'
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version: '1.25.4'
|
||||
|
||||
- name: Install Brew dependencies
|
||||
run: |
|
||||
brew bundle
|
||||
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
||||
|
||||
- name: Log into registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
@@ -64,7 +54,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
with:
|
||||
images: ${{ env.IMAGE }}
|
||||
|
||||
@@ -78,7 +68,7 @@ jobs:
|
||||
SLOG_LEVEL: debug
|
||||
|
||||
- name: Generate artifact attestation
|
||||
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
|
||||
uses: actions/attest-build-provenance@00014ed6ed5efc5b1ab7f7f34a39eb55d41aa4f8 # v3.1.0
|
||||
with:
|
||||
subject-name: ${{ env.IMAGE }}
|
||||
subject-digest: ${{ steps.build.outputs.digest }}
|
||||
|
||||
12
.github/workflows/docs-deploy.yml
vendored
12
.github/workflows/docs-deploy.yml
vendored
@@ -17,15 +17,15 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Log into registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: techarohq
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
with:
|
||||
images: ghcr.io/techarohq/anubis/docs
|
||||
tags: |
|
||||
@@ -53,14 +53,14 @@ jobs:
|
||||
push: true
|
||||
|
||||
- name: Apply k8s manifests to limsa lominsa
|
||||
uses: actions-hub/kubectl@b5b19eeb6a0ffde16637e398f8b96ef01eb8fdb7 # v1.33.3
|
||||
uses: actions-hub/kubectl@f6d776bd78f4523e36d6c74d34f9941c242b2213 # v1.35.0
|
||||
env:
|
||||
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
||||
with:
|
||||
args: apply -k docs/manifest
|
||||
|
||||
- name: Apply k8s manifests to limsa lominsa
|
||||
uses: actions-hub/kubectl@b5b19eeb6a0ffde16637e398f8b96ef01eb8fdb7 # v1.33.3
|
||||
uses: actions-hub/kubectl@f6d776bd78f4523e36d6c74d34f9941c242b2213 # v1.35.0
|
||||
env:
|
||||
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
||||
with:
|
||||
|
||||
6
.github/workflows/docs-test.yml
vendored
6
.github/workflows/docs-test.yml
vendored
@@ -13,16 +13,16 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
|
||||
- name: Docker meta
|
||||
id: meta
|
||||
uses: docker/metadata-action@902fa8ec7d6ecbf8d84d538b9b233a880e428804 # v5.7.0
|
||||
uses: docker/metadata-action@c299e40c65443455700f0fdfc63efafe5b349051 # v5.10.0
|
||||
with:
|
||||
images: ghcr.io/techarohq/anubis/docs
|
||||
tags: |
|
||||
|
||||
76
.github/workflows/go-mod-tidy-check.yml
vendored
Normal file
76
.github/workflows/go-mod-tidy-check.yml
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
name: Go Mod Tidy Check
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
go_mod_tidy_check:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version: '1.25.4'
|
||||
|
||||
- name: Check go.mod and go.sum in main directory
|
||||
run: |
|
||||
# Store original file state
|
||||
cp go.mod go.mod.orig
|
||||
cp go.sum go.sum.orig
|
||||
|
||||
# Run go mod tidy
|
||||
go mod tidy
|
||||
|
||||
# Check if files changed
|
||||
if ! diff -q go.mod.orig go.mod > /dev/null 2>&1; then
|
||||
echo "ERROR: go.mod in main directory has changed after running 'go mod tidy'"
|
||||
echo "Please run 'go mod tidy' locally and commit the changes"
|
||||
diff go.mod.orig go.mod
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! diff -q go.sum.orig go.sum > /dev/null 2>&1; then
|
||||
echo "ERROR: go.sum in main directory has changed after running 'go mod tidy'"
|
||||
echo "Please run 'go mod tidy' locally and commit the changes"
|
||||
diff go.sum.orig go.sum
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "SUCCESS: go.mod and go.sum in main directory are tidy"
|
||||
|
||||
- name: Check go.mod and go.sum in test directory
|
||||
run: |
|
||||
cd test
|
||||
|
||||
# Store original file state
|
||||
cp go.mod go.mod.orig
|
||||
cp go.sum go.sum.orig
|
||||
|
||||
# Run go mod tidy
|
||||
go mod tidy
|
||||
|
||||
# Check if files changed
|
||||
if ! diff -q go.mod.orig go.mod > /dev/null 2>&1; then
|
||||
echo "ERROR: go.mod in test directory has changed after running 'go mod tidy'"
|
||||
echo "Please run 'go mod tidy' locally and commit the changes"
|
||||
diff go.mod.orig go.mod
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! diff -q go.sum.orig go.sum > /dev/null 2>&1; then
|
||||
echo "ERROR: go.sum in test directory has changed after running 'go mod tidy'"
|
||||
echo "Please run 'go mod tidy' locally and commit the changes"
|
||||
diff go.sum.orig go.sum
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "SUCCESS: go.mod and go.sum in test directory are tidy"
|
||||
107
.github/workflows/go.yml
vendored
107
.github/workflows/go.yml
vendored
@@ -2,9 +2,9 @@ name: Go
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -15,77 +15,50 @@ jobs:
|
||||
#runs-on: alrest-techarohq
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: build essential
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
- name: build essential
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: '24.11.0'
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version: '1.25.4'
|
||||
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
/home/linuxbrew/.linuxbrew/bin
|
||||
/home/linuxbrew/.linuxbrew/etc
|
||||
/home/linuxbrew/.linuxbrew/include
|
||||
/home/linuxbrew/.linuxbrew/lib
|
||||
/home/linuxbrew/.linuxbrew/opt
|
||||
/home/linuxbrew/.linuxbrew/sbin
|
||||
/home/linuxbrew/.linuxbrew/share
|
||||
/home/linuxbrew/.linuxbrew/var
|
||||
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-homebrew-cellar-
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@9255dc7a253b0ccc959486e2bca901246202afeb # v5.0.1
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('**/go.sum') }}
|
||||
|
||||
- name: Install Brew dependencies
|
||||
run: |
|
||||
brew bundle
|
||||
- name: install node deps
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Setup Golang caches
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-golang-
|
||||
- name: install playwright browsers
|
||||
run: |
|
||||
npx --no-install playwright@1.52.0 install --with-deps
|
||||
npx --no-install playwright@1.52.0 run-server --port 9001 &
|
||||
|
||||
- name: Cache playwright binaries
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
id: playwright-cache
|
||||
with:
|
||||
path: |
|
||||
~/.cache/ms-playwright
|
||||
key: ${{ runner.os }}-playwright-${{ hashFiles('**/go.sum') }}
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: install node deps
|
||||
run: |
|
||||
npm ci
|
||||
- name: Test
|
||||
run: npm run test
|
||||
|
||||
- name: install playwright browsers
|
||||
run: |
|
||||
npx --no-install playwright@1.52.0 install --with-deps
|
||||
npx --no-install playwright@1.52.0 run-server --port 9001 &
|
||||
- name: Lint with staticcheck
|
||||
uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1.4.0
|
||||
with:
|
||||
version: "latest"
|
||||
|
||||
- name: Build
|
||||
run: npm run build
|
||||
|
||||
- name: Test
|
||||
run: npm run test
|
||||
|
||||
- name: Lint with staticcheck
|
||||
uses: dominikh/staticcheck-action@024238d2898c874f26d723e7d0ff4308c35589a2 # v1.4.0
|
||||
with:
|
||||
version: "latest"
|
||||
|
||||
- name: Govulncheck
|
||||
run: |
|
||||
go tool govulncheck ./...
|
||||
- name: Govulncheck
|
||||
run: |
|
||||
go tool govulncheck ./...
|
||||
|
||||
37
.github/workflows/package-builds-stable.yml
vendored
37
.github/workflows/package-builds-stable.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
#runs-on: alrest-techarohq
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-tags: true
|
||||
@@ -25,39 +25,12 @@ jobs:
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
/home/linuxbrew/.linuxbrew/bin
|
||||
/home/linuxbrew/.linuxbrew/etc
|
||||
/home/linuxbrew/.linuxbrew/include
|
||||
/home/linuxbrew/.linuxbrew/lib
|
||||
/home/linuxbrew/.linuxbrew/opt
|
||||
/home/linuxbrew/.linuxbrew/sbin
|
||||
/home/linuxbrew/.linuxbrew/share
|
||||
/home/linuxbrew/.linuxbrew/var
|
||||
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-homebrew-cellar-
|
||||
|
||||
- name: Install Brew dependencies
|
||||
run: |
|
||||
brew bundle
|
||||
|
||||
- name: Setup Golang caches
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
node-version: '24.11.0'
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-golang-
|
||||
go-version: '1.25.4'
|
||||
|
||||
- name: install node deps
|
||||
run: |
|
||||
|
||||
81
.github/workflows/package-builds-unstable.yml
vendored
81
.github/workflows/package-builds-unstable.yml
vendored
@@ -2,9 +2,9 @@ name: Package builds (unstable)
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
pull_request:
|
||||
branches: [ "main" ]
|
||||
branches: ["main"]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -15,60 +15,33 @@ jobs:
|
||||
#runs-on: alrest-techarohq
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
- uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
|
||||
- name: build essential
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
- name: build essential
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y build-essential
|
||||
|
||||
- name: Set up Homebrew
|
||||
uses: Homebrew/actions/setup-homebrew@main
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: '24.11.0'
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version: '1.25.4'
|
||||
|
||||
- name: Setup Homebrew cellar cache
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
/home/linuxbrew/.linuxbrew/Cellar
|
||||
/home/linuxbrew/.linuxbrew/bin
|
||||
/home/linuxbrew/.linuxbrew/etc
|
||||
/home/linuxbrew/.linuxbrew/include
|
||||
/home/linuxbrew/.linuxbrew/lib
|
||||
/home/linuxbrew/.linuxbrew/opt
|
||||
/home/linuxbrew/.linuxbrew/sbin
|
||||
/home/linuxbrew/.linuxbrew/share
|
||||
/home/linuxbrew/.linuxbrew/var
|
||||
key: ${{ runner.os }}-go-homebrew-cellar-${{ hashFiles('go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-go-homebrew-cellar-
|
||||
- name: install node deps
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Install Brew dependencies
|
||||
run: |
|
||||
brew bundle
|
||||
- name: Build Packages
|
||||
run: |
|
||||
go tool yeet
|
||||
|
||||
- name: Setup Golang caches
|
||||
uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
~/.cache/go-build
|
||||
~/go/pkg/mod
|
||||
key: ${{ runner.os }}-golang-${{ hashFiles('**/go.sum') }}
|
||||
restore-keys: |
|
||||
${{ runner.os }}-golang-
|
||||
|
||||
- name: install node deps
|
||||
run: |
|
||||
npm ci
|
||||
|
||||
- name: Build Packages
|
||||
run: |
|
||||
go tool yeet
|
||||
|
||||
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: packages
|
||||
path: var/*
|
||||
- uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
|
||||
with:
|
||||
name: packages
|
||||
path: var/*
|
||||
|
||||
20
.github/workflows/smoke-tests.yml
vendored
20
.github/workflows/smoke-tests.yml
vendored
@@ -14,26 +14,32 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
test:
|
||||
- default-config-macro
|
||||
- docker-registry
|
||||
- double_slash
|
||||
- forced-language
|
||||
- git-clone
|
||||
- git-push
|
||||
- healthcheck
|
||||
- i18n
|
||||
- log-file
|
||||
- nginx
|
||||
- palemoon/amd64
|
||||
#- palemoon/i386
|
||||
- robots_txt
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4.4.0
|
||||
- uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0
|
||||
with:
|
||||
node-version: latest
|
||||
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
node-version: "24.11.0"
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version: stable
|
||||
go-version: "1.25.4"
|
||||
|
||||
- uses: ko-build/setup-ko@d006021bd0c28d1ce33a07e7943d48b079944c8d # v0.9
|
||||
|
||||
@@ -51,7 +57,7 @@ jobs:
|
||||
run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
if: always()
|
||||
with:
|
||||
name: ${{ env.ARTIFACT_NAME }}
|
||||
|
||||
6
.github/workflows/ssh-ci-runner-cron.yml
vendored
6
.github/workflows/ssh-ci-runner-cron.yml
vendored
@@ -18,19 +18,19 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
- name: Log into registry
|
||||
uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 # v3.4.0
|
||||
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@e468171a9de216ec08956ac3ada2f0791b6bd435 # v3.11.1
|
||||
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
|
||||
- name: Build and push
|
||||
run: |
|
||||
cd ./test/ssh-ci
|
||||
|
||||
14
.github/workflows/ssh-ci.yml
vendored
14
.github/workflows/ssh-ci.yml
vendored
@@ -12,15 +12,17 @@ permissions:
|
||||
jobs:
|
||||
ssh:
|
||||
if: github.repository == 'TecharoHQ/anubis'
|
||||
runs-on: ubuntu-24.04
|
||||
runs-on: alrest-techarohq
|
||||
strategy:
|
||||
matrix:
|
||||
host:
|
||||
- ubuntu@riscv64.techaro.lol
|
||||
- ci@ppc64le.techaro.lol
|
||||
- riscv64
|
||||
- ppc64le
|
||||
- aarch64-4k
|
||||
- aarch64-16k
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
fetch-tags: true
|
||||
fetch-depth: 0
|
||||
@@ -33,9 +35,9 @@ jobs:
|
||||
name: id_rsa
|
||||
known_hosts: ${{ secrets.CI_SSH_KNOWN_HOSTS }}
|
||||
|
||||
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0
|
||||
- uses: actions/setup-go@4dc6199c7b1a012772edbd06daecab0f50c9053c # v6.1.0
|
||||
with:
|
||||
go-version: stable
|
||||
go-version: '1.25.4'
|
||||
|
||||
- name: Run CI
|
||||
run: go run ./utils/cmd/backoff-retry bash test/ssh-ci/rigging.sh ${{ matrix.host }}
|
||||
|
||||
6
.github/workflows/zizmor.yml
vendored
6
.github/workflows/zizmor.yml
vendored
@@ -16,12 +16,12 @@ jobs:
|
||||
security-events: write
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
- name: Install the latest version of uv
|
||||
uses: astral-sh/setup-uv@e92bafb6253dcd438e0484186d7669ea7a8ca1cc # v6.4.3
|
||||
uses: astral-sh/setup-uv@681c641aba71e4a1c380be3ab5e12ad51f415867 # v7.1.6
|
||||
|
||||
- name: Run zizmor 🌈
|
||||
run: uvx zizmor --format sarif . > results.sarif
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Upload SARIF file
|
||||
uses: github/codeql-action/upload-sarif@4e828ff8d448a8a6e532957b1811f387a63867e8 # v3.29.4
|
||||
uses: github/codeql-action/upload-sarif@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v4.31.9
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
category: zizmor
|
||||
|
||||
@@ -20,6 +20,9 @@ Anubis is brought to you by sponsors and donors like:
|
||||
<a href="https://www.raptorcs.com/content/base/products.html">
|
||||
<img src="./docs/static/img/sponsors/raptor-computing-logo.webp" alt="Raptor Computing Systems" height=64 />
|
||||
</a>
|
||||
<a href="https://databento.com/?utm_source=anubis&utm_medium=sponsor&utm_campaign=anubis">
|
||||
<img src="./docs/static/img/sponsors/databento-logo.webp" alt="Databento" />
|
||||
</a>
|
||||
|
||||
### Gold Tier
|
||||
|
||||
@@ -66,7 +69,7 @@ Anubis is a bit of a nuclear response. This will result in your website being bl
|
||||
|
||||
In most cases, you should not need this and can probably get by using Cloudflare to protect a given origin. However, for circumstances where you can't or won't use Cloudflare, Anubis is there for you.
|
||||
|
||||
If you want to try this out, connect to [anubis.techaro.lol](https://anubis.techaro.lol).
|
||||
If you want to try this out, visit the Anubis documentation site at [anubis.techaro.lol](https://anubis.techaro.lol).
|
||||
|
||||
## Support
|
||||
|
||||
|
||||
13
SECURITY.md
Normal file
13
SECURITY.md
Normal file
@@ -0,0 +1,13 @@
|
||||
# Security Policy
|
||||
|
||||
Techaro follows the [Semver 2.0 scheme](https://semver.org/).
|
||||
|
||||
## Supported Versions
|
||||
|
||||
Techaro strives to support the two most recent minor versions of Anubis. Patches to those versions will be published as patch releases.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
Email security@techaro.lol with details on the vulnerability and reproduction steps. You will get a response as soon as possible.
|
||||
|
||||
Please take care to send your email as a mixed plaintext and HTML message. Messages with GPG signatures or that are plaintext only may be blocked by the spam filter.
|
||||
@@ -11,7 +11,7 @@ var Version = "devel"
|
||||
|
||||
// CookieName is the name of the cookie that Anubis uses in order to validate
|
||||
// access.
|
||||
var CookieName = "techaro.lol-anubis-auth"
|
||||
var CookieName = "techaro.lol-anubis"
|
||||
|
||||
// TestCookieName is the name of the cookie that Anubis uses in order to check
|
||||
// if cookies are enabled on the client's browser.
|
||||
@@ -23,6 +23,9 @@ const CookieDefaultExpirationTime = 7 * 24 * time.Hour
|
||||
// BasePrefix is a global prefix for all Anubis endpoints. Can be emptied to remove the prefix entirely.
|
||||
var BasePrefix = ""
|
||||
|
||||
// PublicUrl is the externally accessible URL for this Anubis instance.
|
||||
var PublicUrl = ""
|
||||
|
||||
// StaticPath is the location where all static Anubis assets are located.
|
||||
const StaticPath = "/.within.website/x/cmd/anubis/"
|
||||
|
||||
@@ -36,3 +39,6 @@ const DefaultDifficulty = 4
|
||||
// ForcedLanguage is the language being used instead of the one of the request's Accept-Language header
|
||||
// if being set.
|
||||
var ForcedLanguage = ""
|
||||
|
||||
// UseSimplifiedExplanation can be set to true for using the simplified explanation
|
||||
var UseSimplifiedExplanation = false
|
||||
|
||||
@@ -31,8 +31,8 @@ import (
|
||||
"github.com/TecharoHQ/anubis/data"
|
||||
"github.com/TecharoHQ/anubis/internal"
|
||||
libanubis "github.com/TecharoHQ/anubis/lib"
|
||||
"github.com/TecharoHQ/anubis/lib/config"
|
||||
botPolicy "github.com/TecharoHQ/anubis/lib/policy"
|
||||
"github.com/TecharoHQ/anubis/lib/policy/config"
|
||||
"github.com/TecharoHQ/anubis/lib/thoth"
|
||||
"github.com/TecharoHQ/anubis/web"
|
||||
"github.com/facebookgo/flagenv"
|
||||
@@ -49,11 +49,14 @@ var (
|
||||
cookieDomain = flag.String("cookie-domain", "", "if set, the top-level domain that the Anubis cookie will be valid for")
|
||||
cookieDynamicDomain = flag.Bool("cookie-dynamic-domain", false, "if set, automatically set the cookie Domain value based on the request domain")
|
||||
cookieExpiration = flag.Duration("cookie-expiration-time", anubis.CookieDefaultExpirationTime, "The amount of time the authorization cookie is valid for")
|
||||
cookiePrefix = flag.String("cookie-prefix", "techaro.lol-anubis", "prefix for browser cookies created by Anubis")
|
||||
cookiePrefix = flag.String("cookie-prefix", anubis.CookieName, "prefix for browser cookies created by Anubis")
|
||||
cookiePartitioned = flag.Bool("cookie-partitioned", false, "if true, sets the partitioned flag on Anubis cookies, enabling CHIPS support")
|
||||
difficultyInJWT = flag.Bool("difficulty-in-jwt", false, "if true, adds a difficulty field in the JWT claims")
|
||||
useSimplifiedExplanation = flag.Bool("use-simplified-explanation", false, "if true, replaces the text when clicking \"Why am I seeing this?\" with a more simplified text for a non-tech-savvy audience.")
|
||||
forcedLanguage = flag.String("forced-language", "", "if set, this language is being used instead of the one from the request's Accept-Language header")
|
||||
hs512Secret = flag.String("hs512-secret", "", "secret used to sign JWTs, uses ed25519 if not set")
|
||||
cookieSecure = flag.Bool("cookie-secure", true, "if true, sets the secure flag on Anubis cookies")
|
||||
cookieSameSite = flag.String("cookie-same-site", "None", "sets the same site option on Anubis cookies, will auto-downgrade None to Lax if cookie-secure is false. Valid values are None, Lax, Strict, and Default.")
|
||||
ed25519PrivateKeyHex = flag.String("ed25519-private-key-hex", "", "private key used to sign JWTs, if not set a random one will be assigned")
|
||||
ed25519PrivateKeyHexFile = flag.String("ed25519-private-key-hex-file", "", "file name containing value for ed25519-private-key-hex")
|
||||
metricsBind = flag.String("metrics-bind", ":9090", "network address to bind metrics to")
|
||||
@@ -65,9 +68,10 @@ var (
|
||||
slogLevel = flag.String("slog-level", "INFO", "logging level (see https://pkg.go.dev/log/slog#hdr-Levels)")
|
||||
stripBasePrefix = flag.Bool("strip-base-prefix", false, "if true, strips the base prefix from requests forwarded to the target server")
|
||||
target = flag.String("target", "http://localhost:3923", "target to reverse proxy to, set to an empty string to disable proxying when only using auth request")
|
||||
targetSNI = flag.String("target-sni", "", "if set, the value of the TLS handshake hostname when forwarding requests to the target")
|
||||
targetSNI = flag.String("target-sni", "", "if set, TLS handshake hostname when forwarding requests to the target, if set to auto, use Host header")
|
||||
targetHost = flag.String("target-host", "", "if set, the value of the Host header when forwarding requests to the target")
|
||||
targetInsecureSkipVerify = flag.Bool("target-insecure-skip-verify", false, "if true, skips TLS validation for the backend")
|
||||
targetDisableKeepAlive = flag.Bool("target-disable-keepalive", false, "if true, disables HTTP keep-alive for the backend")
|
||||
healthcheck = flag.Bool("healthcheck", false, "run a health check against Anubis")
|
||||
useRemoteAddress = flag.Bool("use-remote-address", false, "read the client's IP address from the network request, useful for debugging and running Anubis on bare metal")
|
||||
debugBenchmarkJS = flag.Bool("debug-benchmark-js", false, "respond to every request with a challenge for benchmarking hashrate")
|
||||
@@ -77,11 +81,14 @@ var (
|
||||
extractResources = flag.String("extract-resources", "", "if set, extract the static resources to the specified folder")
|
||||
webmasterEmail = flag.String("webmaster-email", "", "if set, displays webmaster's email on the reject page for appeals")
|
||||
versionFlag = flag.Bool("version", false, "print Anubis version")
|
||||
publicUrl = flag.String("public-url", "", "the externally accessible URL for this Anubis instance, used for constructing redirect URLs (e.g., for forwardAuth).")
|
||||
xffStripPrivate = flag.Bool("xff-strip-private", true, "if set, strip private addresses from X-Forwarded-For")
|
||||
customRealIPHeader = flag.String("custom-real-ip-header", "", "if set, read remote IP from header of this name (in case your environment doesn't set X-Real-IP header)")
|
||||
|
||||
thothInsecure = flag.Bool("thoth-insecure", false, "if set, connect to Thoth over plain HTTP/2, don't enable this unless support told you to")
|
||||
thothURL = flag.String("thoth-url", "", "if set, URL for Thoth, the IP reputation database for Anubis")
|
||||
thothToken = flag.String("thoth-token", "", "if set, API token for Thoth, the IP reputation database for Anubis")
|
||||
thothInsecure = flag.Bool("thoth-insecure", false, "if set, connect to Thoth over plain HTTP/2, don't enable this unless support told you to")
|
||||
thothURL = flag.String("thoth-url", "", "if set, URL for Thoth, the IP reputation database for Anubis")
|
||||
thothToken = flag.String("thoth-token", "", "if set, API token for Thoth, the IP reputation database for Anubis")
|
||||
jwtRestrictionHeader = flag.String("jwt-restriction-header", "X-Real-IP", "If set, the JWT is only valid if the current value of this header matched the value when the JWT was created")
|
||||
)
|
||||
|
||||
func keyFromHex(value string) (ed25519.PrivateKey, error) {
|
||||
@@ -138,6 +145,22 @@ func parseBindNetFromAddr(address string) (string, string) {
|
||||
return "", address
|
||||
}
|
||||
|
||||
func parseSameSite(s string) http.SameSite {
|
||||
switch strings.ToLower(s) {
|
||||
case "none":
|
||||
return http.SameSiteNoneMode
|
||||
case "lax":
|
||||
return http.SameSiteLaxMode
|
||||
case "strict":
|
||||
return http.SameSiteStrictMode
|
||||
case "default":
|
||||
return http.SameSiteDefaultMode
|
||||
default:
|
||||
log.Fatalf("invalid cookie same-site mode: %s, valid values are None, Lax, Strict, and Default", s)
|
||||
}
|
||||
return http.SameSiteDefaultMode
|
||||
}
|
||||
|
||||
func setupListener(network string, address string) (net.Listener, string) {
|
||||
formattedAddress := ""
|
||||
|
||||
@@ -185,7 +208,7 @@ func setupListener(network string, address string) (net.Listener, string) {
|
||||
return listener, formattedAddress
|
||||
}
|
||||
|
||||
func makeReverseProxy(target string, targetSNI string, targetHost string, insecureSkipVerify bool) (http.Handler, error) {
|
||||
func makeReverseProxy(target string, targetSNI string, targetHost string, insecureSkipVerify bool, targetDisableKeepAlive bool) (http.Handler, error) {
|
||||
targetUri, err := url.Parse(target)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to parse target URL: %w", err)
|
||||
@@ -193,6 +216,10 @@ func makeReverseProxy(target string, targetSNI string, targetHost string, insecu
|
||||
|
||||
transport := http.DefaultTransport.(*http.Transport).Clone()
|
||||
|
||||
if targetDisableKeepAlive {
|
||||
transport.DisableKeepAlives = true
|
||||
}
|
||||
|
||||
// https://github.com/oauth2-proxy/oauth2-proxy/blob/4e2100a2879ef06aea1411790327019c1a09217c/pkg/upstream/http.go#L124
|
||||
if targetUri.Scheme == "unix" {
|
||||
// clean path up so we don't use the socket path in proxied requests
|
||||
@@ -209,23 +236,28 @@ func makeReverseProxy(target string, targetSNI string, targetHost string, insecu
|
||||
|
||||
if insecureSkipVerify || targetSNI != "" {
|
||||
transport.TLSClientConfig = &tls.Config{}
|
||||
if insecureSkipVerify {
|
||||
slog.Warn("TARGET_INSECURE_SKIP_VERIFY is set to true, TLS certificate validation will not be performed", "target", target)
|
||||
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||
}
|
||||
if targetSNI != "" {
|
||||
transport.TLSClientConfig.ServerName = targetSNI
|
||||
}
|
||||
}
|
||||
if insecureSkipVerify {
|
||||
slog.Warn("TARGET_INSECURE_SKIP_VERIFY is set to true, TLS certificate validation will not be performed", "target", target)
|
||||
transport.TLSClientConfig.InsecureSkipVerify = true
|
||||
}
|
||||
if targetSNI != "" && targetSNI != "auto" {
|
||||
transport.TLSClientConfig.ServerName = targetSNI
|
||||
}
|
||||
|
||||
rp := httputil.NewSingleHostReverseProxy(targetUri)
|
||||
rp.Transport = transport
|
||||
|
||||
if targetHost != "" {
|
||||
if targetHost != "" || targetSNI == "auto" {
|
||||
originalDirector := rp.Director
|
||||
rp.Director = func(req *http.Request) {
|
||||
originalDirector(req)
|
||||
req.Host = targetHost
|
||||
if targetHost != "" {
|
||||
req.Host = targetHost
|
||||
}
|
||||
if targetSNI == "auto" {
|
||||
transport.TLSClientConfig.ServerName = req.Host
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -241,9 +273,11 @@ func main() {
|
||||
return
|
||||
}
|
||||
|
||||
internal.InitSlog(*slogLevel)
|
||||
internal.SetHealth("anubis", healthv1.HealthCheckResponse_NOT_SERVING)
|
||||
|
||||
lg := internal.InitSlog(*slogLevel, os.Stderr)
|
||||
lg.Info("starting up Anubis")
|
||||
|
||||
if *healthcheck {
|
||||
log.Println("running healthcheck")
|
||||
if err := doHealthCheck(); err != nil {
|
||||
@@ -271,14 +305,14 @@ func main() {
|
||||
|
||||
if *metricsBind != "" {
|
||||
wg.Add(1)
|
||||
go metricsServer(ctx, wg.Done)
|
||||
go metricsServer(ctx, *lg.With("subsystem", "metrics"), wg.Done)
|
||||
}
|
||||
|
||||
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
|
||||
if strings.TrimSpace(*target) != "" {
|
||||
var err error
|
||||
rp, err = makeReverseProxy(*target, *targetSNI, *targetHost, *targetInsecureSkipVerify)
|
||||
rp, err = makeReverseProxy(*target, *targetSNI, *targetHost, *targetInsecureSkipVerify, *targetDisableKeepAlive)
|
||||
if err != nil {
|
||||
log.Fatalf("can't make reverse proxy: %v", err)
|
||||
}
|
||||
@@ -291,11 +325,11 @@ func main() {
|
||||
// Thoth configuration
|
||||
switch {
|
||||
case *thothURL != "" && *thothToken == "":
|
||||
slog.Warn("THOTH_URL is set but no THOTH_TOKEN is set")
|
||||
lg.Warn("THOTH_URL is set but no THOTH_TOKEN is set")
|
||||
case *thothURL == "" && *thothToken != "":
|
||||
slog.Warn("THOTH_TOKEN is set but no THOTH_URL is set")
|
||||
lg.Warn("THOTH_TOKEN is set but no THOTH_URL is set")
|
||||
case *thothURL != "" && *thothToken != "":
|
||||
slog.Debug("connecting to Thoth")
|
||||
lg.Debug("connecting to Thoth")
|
||||
thothClient, err := thoth.New(ctx, *thothURL, *thothToken, *thothInsecure)
|
||||
if err != nil {
|
||||
log.Fatalf("can't dial thoth at %s: %v", *thothURL, err)
|
||||
@@ -304,10 +338,24 @@ func main() {
|
||||
ctx = thoth.With(ctx, thothClient)
|
||||
}
|
||||
|
||||
policy, err := libanubis.LoadPoliciesOrDefault(ctx, *policyFname, *challengeDifficulty)
|
||||
lg.Info("loading policy file", "fname", *policyFname)
|
||||
policy, err := libanubis.LoadPoliciesOrDefault(ctx, *policyFname, *challengeDifficulty, *slogLevel)
|
||||
if err != nil {
|
||||
log.Fatalf("can't parse policy file: %v", err)
|
||||
}
|
||||
lg = policy.Logger
|
||||
lg.Debug("swapped to new logger")
|
||||
slog.SetDefault(lg)
|
||||
|
||||
// Warn if persistent storage is used without a configured signing key
|
||||
if policy.Store.IsPersistent() {
|
||||
if *hs512Secret == "" && *ed25519PrivateKeyHex == "" && *ed25519PrivateKeyHexFile == "" {
|
||||
lg.Warn("[misconfiguration] persistent storage backend is configured, but no private key is set. " +
|
||||
"Challenges will be invalidated when Anubis restarts. " +
|
||||
"Set HS512_SECRET, ED25519_PRIVATE_KEY_HEX, or ED25519_PRIVATE_KEY_HEX_FILE to ensure challenges survive service restarts. " +
|
||||
"See: https://anubis.techaro.lol/docs/admin/installation#key-generation")
|
||||
}
|
||||
}
|
||||
|
||||
ruleErrorIDs := make(map[string]string)
|
||||
for _, rule := range policy.Bots {
|
||||
@@ -365,7 +413,7 @@ func main() {
|
||||
log.Fatalf("failed to generate ed25519 key: %v", err)
|
||||
}
|
||||
|
||||
slog.Warn("generating random key, Anubis will have strange behavior when multiple instances are behind the same load balancer target, for more information: see https://anubis.techaro.lol/docs/admin/installation#key-generation")
|
||||
lg.Warn("generating random key, Anubis will have strange behavior when multiple instances are behind the same load balancer target, for more information: see https://anubis.techaro.lol/docs/admin/installation#key-generation")
|
||||
}
|
||||
|
||||
var redirectDomainsList []string
|
||||
@@ -379,12 +427,13 @@ func main() {
|
||||
redirectDomainsList = append(redirectDomainsList, strings.TrimSpace(domain))
|
||||
}
|
||||
} else {
|
||||
slog.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 only redirect to the same domain a request is coming from, see https://anubis.techaro.lol/docs/admin/configuration/redirect-domains")
|
||||
}
|
||||
|
||||
anubis.CookieName = *cookiePrefix + "-auth"
|
||||
anubis.TestCookieName = *cookiePrefix + "-cookie-verification"
|
||||
anubis.ForcedLanguage = *forcedLanguage
|
||||
anubis.UseSimplifiedExplanation = *useSimplifiedExplanation
|
||||
|
||||
// If OpenGraph configuration values are not set in the config file, use the
|
||||
// values from flags / envvars.
|
||||
@@ -396,22 +445,30 @@ func main() {
|
||||
}
|
||||
|
||||
s, err := libanubis.New(libanubis.Options{
|
||||
BasePrefix: *basePrefix,
|
||||
StripBasePrefix: *stripBasePrefix,
|
||||
Next: rp,
|
||||
Policy: policy,
|
||||
ServeRobotsTXT: *robotsTxt,
|
||||
ED25519PrivateKey: ed25519Priv,
|
||||
HS512Secret: []byte(*hs512Secret),
|
||||
CookieDomain: *cookieDomain,
|
||||
CookieDynamicDomain: *cookieDynamicDomain,
|
||||
CookieExpiration: *cookieExpiration,
|
||||
CookiePartitioned: *cookiePartitioned,
|
||||
RedirectDomains: redirectDomainsList,
|
||||
Target: *target,
|
||||
WebmasterEmail: *webmasterEmail,
|
||||
OpenGraph: policy.OpenGraph,
|
||||
CookieSecure: *cookieSecure,
|
||||
BasePrefix: *basePrefix,
|
||||
StripBasePrefix: *stripBasePrefix,
|
||||
Next: rp,
|
||||
Policy: policy,
|
||||
TargetHost: *targetHost,
|
||||
TargetSNI: *targetSNI,
|
||||
TargetInsecureSkipVerify: *targetInsecureSkipVerify,
|
||||
ServeRobotsTXT: *robotsTxt,
|
||||
ED25519PrivateKey: ed25519Priv,
|
||||
HS512Secret: []byte(*hs512Secret),
|
||||
CookieDomain: *cookieDomain,
|
||||
CookieDynamicDomain: *cookieDynamicDomain,
|
||||
CookieExpiration: *cookieExpiration,
|
||||
CookiePartitioned: *cookiePartitioned,
|
||||
RedirectDomains: redirectDomainsList,
|
||||
Target: *target,
|
||||
WebmasterEmail: *webmasterEmail,
|
||||
OpenGraph: policy.OpenGraph,
|
||||
CookieSecure: *cookieSecure,
|
||||
CookieSameSite: parseSameSite(*cookieSameSite),
|
||||
PublicUrl: *publicUrl,
|
||||
JWTRestrictionHeader: *jwtRestrictionHeader,
|
||||
Logger: policy.Logger.With("subsystem", "anubis"),
|
||||
DifficultyInJWT: *difficultyInJWT,
|
||||
})
|
||||
if err != nil {
|
||||
log.Fatalf("can't construct libanubis.Server: %v", err)
|
||||
@@ -419,6 +476,7 @@ func main() {
|
||||
|
||||
var h http.Handler
|
||||
h = s
|
||||
h = internal.CustomRealIPHeader(*customRealIPHeader, h)
|
||||
h = internal.RemoteXRealIP(*useRemoteAddress, *bindNetwork, h)
|
||||
h = internal.XForwardedForToXRealIP(h)
|
||||
h = internal.XForwardedForUpdate(*xffStripPrivate, h)
|
||||
@@ -426,7 +484,7 @@ func main() {
|
||||
|
||||
srv := http.Server{Handler: h, ErrorLog: internal.GetFilteredHTTPLogger()}
|
||||
listener, listenerUrl := setupListener(*bindNetwork, *bind)
|
||||
slog.Info(
|
||||
lg.Info(
|
||||
"listening",
|
||||
"url", listenerUrl,
|
||||
"difficulty", *challengeDifficulty,
|
||||
@@ -440,6 +498,7 @@ func main() {
|
||||
"base-prefix", *basePrefix,
|
||||
"cookie-expiration-time", *cookieExpiration,
|
||||
"rule-error-ids", ruleErrorIDs,
|
||||
"public-url", *publicUrl,
|
||||
)
|
||||
|
||||
go func() {
|
||||
@@ -459,7 +518,7 @@ func main() {
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func metricsServer(ctx context.Context, done func()) {
|
||||
func metricsServer(ctx context.Context, lg slog.Logger, done func()) {
|
||||
defer done()
|
||||
|
||||
mux := http.NewServeMux()
|
||||
@@ -485,7 +544,7 @@ func metricsServer(ctx context.Context, done func()) {
|
||||
|
||||
srv := http.Server{Handler: mux, ErrorLog: internal.GetFilteredHTTPLogger()}
|
||||
listener, metricsUrl := setupListener(*metricsBindNetwork, *metricsBind)
|
||||
slog.Debug("listening for metrics", "url", metricsUrl)
|
||||
lg.Debug("listening for metrics", "url", metricsUrl)
|
||||
|
||||
go func() {
|
||||
<-ctx.Done()
|
||||
|
||||
@@ -28,7 +28,7 @@ func main() {
|
||||
flagenv.Parse()
|
||||
flag.Parse()
|
||||
|
||||
internal.InitSlog(*slogLevel)
|
||||
slog.SetDefault(internal.InitSlog(*slogLevel, os.Stderr))
|
||||
|
||||
koDockerRepo := strings.TrimSuffix(*dockerRepo, "/"+filepath.Base(*dockerRepo))
|
||||
|
||||
@@ -46,6 +46,11 @@ func main() {
|
||||
)
|
||||
}
|
||||
|
||||
if strings.Contains(*dockerTags, ",") {
|
||||
newTags := strings.Join(strings.Split(*dockerTags, ","), "\n")
|
||||
dockerTags = &newTags
|
||||
}
|
||||
|
||||
setOutput("docker_image", strings.SplitN(*dockerTags, "\n", 2)[0])
|
||||
|
||||
version, err := run("git describe --tags --always --dirty")
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/TecharoHQ/anubis/lib/policy/config"
|
||||
"github.com/TecharoHQ/anubis/lib/config"
|
||||
|
||||
"sigs.k8s.io/yaml"
|
||||
)
|
||||
@@ -29,7 +29,7 @@ var (
|
||||
)
|
||||
|
||||
type RobotsRule struct {
|
||||
UserAgent string
|
||||
UserAgents []string
|
||||
Disallows []string
|
||||
Allows []string
|
||||
CrawlDelay int
|
||||
@@ -130,10 +130,26 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
func createRuleFromAccumulated(userAgents, disallows, allows []string, crawlDelay int) RobotsRule {
|
||||
rule := RobotsRule{
|
||||
UserAgents: make([]string, len(userAgents)),
|
||||
Disallows: make([]string, len(disallows)),
|
||||
Allows: make([]string, len(allows)),
|
||||
CrawlDelay: crawlDelay,
|
||||
}
|
||||
copy(rule.UserAgents, userAgents)
|
||||
copy(rule.Disallows, disallows)
|
||||
copy(rule.Allows, allows)
|
||||
return rule
|
||||
}
|
||||
|
||||
func parseRobotsTxt(input io.Reader) ([]RobotsRule, error) {
|
||||
scanner := bufio.NewScanner(input)
|
||||
var rules []RobotsRule
|
||||
var currentRule *RobotsRule
|
||||
var currentUserAgents []string
|
||||
var currentDisallows []string
|
||||
var currentAllows []string
|
||||
var currentCrawlDelay int
|
||||
|
||||
for scanner.Scan() {
|
||||
line := strings.TrimSpace(scanner.Text())
|
||||
@@ -154,38 +170,42 @@ func parseRobotsTxt(input io.Reader) ([]RobotsRule, error) {
|
||||
|
||||
switch directive {
|
||||
case "user-agent":
|
||||
// Start a new rule section
|
||||
if currentRule != nil {
|
||||
rules = append(rules, *currentRule)
|
||||
}
|
||||
currentRule = &RobotsRule{
|
||||
UserAgent: value,
|
||||
Disallows: make([]string, 0),
|
||||
Allows: make([]string, 0),
|
||||
// If we have accumulated rules with directives and encounter a new user-agent,
|
||||
// flush the current rules
|
||||
if len(currentUserAgents) > 0 && (len(currentDisallows) > 0 || len(currentAllows) > 0 || currentCrawlDelay > 0) {
|
||||
rule := createRuleFromAccumulated(currentUserAgents, currentDisallows, currentAllows, currentCrawlDelay)
|
||||
rules = append(rules, rule)
|
||||
// Reset for next group
|
||||
currentUserAgents = nil
|
||||
currentDisallows = nil
|
||||
currentAllows = nil
|
||||
currentCrawlDelay = 0
|
||||
}
|
||||
currentUserAgents = append(currentUserAgents, value)
|
||||
|
||||
case "disallow":
|
||||
if currentRule != nil && value != "" {
|
||||
currentRule.Disallows = append(currentRule.Disallows, value)
|
||||
if len(currentUserAgents) > 0 && value != "" {
|
||||
currentDisallows = append(currentDisallows, value)
|
||||
}
|
||||
|
||||
case "allow":
|
||||
if currentRule != nil && value != "" {
|
||||
currentRule.Allows = append(currentRule.Allows, value)
|
||||
if len(currentUserAgents) > 0 && value != "" {
|
||||
currentAllows = append(currentAllows, value)
|
||||
}
|
||||
|
||||
case "crawl-delay":
|
||||
if currentRule != nil {
|
||||
if len(currentUserAgents) > 0 {
|
||||
if delay, err := parseIntSafe(value); err == nil {
|
||||
currentRule.CrawlDelay = delay
|
||||
currentCrawlDelay = delay
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Don't forget the last rule
|
||||
if currentRule != nil {
|
||||
rules = append(rules, *currentRule)
|
||||
// Don't forget the last group of rules
|
||||
if len(currentUserAgents) > 0 {
|
||||
rule := createRuleFromAccumulated(currentUserAgents, currentDisallows, currentAllows, currentCrawlDelay)
|
||||
rules = append(rules, rule)
|
||||
}
|
||||
|
||||
// Mark blacklisted user agents (those with "Disallow: /")
|
||||
@@ -211,10 +231,11 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
||||
var anubisRules []AnubisRule
|
||||
ruleCounter := 0
|
||||
|
||||
// Process each robots rule individually
|
||||
for _, robotsRule := range robotsRules {
|
||||
userAgent := robotsRule.UserAgent
|
||||
userAgents := robotsRule.UserAgents
|
||||
|
||||
// Handle crawl delay as weight adjustment (do this first before any continues)
|
||||
// Handle crawl delay
|
||||
if robotsRule.CrawlDelay > 0 && *crawlDelay > 0 {
|
||||
ruleCounter++
|
||||
rule := AnubisRule{
|
||||
@@ -223,20 +244,32 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
||||
Weight: &config.Weight{Adjust: *crawlDelay},
|
||||
}
|
||||
|
||||
if userAgent == "*" {
|
||||
if len(userAgents) == 1 && userAgents[0] == "*" {
|
||||
rule.Expression = &config.ExpressionOrList{
|
||||
All: []string{"true"}, // Always applies
|
||||
}
|
||||
} else {
|
||||
} else if len(userAgents) == 1 {
|
||||
rule.Expression = &config.ExpressionOrList{
|
||||
All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgent)},
|
||||
All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgents[0])},
|
||||
}
|
||||
} else {
|
||||
// Multiple user agents - use any block
|
||||
var expressions []string
|
||||
for _, ua := range userAgents {
|
||||
if ua == "*" {
|
||||
expressions = append(expressions, "true")
|
||||
} else {
|
||||
expressions = append(expressions, fmt.Sprintf("userAgent.contains(%q)", ua))
|
||||
}
|
||||
}
|
||||
rule.Expression = &config.ExpressionOrList{
|
||||
Any: expressions,
|
||||
}
|
||||
}
|
||||
|
||||
anubisRules = append(anubisRules, rule)
|
||||
}
|
||||
|
||||
// Handle blacklisted user agents (complete deny/challenge)
|
||||
// Handle blacklisted user agents
|
||||
if robotsRule.IsBlacklist {
|
||||
ruleCounter++
|
||||
rule := AnubisRule{
|
||||
@@ -244,21 +277,36 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
||||
Action: *userAgentDeny,
|
||||
}
|
||||
|
||||
if userAgent == "*" {
|
||||
// This would block everything - convert to a weight adjustment instead
|
||||
rule.Name = fmt.Sprintf("%s-global-restriction-%d", *policyName, ruleCounter)
|
||||
rule.Action = "WEIGH"
|
||||
rule.Weight = &config.Weight{Adjust: 20} // Increase difficulty significantly
|
||||
rule.Expression = &config.ExpressionOrList{
|
||||
All: []string{"true"}, // Always applies
|
||||
if len(userAgents) == 1 {
|
||||
userAgent := userAgents[0]
|
||||
if userAgent == "*" {
|
||||
// This would block everything - convert to a weight adjustment instead
|
||||
rule.Name = fmt.Sprintf("%s-global-restriction-%d", *policyName, ruleCounter)
|
||||
rule.Action = "WEIGH"
|
||||
rule.Weight = &config.Weight{Adjust: 20} // Increase difficulty significantly
|
||||
rule.Expression = &config.ExpressionOrList{
|
||||
All: []string{"true"}, // Always applies
|
||||
}
|
||||
} else {
|
||||
rule.Expression = &config.ExpressionOrList{
|
||||
All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgent)},
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Multiple user agents - use any block
|
||||
var expressions []string
|
||||
for _, ua := range userAgents {
|
||||
if ua == "*" {
|
||||
expressions = append(expressions, "true")
|
||||
} else {
|
||||
expressions = append(expressions, fmt.Sprintf("userAgent.contains(%q)", ua))
|
||||
}
|
||||
}
|
||||
rule.Expression = &config.ExpressionOrList{
|
||||
All: []string{fmt.Sprintf("userAgent.contains(%q)", userAgent)},
|
||||
Any: expressions,
|
||||
}
|
||||
}
|
||||
anubisRules = append(anubisRules, rule)
|
||||
continue
|
||||
}
|
||||
|
||||
// Handle specific disallow rules
|
||||
@@ -276,9 +324,33 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
||||
// Build CEL expression
|
||||
var conditions []string
|
||||
|
||||
// Add user agent condition if not wildcard
|
||||
if userAgent != "*" {
|
||||
conditions = append(conditions, fmt.Sprintf("userAgent.contains(%q)", userAgent))
|
||||
// Add user agent conditions
|
||||
if len(userAgents) == 1 && userAgents[0] == "*" {
|
||||
// Wildcard user agent - no user agent condition needed
|
||||
} else if len(userAgents) == 1 {
|
||||
conditions = append(conditions, fmt.Sprintf("userAgent.contains(%q)", userAgents[0]))
|
||||
} else {
|
||||
// For multiple user agents, we need to use a more complex expression
|
||||
// This is a limitation - we can't easily combine any for user agents with all for path
|
||||
// So we'll create separate rules for each user agent
|
||||
for _, ua := range userAgents {
|
||||
if ua == "*" {
|
||||
continue // Skip wildcard as it's handled separately
|
||||
}
|
||||
ruleCounter++
|
||||
subRule := AnubisRule{
|
||||
Name: fmt.Sprintf("%s-disallow-%d", *policyName, ruleCounter),
|
||||
Action: *baseAction,
|
||||
Expression: &config.ExpressionOrList{
|
||||
All: []string{
|
||||
fmt.Sprintf("userAgent.contains(%q)", ua),
|
||||
buildPathCondition(disallow),
|
||||
},
|
||||
},
|
||||
}
|
||||
anubisRules = append(anubisRules, subRule)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Add path condition
|
||||
@@ -291,7 +363,6 @@ func convertToAnubisRules(robotsRules []RobotsRule) []AnubisRule {
|
||||
|
||||
anubisRules = append(anubisRules, rule)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return anubisRules
|
||||
|
||||
@@ -22,9 +22,9 @@ type TestCase struct {
|
||||
type TestOptions struct {
|
||||
format string
|
||||
action string
|
||||
crawlDelayWeight int
|
||||
policyName string
|
||||
deniedAction string
|
||||
crawlDelayWeight int
|
||||
}
|
||||
|
||||
func TestDataFileConversion(t *testing.T) {
|
||||
@@ -78,6 +78,12 @@ func TestDataFileConversion(t *testing.T) {
|
||||
expectedFile: "complex.yaml",
|
||||
options: TestOptions{format: "yaml", crawlDelayWeight: 5},
|
||||
},
|
||||
{
|
||||
name: "consecutive_user_agents",
|
||||
robotsFile: "consecutive.robots.txt",
|
||||
expectedFile: "consecutive.yaml",
|
||||
options: TestOptions{format: "yaml", crawlDelayWeight: 3},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
|
||||
6
cmd/robots2policy/testdata/blacklist.yaml
vendored
6
cmd/robots2policy/testdata/blacklist.yaml
vendored
@@ -25,6 +25,6 @@
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("Googlebot")
|
||||
- path.startsWith("/search")
|
||||
name: robots-txt-policy-disallow-7
|
||||
- userAgent.contains("Googlebot")
|
||||
- path.startsWith("/search")
|
||||
name: robots-txt-policy-disallow-7
|
||||
|
||||
24
cmd/robots2policy/testdata/complex.yaml
vendored
24
cmd/robots2policy/testdata/complex.yaml
vendored
@@ -20,8 +20,8 @@
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("Googlebot")
|
||||
- path.startsWith("/search/")
|
||||
- userAgent.contains("Googlebot")
|
||||
- path.startsWith("/search/")
|
||||
name: robots-txt-policy-disallow-6
|
||||
- action: WEIGH
|
||||
expression: userAgent.contains("Bingbot")
|
||||
@@ -31,14 +31,14 @@
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("Bingbot")
|
||||
- path.startsWith("/search/")
|
||||
- userAgent.contains("Bingbot")
|
||||
- path.startsWith("/search/")
|
||||
name: robots-txt-policy-disallow-8
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("Bingbot")
|
||||
- path.startsWith("/admin/")
|
||||
- userAgent.contains("Bingbot")
|
||||
- path.startsWith("/admin/")
|
||||
name: robots-txt-policy-disallow-9
|
||||
- action: DENY
|
||||
expression: userAgent.contains("BadBot")
|
||||
@@ -54,18 +54,18 @@
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/.*/admin")
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/.*/admin")
|
||||
name: robots-txt-policy-disallow-13
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/temp.*\\.html")
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/temp.*\\.html")
|
||||
name: robots-txt-policy-disallow-14
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/file.\\.log")
|
||||
- userAgent.contains("TestBot")
|
||||
- path.matches("^/file.\\.log")
|
||||
name: robots-txt-policy-disallow-15
|
||||
|
||||
25
cmd/robots2policy/testdata/consecutive.robots.txt
vendored
Normal file
25
cmd/robots2policy/testdata/consecutive.robots.txt
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
# Test consecutive user agents that should be grouped into any: blocks
|
||||
User-agent: *
|
||||
Disallow: /admin
|
||||
Crawl-delay: 10
|
||||
|
||||
# Multiple consecutive user agents - should be grouped
|
||||
User-agent: BadBot
|
||||
User-agent: SpamBot
|
||||
User-agent: EvilBot
|
||||
Disallow: /
|
||||
|
||||
# Single user agent - should be separate
|
||||
User-agent: GoodBot
|
||||
Disallow: /private
|
||||
|
||||
# Multiple consecutive user agents with crawl delay
|
||||
User-agent: SlowBot1
|
||||
User-agent: SlowBot2
|
||||
Crawl-delay: 5
|
||||
|
||||
# Multiple consecutive user agents with specific path
|
||||
User-agent: SearchBot1
|
||||
User-agent: SearchBot2
|
||||
User-agent: SearchBot3
|
||||
Disallow: /search
|
||||
47
cmd/robots2policy/testdata/consecutive.yaml
vendored
Normal file
47
cmd/robots2policy/testdata/consecutive.yaml
vendored
Normal file
@@ -0,0 +1,47 @@
|
||||
- action: WEIGH
|
||||
expression: "true"
|
||||
name: robots-txt-policy-crawl-delay-1
|
||||
weight:
|
||||
adjust: 3
|
||||
- action: CHALLENGE
|
||||
expression: path.startsWith("/admin")
|
||||
name: robots-txt-policy-disallow-2
|
||||
- action: DENY
|
||||
expression:
|
||||
any:
|
||||
- userAgent.contains("BadBot")
|
||||
- userAgent.contains("SpamBot")
|
||||
- userAgent.contains("EvilBot")
|
||||
name: robots-txt-policy-blacklist-3
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("GoodBot")
|
||||
- path.startsWith("/private")
|
||||
name: robots-txt-policy-disallow-4
|
||||
- action: WEIGH
|
||||
expression:
|
||||
any:
|
||||
- userAgent.contains("SlowBot1")
|
||||
- userAgent.contains("SlowBot2")
|
||||
name: robots-txt-policy-crawl-delay-5
|
||||
weight:
|
||||
adjust: 3
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("SearchBot1")
|
||||
- path.startsWith("/search")
|
||||
name: robots-txt-policy-disallow-7
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("SearchBot2")
|
||||
- path.startsWith("/search")
|
||||
name: robots-txt-policy-disallow-8
|
||||
- action: CHALLENGE
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("SearchBot3")
|
||||
- path.startsWith("/search")
|
||||
name: robots-txt-policy-disallow-9
|
||||
8
cmd/robots2policy/testdata/simple.json
vendored
8
cmd/robots2policy/testdata/simple.json
vendored
@@ -1,12 +1,12 @@
|
||||
[
|
||||
{
|
||||
"action": "CHALLENGE",
|
||||
"expression": "path.startsWith(\"/admin/\")",
|
||||
"name": "robots-txt-policy-disallow-1"
|
||||
"name": "robots-txt-policy-disallow-1",
|
||||
"action": "CHALLENGE"
|
||||
},
|
||||
{
|
||||
"action": "CHALLENGE",
|
||||
"expression": "path.startsWith(\"/private\")",
|
||||
"name": "robots-txt-policy-disallow-2"
|
||||
"name": "robots-txt-policy-disallow-2",
|
||||
"action": "CHALLENGE"
|
||||
}
|
||||
]
|
||||
@@ -3,5 +3,6 @@
|
||||
- name: qualys-ssl-labs
|
||||
action: ALLOW
|
||||
remote_addresses:
|
||||
- 64.41.200.0/24
|
||||
- 2600:C02:1020:4202::/64
|
||||
- 69.67.183.0/24
|
||||
- 2600:C02:1020:4202::/64
|
||||
- 2602:fdaa:c6:2::/64
|
||||
@@ -1,29 +0,0 @@
|
||||
{
|
||||
"bots": [
|
||||
{
|
||||
"import": "(data)/bots/_deny-pathological.yaml"
|
||||
},
|
||||
{
|
||||
"import": "(data)/meta/ai-block-aggressive.yaml"
|
||||
},
|
||||
{
|
||||
"import": "(data)/crawlers/_allow-good.yaml"
|
||||
},
|
||||
{
|
||||
"import": "(data)/bots/aggressive-brazilian-scrapers.yaml"
|
||||
},
|
||||
{
|
||||
"import": "(data)/common/keep-internet-working.yaml"
|
||||
},
|
||||
{
|
||||
"name": "generic-browser",
|
||||
"user_agent_regex": "Mozilla|Opera",
|
||||
"action": "CHALLENGE"
|
||||
}
|
||||
],
|
||||
"dnsbl": false,
|
||||
"status_codes": {
|
||||
"CHALLENGE": 200,
|
||||
"DENY": 200
|
||||
}
|
||||
}
|
||||
@@ -11,9 +11,12 @@
|
||||
## /usr/share/docs/anubis/data or in the tarball you extracted Anubis from.
|
||||
|
||||
bots:
|
||||
# You can import the entire default config with this macro:
|
||||
# - import: (data)/meta/default-config.yaml
|
||||
|
||||
# Pathological bots to deny
|
||||
- # This correlates to data/bots/deny-pathological.yaml in the source tree
|
||||
# https://github.com/TecharoHQ/anubis/blob/main/data/bots/deny-pathological.yaml
|
||||
- # This correlates to data/bots/_deny-pathological.yaml in the source tree
|
||||
# https://github.com/TecharoHQ/anubis/blob/main/data/bots/_deny-pathological.yaml
|
||||
import: (data)/bots/_deny-pathological.yaml
|
||||
- import: (data)/bots/aggressive-brazilian-scrapers.yaml
|
||||
|
||||
@@ -47,8 +50,7 @@ bots:
|
||||
# user_agent_regex: (?i:bot|crawler)
|
||||
# action: CHALLENGE
|
||||
# challenge:
|
||||
# difficulty: 16 # impossible
|
||||
# report_as: 4 # lie to the operator
|
||||
# difficulty: 16 # impossible
|
||||
# algorithm: slow # intentionally waste CPU cycles and time
|
||||
|
||||
# Requires a subscription to Thoth to use, see
|
||||
@@ -202,7 +204,6 @@ thresholds:
|
||||
# https://anubis.techaro.lol/docs/admin/configuration/challenges/metarefresh
|
||||
algorithm: metarefresh
|
||||
difficulty: 1
|
||||
report_as: 1
|
||||
# For clients that are browser-like but have either gained points from custom rules or
|
||||
# report as a standard browser.
|
||||
- name: moderate-suspicion
|
||||
@@ -215,13 +216,21 @@ thresholds:
|
||||
# https://anubis.techaro.lol/docs/admin/configuration/challenges/proof-of-work
|
||||
algorithm: fast
|
||||
difficulty: 2 # two leading zeros, very fast for most clients
|
||||
report_as: 2
|
||||
# For clients that are browser like and have gained many points from custom rules
|
||||
- name: extreme-suspicion
|
||||
expression: weight >= 20
|
||||
- name: mild-proof-of-work
|
||||
expression:
|
||||
all:
|
||||
- weight >= 20
|
||||
- weight < 30
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
# https://anubis.techaro.lol/docs/admin/configuration/challenges/proof-of-work
|
||||
algorithm: fast
|
||||
difficulty: 4
|
||||
report_as: 4
|
||||
# For clients that are browser like and have gained many points from custom rules
|
||||
- name: extreme-suspicion
|
||||
expression: weight >= 30
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
# https://anubis.techaro.lol/docs/admin/configuration/challenges/proof-of-work
|
||||
algorithm: fast
|
||||
difficulty: 6
|
||||
|
||||
@@ -2,3 +2,5 @@
|
||||
- import: (data)/bots/headless-browsers.yaml
|
||||
- import: (data)/bots/us-ai-scraper.yaml
|
||||
- import: (data)/bots/custom-async-http-client.yaml
|
||||
- import: (data)/crawlers/alibaba-cloud.yaml
|
||||
- import: (data)/crawlers/huawei-cloud.yaml
|
||||
|
||||
@@ -4,5 +4,5 @@
|
||||
# CCBot is allowed because if Common Crawl is allowed, then scrapers don't need to scrape to get the data.
|
||||
- name: "ai-robots-txt"
|
||||
user_agent_regex: >-
|
||||
AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|Andibot|anthropic-ai|Applebot|Applebot-Extended|bedrockbot|Brightbot 1.0|Bytespider|ChatGPT-User|Claude-SearchBot|Claude-User|Claude-Web|ClaudeBot|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Diffbot|DuckAssistBot|EchoboxBot|FacebookBot|facebookexternalhit|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Google-CloudVertexBot|Google-Extended|GoogleOther|GoogleOther-Image|GoogleOther-Video|GPTBot|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|ISSCyberRiskCrawler|Kangaroo Bot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|MistralAI-User/1.0|MyCentralAIScraperBot|NovaAct|OAI-SearchBot|omgili|omgilibot|Operator|PanguBot|Panscient|panscient.com|Perplexity-User|PerplexityBot|PetalBot|PhindBot|Poseidon Research Crawler|QualifiedBot|QuillBot|quillbot.com|SBIntuitionsBot|Scrapy|SemrushBot|SemrushBot-BA|SemrushBot-CT|SemrushBot-OCOB|SemrushBot-SI|SemrushBot-SWA|Sidetrade indexer bot|TikTokSpider|Timpibot|VelenPublicWebCrawler|Webzio-Extended|wpbot|YandexAdditional|YandexAdditionalBot|YouBot
|
||||
AddSearchBot|AI2Bot|Ai2Bot-Dolma|aiHitBot|Amazonbot|Andibot|anthropic-ai|Applebot|Applebot-Extended|Awario|bedrockbot|bigsur.ai|Brightbot 1.0|Bytespider|CCBot|ChatGPT Agent|ChatGPT-User|Claude-SearchBot|Claude-User|Claude-Web|ClaudeBot|CloudVertexBot|cohere-ai|cohere-training-data-crawler|Cotoyogi|Crawlspace|Datenbank Crawler|Devin|Diffbot|DuckAssistBot|Echobot Bot|EchoboxBot|FacebookBot|facebookexternalhit|Factset_spyderbot|FirecrawlAgent|FriendlyCrawler|Gemini-Deep-Research|Google-CloudVertexBot|Google-Extended|GoogleAgent-Mariner|GoogleOther|GoogleOther-Image|GoogleOther-Video|GPTBot|iaskspider/2.0|ICC-Crawler|ImagesiftBot|img2dataset|ISSCyberRiskCrawler|Kangaroo Bot|LinerBot|meta-externalagent|Meta-ExternalAgent|meta-externalfetcher|Meta-ExternalFetcher|MistralAI-User|MistralAI-User/1.0|MyCentralAIScraperBot|netEstate Imprint Crawler|NovaAct|OAI-SearchBot|omgili|omgilibot|OpenAI|Operator|PanguBot|Panscient|panscient.com|Perplexity-User|PerplexityBot|PetalBot|PhindBot|Poseidon Research Crawler|QualifiedBot|QuillBot|quillbot.com|SBIntuitionsBot|Scrapy|SemrushBot-OCOB|SemrushBot-SWA|Sidetrade indexer bot|Thinkbot|TikTokSpider|Timpibot|VelenPublicWebCrawler|WARDBot|Webzio-Extended|wpbot|YaK|YandexAdditional|YandexAdditionalBot|YouBot
|
||||
action: DENY
|
||||
|
||||
60
data/clients/docker-client.yaml
Normal file
60
data/clients/docker-client.yaml
Normal file
@@ -0,0 +1,60 @@
|
||||
- name: allow-docker-client
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- path.startsWith("/v2/")
|
||||
- userAgent.contains("docker/")
|
||||
- userAgent.contains("git-commit/")
|
||||
- '"Accept" in headers'
|
||||
- headers["Accept"].contains("vnd.docker.distribution")
|
||||
- '"Baggage" in headers'
|
||||
- headers["Baggage"].contains("trigger")
|
||||
|
||||
- name: allow-crane-client
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- userAgent.contains("crane/")
|
||||
- userAgent.contains("go-containerregistry/")
|
||||
|
||||
- name: allow-docker-distribution-api-client
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- '"Docker-Distribution-Api-Version" in headers'
|
||||
- '!(userAgent.contains("Mozilla"))'
|
||||
|
||||
- name: allow-go-containerregistry-client
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- path.startsWith("/v2/")
|
||||
- userAgent.contains("go-containerregistry/")
|
||||
|
||||
- name: allow-buildah
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- path.startsWith("/v2/")
|
||||
- userAgent.contains("Buildah/")
|
||||
|
||||
- name: allow-podman
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- path.startsWith("/v2/")
|
||||
- userAgent.contains("containers/")
|
||||
|
||||
- name: allow-containerd
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- path.startsWith("/v2/")
|
||||
- userAgent.contains("containerd/")
|
||||
|
||||
- name: allow-renovate
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- path.startsWith("/v2/")
|
||||
- userAgent.contains("Renovate/")
|
||||
@@ -2,13 +2,19 @@
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- >
|
||||
(
|
||||
userAgent.startsWith("git/") ||
|
||||
userAgent.contains("libgit") ||
|
||||
userAgent.startsWith("go-git") ||
|
||||
userAgent.startsWith("JGit/") ||
|
||||
userAgent.startsWith("JGit-")
|
||||
)
|
||||
- '"Git-Protocol" in headers'
|
||||
- headers["Git-Protocol"] == "version=2"
|
||||
- >
|
||||
(
|
||||
userAgent.startsWith("git/") ||
|
||||
userAgent.contains("libgit") ||
|
||||
userAgent.startsWith("go-git") ||
|
||||
userAgent.startsWith("JGit/") ||
|
||||
userAgent.startsWith("JGit-")
|
||||
)
|
||||
- '"Accept" in headers'
|
||||
- headers["Accept"] == "*/*"
|
||||
- '"Cache-Control" in headers'
|
||||
- headers["Cache-Control"] == "no-cache"
|
||||
- '"Pragma" in headers'
|
||||
- headers["Pragma"] == "no-cache"
|
||||
- '"Accept-Encoding" in headers'
|
||||
- headers["Accept-Encoding"].contains("gzip")
|
||||
|
||||
6
data/clients/telegram-preview.yaml
Normal file
6
data/clients/telegram-preview.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
- name: telegrambot
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- userAgent.matches("TelegramBot")
|
||||
- verifyFCrDNS(remoteAddress, "ptr\\.telegram\\.org$")
|
||||
6
data/clients/vk-preview.yaml
Normal file
6
data/clients/vk-preview.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
- name: vkbot
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- userAgent.matches("vkShare[^+]+\\+http\\://vk\\.com/dev/Share")
|
||||
- verifyFCrDNS(remoteAddress, "^snipster\\d+\\.go\\.mail\\.ru$")
|
||||
@@ -1,13 +1,13 @@
|
||||
# Common "keeping the internet working" routes
|
||||
- name: well-known
|
||||
path_regex: ^/.well-known/.*$
|
||||
path_regex: ^/\.well-known/.*$
|
||||
action: ALLOW
|
||||
- name: favicon
|
||||
path_regex: ^/favicon.ico$
|
||||
path_regex: ^/favicon\.(?:ico|png|gif|jpg|jpeg|svg)$
|
||||
action: ALLOW
|
||||
- name: robots-txt
|
||||
path_regex: ^/robots.txt$
|
||||
path_regex: ^/robots\.txt$
|
||||
action: ALLOW
|
||||
- name: sitemap
|
||||
path_regex: ^/sitemap.xml$
|
||||
action: ALLOW
|
||||
path_regex: ^/sitemap\.xml$
|
||||
action: ALLOW
|
||||
|
||||
@@ -8,3 +8,4 @@
|
||||
- import: (data)/crawlers/marginalia.yaml
|
||||
- import: (data)/crawlers/mojeekbot.yaml
|
||||
- import: (data)/crawlers/commoncrawl.yaml
|
||||
- import: (data)/crawlers/yandexbot.yaml
|
||||
|
||||
881
data/crawlers/alibaba-cloud.yaml
Normal file
881
data/crawlers/alibaba-cloud.yaml
Normal file
@@ -0,0 +1,881 @@
|
||||
- name: alibaba-cloud
|
||||
action: DENY
|
||||
# Updated 2025-08-20 from IP addresses for AS45102
|
||||
remote_addresses:
|
||||
- 103.81.186.0/23
|
||||
- 110.76.21.0/24
|
||||
- 110.76.23.0/24
|
||||
- 116.251.64.0/18
|
||||
- 139.95.0.0/23
|
||||
- 139.95.10.0/23
|
||||
- 139.95.12.0/23
|
||||
- 139.95.14.0/23
|
||||
- 139.95.16.0/23
|
||||
- 139.95.18.0/23
|
||||
- 139.95.2.0/23
|
||||
- 139.95.4.0/23
|
||||
- 139.95.6.0/23
|
||||
- 139.95.64.0/24
|
||||
- 139.95.8.0/23
|
||||
- 14.1.112.0/22
|
||||
- 14.1.115.0/24
|
||||
- 140.205.1.0/24
|
||||
- 140.205.122.0/24
|
||||
- 147.139.0.0/17
|
||||
- 147.139.0.0/18
|
||||
- 147.139.128.0/17
|
||||
- 147.139.128.0/18
|
||||
- 147.139.155.0/24
|
||||
- 147.139.192.0/18
|
||||
- 147.139.64.0/18
|
||||
- 149.129.0.0/20
|
||||
- 149.129.0.0/21
|
||||
- 149.129.16.0/23
|
||||
- 149.129.192.0/18
|
||||
- 149.129.192.0/19
|
||||
- 149.129.224.0/19
|
||||
- 149.129.32.0/19
|
||||
- 149.129.64.0/18
|
||||
- 149.129.64.0/19
|
||||
- 149.129.8.0/21
|
||||
- 149.129.96.0/19
|
||||
- 156.227.20.0/24
|
||||
- 156.236.12.0/24
|
||||
- 156.236.17.0/24
|
||||
- 156.240.76.0/23
|
||||
- 156.245.1.0/24
|
||||
- 161.117.0.0/16
|
||||
- 161.117.0.0/17
|
||||
- 161.117.126.0/24
|
||||
- 161.117.127.0/24
|
||||
- 161.117.128.0/17
|
||||
- 161.117.128.0/24
|
||||
- 161.117.129.0/24
|
||||
- 161.117.138.0/24
|
||||
- 161.117.143.0/24
|
||||
- 170.33.104.0/24
|
||||
- 170.33.105.0/24
|
||||
- 170.33.106.0/24
|
||||
- 170.33.107.0/24
|
||||
- 170.33.136.0/24
|
||||
- 170.33.137.0/24
|
||||
- 170.33.138.0/24
|
||||
- 170.33.20.0/24
|
||||
- 170.33.21.0/24
|
||||
- 170.33.22.0/24
|
||||
- 170.33.23.0/24
|
||||
- 170.33.24.0/24
|
||||
- 170.33.29.0/24
|
||||
- 170.33.30.0/24
|
||||
- 170.33.31.0/24
|
||||
- 170.33.32.0/24
|
||||
- 170.33.33.0/24
|
||||
- 170.33.34.0/24
|
||||
- 170.33.35.0/24
|
||||
- 170.33.64.0/24
|
||||
- 170.33.65.0/24
|
||||
- 170.33.66.0/24
|
||||
- 170.33.68.0/24
|
||||
- 170.33.69.0/24
|
||||
- 170.33.72.0/24
|
||||
- 170.33.73.0/24
|
||||
- 170.33.76.0/24
|
||||
- 170.33.77.0/24
|
||||
- 170.33.78.0/24
|
||||
- 170.33.79.0/24
|
||||
- 170.33.80.0/24
|
||||
- 170.33.81.0/24
|
||||
- 170.33.82.0/24
|
||||
- 170.33.83.0/24
|
||||
- 170.33.84.0/24
|
||||
- 170.33.85.0/24
|
||||
- 170.33.86.0/24
|
||||
- 170.33.88.0/24
|
||||
- 170.33.90.0/24
|
||||
- 170.33.92.0/24
|
||||
- 170.33.93.0/24
|
||||
- 185.78.106.0/23
|
||||
- 198.11.128.0/18
|
||||
- 198.11.137.0/24
|
||||
- 198.11.184.0/21
|
||||
- 202.144.199.0/24
|
||||
- 203.107.64.0/24
|
||||
- 203.107.65.0/24
|
||||
- 203.107.66.0/24
|
||||
- 203.107.67.0/24
|
||||
- 203.107.68.0/24
|
||||
- 205.204.102.0/23
|
||||
- 205.204.111.0/24
|
||||
- 205.204.117.0/24
|
||||
- 205.204.125.0/24
|
||||
- 205.204.96.0/19
|
||||
- 223.5.5.0/24
|
||||
- 223.6.6.0/24
|
||||
- 2400:3200::/48
|
||||
- 2400:3200:baba::/48
|
||||
- 2400:b200:4100::/48
|
||||
- 2400:b200:4101::/48
|
||||
- 2400:b200:4102::/48
|
||||
- 2400:b200:4103::/48
|
||||
- 2401:8680:4100::/48
|
||||
- 2401:b180:4100::/48
|
||||
- 2404:2280:1000::/36
|
||||
- 2404:2280:1000::/37
|
||||
- 2404:2280:1800::/37
|
||||
- 2404:2280:2000::/36
|
||||
- 2404:2280:2000::/37
|
||||
- 2404:2280:2800::/37
|
||||
- 2404:2280:3000::/36
|
||||
- 2404:2280:3000::/37
|
||||
- 2404:2280:3800::/37
|
||||
- 2404:2280:4000::/36
|
||||
- 2404:2280:4000::/37
|
||||
- 2404:2280:4800::/37
|
||||
- 2408:4000:1000::/48
|
||||
- 2408:4009:500::/48
|
||||
- 240b:4000::/32
|
||||
- 240b:4000::/33
|
||||
- 240b:4000:8000::/33
|
||||
- 240b:4000:fffe::/48
|
||||
- 240b:4001::/32
|
||||
- 240b:4001::/33
|
||||
- 240b:4001:8000::/33
|
||||
- 240b:4002::/32
|
||||
- 240b:4002::/33
|
||||
- 240b:4002:8000::/33
|
||||
- 240b:4004::/32
|
||||
- 240b:4004::/33
|
||||
- 240b:4004:8000::/33
|
||||
- 240b:4005::/32
|
||||
- 240b:4005::/33
|
||||
- 240b:4005:8000::/33
|
||||
- 240b:4006::/48
|
||||
- 240b:4006:1000::/44
|
||||
- 240b:4006:1000::/45
|
||||
- 240b:4006:1000::/47
|
||||
- 240b:4006:1002::/47
|
||||
- 240b:4006:1008::/45
|
||||
- 240b:4006:1010::/44
|
||||
- 240b:4006:1010::/45
|
||||
- 240b:4006:1018::/45
|
||||
- 240b:4006:1020::/44
|
||||
- 240b:4006:1020::/45
|
||||
- 240b:4006:1028::/45
|
||||
- 240b:4007::/32
|
||||
- 240b:4007::/33
|
||||
- 240b:4007:8000::/33
|
||||
- 240b:4009::/32
|
||||
- 240b:4009::/33
|
||||
- 240b:4009:8000::/33
|
||||
- 240b:400b::/32
|
||||
- 240b:400b::/33
|
||||
- 240b:400b:8000::/33
|
||||
- 240b:400c::/32
|
||||
- 240b:400c::/33
|
||||
- 240b:400c::/40
|
||||
- 240b:400c::/41
|
||||
- 240b:400c:100::/40
|
||||
- 240b:400c:100::/41
|
||||
- 240b:400c:180::/41
|
||||
- 240b:400c:80::/41
|
||||
- 240b:400c:8000::/33
|
||||
- 240b:400c:f00::/48
|
||||
- 240b:400c:f01::/48
|
||||
- 240b:400c:ffff::/48
|
||||
- 240b:400d::/32
|
||||
- 240b:400d::/33
|
||||
- 240b:400d:8000::/33
|
||||
- 240b:400e::/32
|
||||
- 240b:400e::/33
|
||||
- 240b:400e:8000::/33
|
||||
- 240b:400f::/32
|
||||
- 240b:400f::/33
|
||||
- 240b:400f:8000::/33
|
||||
- 240b:4011::/32
|
||||
- 240b:4011::/33
|
||||
- 240b:4011:8000::/33
|
||||
- 240b:4012::/48
|
||||
- 240b:4013::/32
|
||||
- 240b:4013::/33
|
||||
- 240b:4013:8000::/33
|
||||
- 240b:4014::/32
|
||||
- 240b:4014::/33
|
||||
- 240b:4014:8000::/33
|
||||
- 43.100.0.0/15
|
||||
- 43.100.0.0/16
|
||||
- 43.101.0.0/16
|
||||
- 43.102.0.0/20
|
||||
- 43.102.112.0/20
|
||||
- 43.102.16.0/20
|
||||
- 43.102.32.0/20
|
||||
- 43.102.48.0/20
|
||||
- 43.102.64.0/20
|
||||
- 43.102.80.0/20
|
||||
- 43.102.96.0/20
|
||||
- 43.103.0.0/17
|
||||
- 43.103.0.0/18
|
||||
- 43.103.64.0/18
|
||||
- 43.104.0.0/15
|
||||
- 43.104.0.0/16
|
||||
- 43.105.0.0/16
|
||||
- 43.108.0.0/17
|
||||
- 43.108.0.0/18
|
||||
- 43.108.64.0/18
|
||||
- 43.91.0.0/16
|
||||
- 43.91.0.0/17
|
||||
- 43.91.128.0/17
|
||||
- 43.96.10.0/24
|
||||
- 43.96.100.0/24
|
||||
- 43.96.101.0/24
|
||||
- 43.96.102.0/24
|
||||
- 43.96.104.0/24
|
||||
- 43.96.11.0/24
|
||||
- 43.96.20.0/24
|
||||
- 43.96.21.0/24
|
||||
- 43.96.23.0/24
|
||||
- 43.96.24.0/24
|
||||
- 43.96.25.0/24
|
||||
- 43.96.3.0/24
|
||||
- 43.96.32.0/24
|
||||
- 43.96.33.0/24
|
||||
- 43.96.34.0/24
|
||||
- 43.96.35.0/24
|
||||
- 43.96.4.0/24
|
||||
- 43.96.40.0/24
|
||||
- 43.96.5.0/24
|
||||
- 43.96.52.0/24
|
||||
- 43.96.6.0/24
|
||||
- 43.96.66.0/24
|
||||
- 43.96.67.0/24
|
||||
- 43.96.68.0/24
|
||||
- 43.96.69.0/24
|
||||
- 43.96.7.0/24
|
||||
- 43.96.70.0/24
|
||||
- 43.96.71.0/24
|
||||
- 43.96.72.0/24
|
||||
- 43.96.73.0/24
|
||||
- 43.96.74.0/24
|
||||
- 43.96.75.0/24
|
||||
- 43.96.8.0/24
|
||||
- 43.96.80.0/24
|
||||
- 43.96.81.0/24
|
||||
- 43.96.84.0/24
|
||||
- 43.96.85.0/24
|
||||
- 43.96.86.0/24
|
||||
- 43.96.88.0/24
|
||||
- 43.96.9.0/24
|
||||
- 43.96.96.0/24
|
||||
- 43.98.0.0/16
|
||||
- 43.98.0.0/17
|
||||
- 43.98.128.0/17
|
||||
- 43.99.0.0/16
|
||||
- 43.99.0.0/17
|
||||
- 43.99.128.0/17
|
||||
- 45.199.179.0/24
|
||||
- 47.235.0.0/22
|
||||
- 47.235.0.0/23
|
||||
- 47.235.1.0/24
|
||||
- 47.235.10.0/23
|
||||
- 47.235.10.0/24
|
||||
- 47.235.11.0/24
|
||||
- 47.235.12.0/23
|
||||
- 47.235.12.0/24
|
||||
- 47.235.13.0/24
|
||||
- 47.235.16.0/23
|
||||
- 47.235.16.0/24
|
||||
- 47.235.18.0/23
|
||||
- 47.235.18.0/24
|
||||
- 47.235.19.0/24
|
||||
- 47.235.2.0/23
|
||||
- 47.235.20.0/24
|
||||
- 47.235.21.0/24
|
||||
- 47.235.22.0/24
|
||||
- 47.235.23.0/24
|
||||
- 47.235.24.0/22
|
||||
- 47.235.24.0/23
|
||||
- 47.235.26.0/23
|
||||
- 47.235.28.0/23
|
||||
- 47.235.28.0/24
|
||||
- 47.235.29.0/24
|
||||
- 47.235.30.0/24
|
||||
- 47.235.31.0/24
|
||||
- 47.235.4.0/24
|
||||
- 47.235.5.0/24
|
||||
- 47.235.6.0/23
|
||||
- 47.235.6.0/24
|
||||
- 47.235.7.0/24
|
||||
- 47.235.8.0/24
|
||||
- 47.235.9.0/24
|
||||
- 47.236.0.0/15
|
||||
- 47.236.0.0/16
|
||||
- 47.237.0.0/16
|
||||
- 47.237.32.0/20
|
||||
- 47.237.34.0/24
|
||||
- 47.238.0.0/15
|
||||
- 47.238.0.0/16
|
||||
- 47.239.0.0/16
|
||||
- 47.240.0.0/16
|
||||
- 47.240.0.0/17
|
||||
- 47.240.128.0/17
|
||||
- 47.241.0.0/16
|
||||
- 47.241.0.0/17
|
||||
- 47.241.128.0/17
|
||||
- 47.242.0.0/15
|
||||
- 47.242.0.0/16
|
||||
- 47.243.0.0/16
|
||||
- 47.244.0.0/16
|
||||
- 47.244.0.0/17
|
||||
- 47.244.128.0/17
|
||||
- 47.244.73.0/24
|
||||
- 47.245.0.0/18
|
||||
- 47.245.0.0/19
|
||||
- 47.245.128.0/17
|
||||
- 47.245.128.0/18
|
||||
- 47.245.192.0/18
|
||||
- 47.245.32.0/19
|
||||
- 47.245.64.0/18
|
||||
- 47.245.64.0/19
|
||||
- 47.245.96.0/19
|
||||
- 47.246.100.0/22
|
||||
- 47.246.104.0/21
|
||||
- 47.246.104.0/22
|
||||
- 47.246.108.0/22
|
||||
- 47.246.120.0/24
|
||||
- 47.246.122.0/24
|
||||
- 47.246.123.0/24
|
||||
- 47.246.124.0/24
|
||||
- 47.246.125.0/24
|
||||
- 47.246.128.0/22
|
||||
- 47.246.128.0/23
|
||||
- 47.246.130.0/23
|
||||
- 47.246.132.0/22
|
||||
- 47.246.132.0/23
|
||||
- 47.246.134.0/23
|
||||
- 47.246.136.0/21
|
||||
- 47.246.136.0/22
|
||||
- 47.246.140.0/22
|
||||
- 47.246.144.0/23
|
||||
- 47.246.144.0/24
|
||||
- 47.246.145.0/24
|
||||
- 47.246.146.0/23
|
||||
- 47.246.146.0/24
|
||||
- 47.246.147.0/24
|
||||
- 47.246.150.0/23
|
||||
- 47.246.150.0/24
|
||||
- 47.246.151.0/24
|
||||
- 47.246.152.0/23
|
||||
- 47.246.152.0/24
|
||||
- 47.246.153.0/24
|
||||
- 47.246.154.0/24
|
||||
- 47.246.155.0/24
|
||||
- 47.246.156.0/22
|
||||
- 47.246.156.0/23
|
||||
- 47.246.158.0/23
|
||||
- 47.246.160.0/20
|
||||
- 47.246.160.0/21
|
||||
- 47.246.168.0/21
|
||||
- 47.246.176.0/20
|
||||
- 47.246.176.0/21
|
||||
- 47.246.184.0/21
|
||||
- 47.246.192.0/22
|
||||
- 47.246.192.0/23
|
||||
- 47.246.194.0/23
|
||||
- 47.246.196.0/22
|
||||
- 47.246.196.0/23
|
||||
- 47.246.198.0/23
|
||||
- 47.246.32.0/22
|
||||
- 47.246.66.0/24
|
||||
- 47.246.67.0/24
|
||||
- 47.246.68.0/23
|
||||
- 47.246.68.0/24
|
||||
- 47.246.69.0/24
|
||||
- 47.246.72.0/21
|
||||
- 47.246.72.0/22
|
||||
- 47.246.76.0/22
|
||||
- 47.246.80.0/24
|
||||
- 47.246.82.0/23
|
||||
- 47.246.82.0/24
|
||||
- 47.246.83.0/24
|
||||
- 47.246.84.0/22
|
||||
- 47.246.84.0/23
|
||||
- 47.246.86.0/23
|
||||
- 47.246.88.0/22
|
||||
- 47.246.88.0/23
|
||||
- 47.246.90.0/23
|
||||
- 47.246.92.0/23
|
||||
- 47.246.92.0/24
|
||||
- 47.246.93.0/24
|
||||
- 47.246.96.0/21
|
||||
- 47.246.96.0/22
|
||||
- 47.250.0.0/17
|
||||
- 47.250.0.0/18
|
||||
- 47.250.128.0/17
|
||||
- 47.250.128.0/18
|
||||
- 47.250.192.0/18
|
||||
- 47.250.64.0/18
|
||||
- 47.250.99.0/24
|
||||
- 47.251.0.0/16
|
||||
- 47.251.0.0/17
|
||||
- 47.251.128.0/17
|
||||
- 47.251.224.0/22
|
||||
- 47.252.0.0/17
|
||||
- 47.252.0.0/18
|
||||
- 47.252.128.0/17
|
||||
- 47.252.128.0/18
|
||||
- 47.252.192.0/18
|
||||
- 47.252.64.0/18
|
||||
- 47.252.67.0/24
|
||||
- 47.253.0.0/16
|
||||
- 47.253.0.0/17
|
||||
- 47.253.128.0/17
|
||||
- 47.254.0.0/17
|
||||
- 47.254.0.0/18
|
||||
- 47.254.113.0/24
|
||||
- 47.254.128.0/18
|
||||
- 47.254.128.0/19
|
||||
- 47.254.160.0/19
|
||||
- 47.254.192.0/18
|
||||
- 47.254.192.0/19
|
||||
- 47.254.224.0/19
|
||||
- 47.254.64.0/18
|
||||
- 47.52.0.0/16
|
||||
- 47.52.0.0/17
|
||||
- 47.52.128.0/17
|
||||
- 47.56.0.0/15
|
||||
- 47.56.0.0/16
|
||||
- 47.57.0.0/16
|
||||
- 47.74.0.0/18
|
||||
- 47.74.0.0/19
|
||||
- 47.74.0.0/21
|
||||
- 47.74.128.0/17
|
||||
- 47.74.128.0/18
|
||||
- 47.74.192.0/18
|
||||
- 47.74.32.0/19
|
||||
- 47.74.64.0/18
|
||||
- 47.74.64.0/19
|
||||
- 47.74.96.0/19
|
||||
- 47.75.0.0/16
|
||||
- 47.75.0.0/17
|
||||
- 47.75.128.0/17
|
||||
- 47.76.0.0/16
|
||||
- 47.76.0.0/17
|
||||
- 47.76.128.0/17
|
||||
- 47.77.0.0/22
|
||||
- 47.77.0.0/23
|
||||
- 47.77.104.0/21
|
||||
- 47.77.12.0/22
|
||||
- 47.77.128.0/17
|
||||
- 47.77.128.0/18
|
||||
- 47.77.128.0/21
|
||||
- 47.77.136.0/21
|
||||
- 47.77.144.0/21
|
||||
- 47.77.152.0/21
|
||||
- 47.77.16.0/21
|
||||
- 47.77.16.0/22
|
||||
- 47.77.192.0/18
|
||||
- 47.77.2.0/23
|
||||
- 47.77.20.0/22
|
||||
- 47.77.24.0/22
|
||||
- 47.77.24.0/23
|
||||
- 47.77.26.0/23
|
||||
- 47.77.32.0/19
|
||||
- 47.77.32.0/20
|
||||
- 47.77.4.0/22
|
||||
- 47.77.4.0/23
|
||||
- 47.77.48.0/20
|
||||
- 47.77.6.0/23
|
||||
- 47.77.64.0/19
|
||||
- 47.77.64.0/20
|
||||
- 47.77.8.0/21
|
||||
- 47.77.8.0/22
|
||||
- 47.77.80.0/20
|
||||
- 47.77.96.0/20
|
||||
- 47.77.96.0/21
|
||||
- 47.78.0.0/17
|
||||
- 47.78.128.0/17
|
||||
- 47.79.0.0/20
|
||||
- 47.79.0.0/21
|
||||
- 47.79.104.0/21
|
||||
- 47.79.112.0/20
|
||||
- 47.79.128.0/19
|
||||
- 47.79.128.0/20
|
||||
- 47.79.144.0/20
|
||||
- 47.79.16.0/20
|
||||
- 47.79.16.0/21
|
||||
- 47.79.192.0/18
|
||||
- 47.79.192.0/19
|
||||
- 47.79.224.0/19
|
||||
- 47.79.24.0/21
|
||||
- 47.79.32.0/20
|
||||
- 47.79.32.0/21
|
||||
- 47.79.40.0/21
|
||||
- 47.79.48.0/20
|
||||
- 47.79.48.0/21
|
||||
- 47.79.52.0/23
|
||||
- 47.79.54.0/23
|
||||
- 47.79.56.0/21
|
||||
- 47.79.56.0/23
|
||||
- 47.79.58.0/23
|
||||
- 47.79.60.0/23
|
||||
- 47.79.62.0/23
|
||||
- 47.79.64.0/20
|
||||
- 47.79.64.0/21
|
||||
- 47.79.72.0/21
|
||||
- 47.79.8.0/21
|
||||
- 47.79.80.0/20
|
||||
- 47.79.80.0/21
|
||||
- 47.79.83.0/24
|
||||
- 47.79.88.0/21
|
||||
- 47.79.96.0/19
|
||||
- 47.79.96.0/20
|
||||
- 47.80.0.0/18
|
||||
- 47.80.0.0/19
|
||||
- 47.80.128.0/17
|
||||
- 47.80.128.0/18
|
||||
- 47.80.192.0/18
|
||||
- 47.80.32.0/19
|
||||
- 47.80.64.0/18
|
||||
- 47.80.64.0/19
|
||||
- 47.80.96.0/19
|
||||
- 47.81.0.0/18
|
||||
- 47.81.0.0/19
|
||||
- 47.81.128.0/17
|
||||
- 47.81.128.0/18
|
||||
- 47.81.192.0/18
|
||||
- 47.81.32.0/19
|
||||
- 47.81.64.0/18
|
||||
- 47.81.64.0/19
|
||||
- 47.81.96.0/19
|
||||
- 47.82.0.0/18
|
||||
- 47.82.0.0/19
|
||||
- 47.82.10.0/23
|
||||
- 47.82.12.0/23
|
||||
- 47.82.128.0/17
|
||||
- 47.82.128.0/18
|
||||
- 47.82.14.0/23
|
||||
- 47.82.192.0/18
|
||||
- 47.82.32.0/19
|
||||
- 47.82.32.0/21
|
||||
- 47.82.40.0/21
|
||||
- 47.82.48.0/21
|
||||
- 47.82.56.0/21
|
||||
- 47.82.64.0/18
|
||||
- 47.82.64.0/19
|
||||
- 47.82.8.0/23
|
||||
- 47.82.96.0/19
|
||||
- 47.83.0.0/16
|
||||
- 47.83.0.0/17
|
||||
- 47.83.128.0/17
|
||||
- 47.83.32.0/21
|
||||
- 47.83.40.0/21
|
||||
- 47.83.48.0/21
|
||||
- 47.83.56.0/21
|
||||
- 47.84.0.0/16
|
||||
- 47.84.0.0/17
|
||||
- 47.84.128.0/17
|
||||
- 47.84.144.0/21
|
||||
- 47.84.152.0/21
|
||||
- 47.84.160.0/21
|
||||
- 47.84.168.0/21
|
||||
- 47.85.0.0/16
|
||||
- 47.85.0.0/17
|
||||
- 47.85.112.0/22
|
||||
- 47.85.112.0/23
|
||||
- 47.85.114.0/23
|
||||
- 47.85.128.0/17
|
||||
- 47.86.0.0/16
|
||||
- 47.86.0.0/17
|
||||
- 47.86.128.0/17
|
||||
- 47.87.0.0/18
|
||||
- 47.87.0.0/19
|
||||
- 47.87.128.0/18
|
||||
- 47.87.128.0/19
|
||||
- 47.87.160.0/19
|
||||
- 47.87.192.0/22
|
||||
- 47.87.192.0/23
|
||||
- 47.87.194.0/23
|
||||
- 47.87.196.0/22
|
||||
- 47.87.196.0/23
|
||||
- 47.87.198.0/23
|
||||
- 47.87.200.0/22
|
||||
- 47.87.200.0/23
|
||||
- 47.87.202.0/23
|
||||
- 47.87.204.0/22
|
||||
- 47.87.204.0/23
|
||||
- 47.87.206.0/23
|
||||
- 47.87.208.0/22
|
||||
- 47.87.208.0/23
|
||||
- 47.87.210.0/23
|
||||
- 47.87.212.0/22
|
||||
- 47.87.212.0/23
|
||||
- 47.87.214.0/23
|
||||
- 47.87.216.0/22
|
||||
- 47.87.216.0/23
|
||||
- 47.87.218.0/23
|
||||
- 47.87.220.0/22
|
||||
- 47.87.220.0/23
|
||||
- 47.87.222.0/23
|
||||
- 47.87.224.0/22
|
||||
- 47.87.224.0/23
|
||||
- 47.87.226.0/23
|
||||
- 47.87.228.0/22
|
||||
- 47.87.228.0/23
|
||||
- 47.87.230.0/23
|
||||
- 47.87.232.0/22
|
||||
- 47.87.232.0/23
|
||||
- 47.87.234.0/23
|
||||
- 47.87.236.0/22
|
||||
- 47.87.236.0/23
|
||||
- 47.87.238.0/23
|
||||
- 47.87.240.0/22
|
||||
- 47.87.240.0/23
|
||||
- 47.87.242.0/23
|
||||
- 47.87.32.0/19
|
||||
- 47.87.64.0/18
|
||||
- 47.87.64.0/19
|
||||
- 47.87.96.0/19
|
||||
- 47.88.0.0/17
|
||||
- 47.88.0.0/18
|
||||
- 47.88.109.0/24
|
||||
- 47.88.128.0/17
|
||||
- 47.88.128.0/18
|
||||
- 47.88.135.0/24
|
||||
- 47.88.192.0/18
|
||||
- 47.88.41.0/24
|
||||
- 47.88.42.0/24
|
||||
- 47.88.43.0/24
|
||||
- 47.88.64.0/18
|
||||
- 47.89.0.0/18
|
||||
- 47.89.0.0/19
|
||||
- 47.89.100.0/24
|
||||
- 47.89.101.0/24
|
||||
- 47.89.102.0/24
|
||||
- 47.89.103.0/24
|
||||
- 47.89.104.0/21
|
||||
- 47.89.104.0/22
|
||||
- 47.89.108.0/22
|
||||
- 47.89.122.0/24
|
||||
- 47.89.123.0/24
|
||||
- 47.89.124.0/23
|
||||
- 47.89.124.0/24
|
||||
- 47.89.125.0/24
|
||||
- 47.89.128.0/18
|
||||
- 47.89.128.0/19
|
||||
- 47.89.160.0/19
|
||||
- 47.89.192.0/18
|
||||
- 47.89.192.0/19
|
||||
- 47.89.221.0/24
|
||||
- 47.89.224.0/19
|
||||
- 47.89.32.0/19
|
||||
- 47.89.72.0/22
|
||||
- 47.89.72.0/23
|
||||
- 47.89.74.0/23
|
||||
- 47.89.76.0/22
|
||||
- 47.89.76.0/23
|
||||
- 47.89.78.0/23
|
||||
- 47.89.80.0/23
|
||||
- 47.89.82.0/23
|
||||
- 47.89.84.0/24
|
||||
- 47.89.88.0/22
|
||||
- 47.89.88.0/23
|
||||
- 47.89.90.0/23
|
||||
- 47.89.92.0/22
|
||||
- 47.89.92.0/23
|
||||
- 47.89.94.0/23
|
||||
- 47.89.96.0/24
|
||||
- 47.89.97.0/24
|
||||
- 47.89.98.0/23
|
||||
- 47.89.99.0/24
|
||||
- 47.90.0.0/17
|
||||
- 47.90.0.0/18
|
||||
- 47.90.128.0/17
|
||||
- 47.90.128.0/18
|
||||
- 47.90.172.0/24
|
||||
- 47.90.173.0/24
|
||||
- 47.90.174.0/24
|
||||
- 47.90.175.0/24
|
||||
- 47.90.192.0/18
|
||||
- 47.90.64.0/18
|
||||
- 47.91.0.0/19
|
||||
- 47.91.0.0/20
|
||||
- 47.91.112.0/20
|
||||
- 47.91.128.0/17
|
||||
- 47.91.128.0/18
|
||||
- 47.91.16.0/20
|
||||
- 47.91.192.0/18
|
||||
- 47.91.32.0/19
|
||||
- 47.91.32.0/20
|
||||
- 47.91.48.0/20
|
||||
- 47.91.64.0/19
|
||||
- 47.91.64.0/20
|
||||
- 47.91.80.0/20
|
||||
- 47.91.96.0/19
|
||||
- 47.91.96.0/20
|
||||
- 5.181.224.0/23
|
||||
- 59.82.136.0/23
|
||||
- 8.208.0.0/16
|
||||
- 8.208.0.0/17
|
||||
- 8.208.0.0/18
|
||||
- 8.208.0.0/19
|
||||
- 8.208.128.0/17
|
||||
- 8.208.141.0/24
|
||||
- 8.208.32.0/19
|
||||
- 8.209.0.0/19
|
||||
- 8.209.0.0/20
|
||||
- 8.209.128.0/18
|
||||
- 8.209.128.0/19
|
||||
- 8.209.16.0/20
|
||||
- 8.209.160.0/19
|
||||
- 8.209.192.0/18
|
||||
- 8.209.192.0/19
|
||||
- 8.209.224.0/19
|
||||
- 8.209.36.0/23
|
||||
- 8.209.36.0/24
|
||||
- 8.209.37.0/24
|
||||
- 8.209.38.0/23
|
||||
- 8.209.38.0/24
|
||||
- 8.209.39.0/24
|
||||
- 8.209.40.0/22
|
||||
- 8.209.40.0/23
|
||||
- 8.209.42.0/23
|
||||
- 8.209.44.0/22
|
||||
- 8.209.44.0/23
|
||||
- 8.209.46.0/23
|
||||
- 8.209.48.0/20
|
||||
- 8.209.48.0/21
|
||||
- 8.209.56.0/21
|
||||
- 8.209.64.0/18
|
||||
- 8.209.64.0/19
|
||||
- 8.209.96.0/19
|
||||
- 8.210.0.0/16
|
||||
- 8.210.0.0/17
|
||||
- 8.210.128.0/17
|
||||
- 8.210.240.0/24
|
||||
- 8.211.0.0/17
|
||||
- 8.211.0.0/18
|
||||
- 8.211.104.0/21
|
||||
- 8.211.128.0/18
|
||||
- 8.211.128.0/19
|
||||
- 8.211.160.0/19
|
||||
- 8.211.192.0/18
|
||||
- 8.211.192.0/19
|
||||
- 8.211.224.0/19
|
||||
- 8.211.226.0/24
|
||||
- 8.211.64.0/18
|
||||
- 8.211.80.0/21
|
||||
- 8.211.88.0/21
|
||||
- 8.211.96.0/21
|
||||
- 8.212.0.0/17
|
||||
- 8.212.0.0/18
|
||||
- 8.212.128.0/18
|
||||
- 8.212.128.0/19
|
||||
- 8.212.160.0/19
|
||||
- 8.212.192.0/18
|
||||
- 8.212.192.0/19
|
||||
- 8.212.224.0/19
|
||||
- 8.212.64.0/18
|
||||
- 8.213.0.0/17
|
||||
- 8.213.0.0/18
|
||||
- 8.213.128.0/19
|
||||
- 8.213.128.0/20
|
||||
- 8.213.144.0/20
|
||||
- 8.213.160.0/21
|
||||
- 8.213.160.0/22
|
||||
- 8.213.164.0/22
|
||||
- 8.213.176.0/20
|
||||
- 8.213.176.0/21
|
||||
- 8.213.184.0/21
|
||||
- 8.213.192.0/18
|
||||
- 8.213.192.0/19
|
||||
- 8.213.224.0/19
|
||||
- 8.213.251.0/24
|
||||
- 8.213.252.0/24
|
||||
- 8.213.253.0/24
|
||||
- 8.213.64.0/18
|
||||
- 8.214.0.0/16
|
||||
- 8.214.0.0/17
|
||||
- 8.214.128.0/17
|
||||
- 8.215.0.0/16
|
||||
- 8.215.0.0/17
|
||||
- 8.215.128.0/17
|
||||
- 8.215.160.0/24
|
||||
- 8.215.162.0/23
|
||||
- 8.215.168.0/24
|
||||
- 8.215.169.0/24
|
||||
- 8.216.0.0/17
|
||||
- 8.216.0.0/18
|
||||
- 8.216.128.0/17
|
||||
- 8.216.128.0/18
|
||||
- 8.216.148.0/24
|
||||
- 8.216.192.0/18
|
||||
- 8.216.64.0/18
|
||||
- 8.216.69.0/24
|
||||
- 8.216.74.0/24
|
||||
- 8.217.0.0/16
|
||||
- 8.217.0.0/17
|
||||
- 8.217.128.0/17
|
||||
- 8.218.0.0/16
|
||||
- 8.218.0.0/17
|
||||
- 8.218.128.0/17
|
||||
- 8.219.0.0/16
|
||||
- 8.219.0.0/17
|
||||
- 8.219.128.0/17
|
||||
- 8.219.40.0/21
|
||||
- 8.220.116.0/24
|
||||
- 8.220.128.0/18
|
||||
- 8.220.128.0/19
|
||||
- 8.220.147.0/24
|
||||
- 8.220.160.0/19
|
||||
- 8.220.192.0/18
|
||||
- 8.220.192.0/19
|
||||
- 8.220.224.0/19
|
||||
- 8.220.229.0/24
|
||||
- 8.220.64.0/18
|
||||
- 8.220.64.0/19
|
||||
- 8.220.96.0/19
|
||||
- 8.221.0.0/17
|
||||
- 8.221.0.0/18
|
||||
- 8.221.0.0/21
|
||||
- 8.221.128.0/17
|
||||
- 8.221.128.0/18
|
||||
- 8.221.184.0/22
|
||||
- 8.221.188.0/22
|
||||
- 8.221.192.0/18
|
||||
- 8.221.192.0/21
|
||||
- 8.221.200.0/21
|
||||
- 8.221.208.0/21
|
||||
- 8.221.216.0/21
|
||||
- 8.221.48.0/21
|
||||
- 8.221.56.0/21
|
||||
- 8.221.64.0/18
|
||||
- 8.221.8.0/21
|
||||
- 8.222.0.0/20
|
||||
- 8.222.0.0/21
|
||||
- 8.222.112.0/20
|
||||
- 8.222.128.0/17
|
||||
- 8.222.128.0/18
|
||||
- 8.222.16.0/20
|
||||
- 8.222.16.0/21
|
||||
- 8.222.192.0/18
|
||||
- 8.222.24.0/21
|
||||
- 8.222.32.0/20
|
||||
- 8.222.32.0/21
|
||||
- 8.222.40.0/21
|
||||
- 8.222.48.0/20
|
||||
- 8.222.48.0/21
|
||||
- 8.222.56.0/21
|
||||
- 8.222.64.0/20
|
||||
- 8.222.64.0/21
|
||||
- 8.222.72.0/21
|
||||
- 8.222.8.0/21
|
||||
- 8.222.80.0/20
|
||||
- 8.222.80.0/21
|
||||
- 8.222.88.0/21
|
||||
- 8.222.96.0/19
|
||||
- 8.222.96.0/20
|
||||
- 8.223.0.0/17
|
||||
- 8.223.0.0/18
|
||||
- 8.223.128.0/17
|
||||
- 8.223.128.0/18
|
||||
- 8.223.192.0/18
|
||||
- 8.223.64.0/18
|
||||
617
data/crawlers/huawei-cloud.yaml
Normal file
617
data/crawlers/huawei-cloud.yaml
Normal file
@@ -0,0 +1,617 @@
|
||||
- name: huawei-cloud
|
||||
action: DENY
|
||||
# Updated 2025-08-20 from IP addresses for AS136907
|
||||
remote_addresses:
|
||||
- 1.178.32.0/20
|
||||
- 1.178.48.0/20
|
||||
- 101.44.0.0/20
|
||||
- 101.44.144.0/20
|
||||
- 101.44.16.0/20
|
||||
- 101.44.160.0/20
|
||||
- 101.44.173.0/24
|
||||
- 101.44.176.0/20
|
||||
- 101.44.192.0/20
|
||||
- 101.44.208.0/22
|
||||
- 101.44.212.0/22
|
||||
- 101.44.216.0/22
|
||||
- 101.44.220.0/22
|
||||
- 101.44.224.0/22
|
||||
- 101.44.228.0/22
|
||||
- 101.44.232.0/22
|
||||
- 101.44.236.0/22
|
||||
- 101.44.240.0/22
|
||||
- 101.44.244.0/22
|
||||
- 101.44.248.0/22
|
||||
- 101.44.252.0/24
|
||||
- 101.44.253.0/24
|
||||
- 101.44.254.0/24
|
||||
- 101.44.255.0/24
|
||||
- 101.44.32.0/20
|
||||
- 101.44.48.0/20
|
||||
- 101.44.64.0/20
|
||||
- 101.44.80.0/20
|
||||
- 101.44.96.0/20
|
||||
- 101.46.0.0/20
|
||||
- 101.46.128.0/21
|
||||
- 101.46.136.0/21
|
||||
- 101.46.144.0/21
|
||||
- 101.46.152.0/21
|
||||
- 101.46.160.0/21
|
||||
- 101.46.168.0/21
|
||||
- 101.46.176.0/21
|
||||
- 101.46.184.0/21
|
||||
- 101.46.192.0/21
|
||||
- 101.46.200.0/21
|
||||
- 101.46.208.0/21
|
||||
- 101.46.216.0/21
|
||||
- 101.46.224.0/22
|
||||
- 101.46.232.0/22
|
||||
- 101.46.236.0/22
|
||||
- 101.46.240.0/22
|
||||
- 101.46.244.0/22
|
||||
- 101.46.248.0/22
|
||||
- 101.46.252.0/24
|
||||
- 101.46.253.0/24
|
||||
- 101.46.254.0/24
|
||||
- 101.46.255.0/24
|
||||
- 101.46.32.0/20
|
||||
- 101.46.48.0/20
|
||||
- 101.46.64.0/20
|
||||
- 101.46.80.0/20
|
||||
- 103.198.203.0/24
|
||||
- 103.215.0.0/24
|
||||
- 103.215.1.0/24
|
||||
- 103.215.3.0/24
|
||||
- 103.240.156.0/22
|
||||
- 103.240.157.0/24
|
||||
- 103.255.60.0/22
|
||||
- 103.255.60.0/24
|
||||
- 103.255.61.0/24
|
||||
- 103.255.62.0/24
|
||||
- 103.255.63.0/24
|
||||
- 103.40.100.0/23
|
||||
- 103.84.110.0/24
|
||||
- 110.238.100.0/22
|
||||
- 110.238.104.0/21
|
||||
- 110.238.112.0/21
|
||||
- 110.238.120.0/22
|
||||
- 110.238.124.0/22
|
||||
- 110.238.64.0/21
|
||||
- 110.238.72.0/21
|
||||
- 110.238.80.0/20
|
||||
- 110.238.96.0/24
|
||||
- 110.238.98.0/24
|
||||
- 110.238.99.0/24
|
||||
- 110.239.127.0/24
|
||||
- 110.239.184.0/22
|
||||
- 110.239.188.0/23
|
||||
- 110.239.190.0/23
|
||||
- 110.239.64.0/19
|
||||
- 110.239.96.0/19
|
||||
- 110.41.208.0/24
|
||||
- 110.41.209.0/24
|
||||
- 110.41.210.0/24
|
||||
- 111.119.192.0/20
|
||||
- 111.119.208.0/20
|
||||
- 111.119.224.0/20
|
||||
- 111.119.240.0/20
|
||||
- 111.91.0.0/20
|
||||
- 111.91.112.0/20
|
||||
- 111.91.16.0/20
|
||||
- 111.91.32.0/20
|
||||
- 111.91.48.0/20
|
||||
- 111.91.64.0/20
|
||||
- 111.91.80.0/20
|
||||
- 111.91.96.0/20
|
||||
- 114.119.128.0/19
|
||||
- 114.119.160.0/21
|
||||
- 114.119.168.0/24
|
||||
- 114.119.169.0/24
|
||||
- 114.119.170.0/24
|
||||
- 114.119.171.0/24
|
||||
- 114.119.172.0/22
|
||||
- 114.119.176.0/20
|
||||
- 115.30.32.0/20
|
||||
- 115.30.48.0/20
|
||||
- 119.12.160.0/20
|
||||
- 119.13.112.0/20
|
||||
- 119.13.160.0/24
|
||||
- 119.13.161.0/24
|
||||
- 119.13.162.0/23
|
||||
- 119.13.163.0/24
|
||||
- 119.13.164.0/22
|
||||
- 119.13.168.0/21
|
||||
- 119.13.168.0/24
|
||||
- 119.13.169.0/24
|
||||
- 119.13.170.0/24
|
||||
- 119.13.172.0/24
|
||||
- 119.13.173.0/24
|
||||
- 119.13.32.0/22
|
||||
- 119.13.36.0/22
|
||||
- 119.13.64.0/24
|
||||
- 119.13.65.0/24
|
||||
- 119.13.66.0/23
|
||||
- 119.13.68.0/22
|
||||
- 119.13.72.0/22
|
||||
- 119.13.76.0/22
|
||||
- 119.13.80.0/21
|
||||
- 119.13.88.0/22
|
||||
- 119.13.92.0/22
|
||||
- 119.13.96.0/20
|
||||
- 119.8.0.0/21
|
||||
- 119.8.128.0/24
|
||||
- 119.8.129.0/24
|
||||
- 119.8.130.0/23
|
||||
- 119.8.132.0/22
|
||||
- 119.8.136.0/21
|
||||
- 119.8.144.0/20
|
||||
- 119.8.160.0/19
|
||||
- 119.8.18.0/24
|
||||
- 119.8.192.0/20
|
||||
- 119.8.192.0/21
|
||||
- 119.8.200.0/21
|
||||
- 119.8.208.0/20
|
||||
- 119.8.21.0/24
|
||||
- 119.8.22.0/24
|
||||
- 119.8.224.0/24
|
||||
- 119.8.227.0/24
|
||||
- 119.8.228.0/22
|
||||
- 119.8.23.0/24
|
||||
- 119.8.232.0/21
|
||||
- 119.8.24.0/21
|
||||
- 119.8.240.0/23
|
||||
- 119.8.242.0/23
|
||||
- 119.8.244.0/24
|
||||
- 119.8.245.0/24
|
||||
- 119.8.246.0/24
|
||||
- 119.8.247.0/24
|
||||
- 119.8.248.0/24
|
||||
- 119.8.249.0/24
|
||||
- 119.8.250.0/24
|
||||
- 119.8.253.0/24
|
||||
- 119.8.254.0/23
|
||||
- 119.8.32.0/19
|
||||
- 119.8.4.0/24
|
||||
- 119.8.64.0/22
|
||||
- 119.8.68.0/24
|
||||
- 119.8.69.0/24
|
||||
- 119.8.70.0/24
|
||||
- 119.8.71.0/24
|
||||
- 119.8.72.0/21
|
||||
- 119.8.8.0/21
|
||||
- 119.8.80.0/20
|
||||
- 119.8.96.0/19
|
||||
- 121.91.152.0/21
|
||||
- 121.91.168.0/21
|
||||
- 121.91.200.0/21
|
||||
- 121.91.200.0/24
|
||||
- 121.91.201.0/24
|
||||
- 121.91.204.0/24
|
||||
- 121.91.205.0/24
|
||||
- 122.8.128.0/20
|
||||
- 122.8.144.0/20
|
||||
- 122.8.160.0/20
|
||||
- 122.8.176.0/21
|
||||
- 122.8.184.0/22
|
||||
- 122.8.188.0/22
|
||||
- 124.243.128.0/18
|
||||
- 124.243.156.0/24
|
||||
- 124.243.157.0/24
|
||||
- 124.243.158.0/24
|
||||
- 124.243.159.0/24
|
||||
- 124.71.248.0/24
|
||||
- 124.71.249.0/24
|
||||
- 124.71.250.0/24
|
||||
- 124.71.252.0/24
|
||||
- 124.71.253.0/24
|
||||
- 124.81.0.0/20
|
||||
- 124.81.112.0/20
|
||||
- 124.81.128.0/20
|
||||
- 124.81.144.0/20
|
||||
- 124.81.16.0/20
|
||||
- 124.81.160.0/20
|
||||
- 124.81.176.0/20
|
||||
- 124.81.192.0/20
|
||||
- 124.81.208.0/20
|
||||
- 124.81.224.0/20
|
||||
- 124.81.240.0/20
|
||||
- 124.81.32.0/20
|
||||
- 124.81.48.0/20
|
||||
- 124.81.64.0/20
|
||||
- 124.81.80.0/20
|
||||
- 124.81.96.0/20
|
||||
- 139.9.98.0/24
|
||||
- 139.9.99.0/24
|
||||
- 14.137.132.0/22
|
||||
- 14.137.136.0/22
|
||||
- 14.137.140.0/22
|
||||
- 14.137.152.0/24
|
||||
- 14.137.153.0/24
|
||||
- 14.137.154.0/24
|
||||
- 14.137.155.0/24
|
||||
- 14.137.156.0/24
|
||||
- 14.137.157.0/24
|
||||
- 14.137.161.0/24
|
||||
- 14.137.163.0/24
|
||||
- 14.137.169.0/24
|
||||
- 14.137.170.0/23
|
||||
- 14.137.172.0/22
|
||||
- 146.174.128.0/20
|
||||
- 146.174.144.0/20
|
||||
- 146.174.160.0/20
|
||||
- 146.174.176.0/20
|
||||
- 148.145.160.0/20
|
||||
- 148.145.192.0/20
|
||||
- 148.145.208.0/20
|
||||
- 148.145.224.0/23
|
||||
- 148.145.234.0/23
|
||||
- 148.145.236.0/23
|
||||
- 148.145.238.0/23
|
||||
- 149.232.128.0/20
|
||||
- 149.232.144.0/20
|
||||
- 150.40.128.0/20
|
||||
- 150.40.144.0/20
|
||||
- 150.40.160.0/20
|
||||
- 150.40.176.0/20
|
||||
- 150.40.182.0/24
|
||||
- 150.40.192.0/20
|
||||
- 150.40.208.0/20
|
||||
- 150.40.224.0/20
|
||||
- 150.40.240.0/20
|
||||
- 154.220.192.0/19
|
||||
- 154.81.16.0/20
|
||||
- 154.83.0.0/23
|
||||
- 154.86.32.0/20
|
||||
- 154.86.48.0/20
|
||||
- 154.93.100.0/23
|
||||
- 154.93.104.0/23
|
||||
- 156.227.22.0/23
|
||||
- 156.230.32.0/21
|
||||
- 156.230.40.0/21
|
||||
- 156.230.64.0/18
|
||||
- 156.232.16.0/20
|
||||
- 156.240.128.0/18
|
||||
- 156.249.32.0/20
|
||||
- 156.253.16.0/20
|
||||
- 157.254.211.0/24
|
||||
- 157.254.212.0/24
|
||||
- 159.138.0.0/20
|
||||
- 159.138.112.0/21
|
||||
- 159.138.114.0/24
|
||||
- 159.138.120.0/22
|
||||
- 159.138.124.0/24
|
||||
- 159.138.125.0/24
|
||||
- 159.138.126.0/23
|
||||
- 159.138.128.0/20
|
||||
- 159.138.144.0/20
|
||||
- 159.138.152.0/21
|
||||
- 159.138.16.0/22
|
||||
- 159.138.160.0/20
|
||||
- 159.138.176.0/23
|
||||
- 159.138.178.0/24
|
||||
- 159.138.179.0/24
|
||||
- 159.138.180.0/24
|
||||
- 159.138.181.0/24
|
||||
- 159.138.182.0/23
|
||||
- 159.138.188.0/23
|
||||
- 159.138.190.0/23
|
||||
- 159.138.192.0/20
|
||||
- 159.138.20.0/22
|
||||
- 159.138.208.0/21
|
||||
- 159.138.216.0/22
|
||||
- 159.138.220.0/23
|
||||
- 159.138.224.0/20
|
||||
- 159.138.24.0/21
|
||||
- 159.138.240.0/20
|
||||
- 159.138.32.0/20
|
||||
- 159.138.48.0/20
|
||||
- 159.138.64.0/21
|
||||
- 159.138.67.0/24
|
||||
- 159.138.76.0/24
|
||||
- 159.138.77.0/24
|
||||
- 159.138.78.0/24
|
||||
- 159.138.79.0/24
|
||||
- 159.138.80.0/20
|
||||
- 159.138.96.0/20
|
||||
- 166.108.192.0/20
|
||||
- 166.108.208.0/20
|
||||
- 166.108.224.0/20
|
||||
- 166.108.240.0/20
|
||||
- 176.52.128.0/20
|
||||
- 176.52.144.0/20
|
||||
- 180.87.192.0/20
|
||||
- 180.87.208.0/20
|
||||
- 180.87.224.0/20
|
||||
- 180.87.240.0/20
|
||||
- 182.160.0.0/20
|
||||
- 182.160.16.0/24
|
||||
- 182.160.17.0/24
|
||||
- 182.160.18.0/23
|
||||
- 182.160.20.0/22
|
||||
- 182.160.20.0/24
|
||||
- 182.160.24.0/21
|
||||
- 182.160.36.0/22
|
||||
- 182.160.49.0/24
|
||||
- 182.160.52.0/22
|
||||
- 182.160.56.0/21
|
||||
- 182.160.56.0/24
|
||||
- 182.160.57.0/24
|
||||
- 182.160.58.0/24
|
||||
- 182.160.59.0/24
|
||||
- 182.160.60.0/24
|
||||
- 182.160.61.0/24
|
||||
- 182.160.62.0/24
|
||||
- 183.87.112.0/20
|
||||
- 183.87.128.0/20
|
||||
- 183.87.144.0/20
|
||||
- 183.87.32.0/20
|
||||
- 183.87.48.0/20
|
||||
- 183.87.64.0/20
|
||||
- 183.87.80.0/20
|
||||
- 183.87.96.0/20
|
||||
- 188.119.192.0/20
|
||||
- 188.119.208.0/20
|
||||
- 188.119.224.0/20
|
||||
- 188.119.240.0/20
|
||||
- 188.239.0.0/20
|
||||
- 188.239.16.0/20
|
||||
- 188.239.32.0/20
|
||||
- 188.239.48.0/20
|
||||
- 189.1.192.0/20
|
||||
- 189.1.208.0/20
|
||||
- 189.1.224.0/20
|
||||
- 189.1.240.0/20
|
||||
- 189.28.112.0/20
|
||||
- 189.28.96.0/20
|
||||
- 190.92.192.0/19
|
||||
- 190.92.224.0/19
|
||||
- 190.92.248.0/24
|
||||
- 190.92.252.0/24
|
||||
- 190.92.253.0/24
|
||||
- 190.92.254.0/24
|
||||
- 201.77.32.0/20
|
||||
- 202.170.88.0/21
|
||||
- 202.76.128.0/20
|
||||
- 202.76.144.0/20
|
||||
- 202.76.160.0/20
|
||||
- 202.76.176.0/20
|
||||
- 203.123.80.0/20
|
||||
- 203.167.20.0/23
|
||||
- 203.167.22.0/24
|
||||
- 212.34.192.0/20
|
||||
- 212.34.208.0/20
|
||||
- 213.250.128.0/20
|
||||
- 213.250.144.0/20
|
||||
- 213.250.160.0/20
|
||||
- 213.250.176.0/21
|
||||
- 213.250.184.0/21
|
||||
- 219.83.0.0/20
|
||||
- 219.83.112.0/22
|
||||
- 219.83.116.0/23
|
||||
- 219.83.118.0/23
|
||||
- 219.83.121.0/24
|
||||
- 219.83.122.0/24
|
||||
- 219.83.123.0/24
|
||||
- 219.83.124.0/24
|
||||
- 219.83.16.0/20
|
||||
- 219.83.32.0/20
|
||||
- 219.83.76.0/23
|
||||
- 2404:a140:43::/48
|
||||
- 2405:f080::/39
|
||||
- 2405:f080:1::/48
|
||||
- 2405:f080:1000::/39
|
||||
- 2405:f080:1200::/39
|
||||
- 2405:f080:1400::/48
|
||||
- 2405:f080:1401::/48
|
||||
- 2405:f080:1402::/48
|
||||
- 2405:f080:1403::/48
|
||||
- 2405:f080:1500::/40
|
||||
- 2405:f080:1600::/48
|
||||
- 2405:f080:1602::/48
|
||||
- 2405:f080:1603::/48
|
||||
- 2405:f080:1800::/39
|
||||
- 2405:f080:1800::/44
|
||||
- 2405:f080:1810::/48
|
||||
- 2405:f080:1811::/48
|
||||
- 2405:f080:1812::/48
|
||||
- 2405:f080:1813::/48
|
||||
- 2405:f080:1814::/48
|
||||
- 2405:f080:1815::/48
|
||||
- 2405:f080:1900::/40
|
||||
- 2405:f080:1e02::/47
|
||||
- 2405:f080:1e04::/47
|
||||
- 2405:f080:1e06::/47
|
||||
- 2405:f080:1e1e::/47
|
||||
- 2405:f080:1e20::/47
|
||||
- 2405:f080:200::/48
|
||||
- 2405:f080:2000::/39
|
||||
- 2405:f080:201::/48
|
||||
- 2405:f080:202::/48
|
||||
- 2405:f080:2040::/48
|
||||
- 2405:f080:2200::/39
|
||||
- 2405:f080:2280::/48
|
||||
- 2405:f080:2281::/48
|
||||
- 2405:f080:2282::/48
|
||||
- 2405:f080:2283::/48
|
||||
- 2405:f080:2284::/48
|
||||
- 2405:f080:2285::/48
|
||||
- 2405:f080:2286::/48
|
||||
- 2405:f080:2287::/48
|
||||
- 2405:f080:2288::/48
|
||||
- 2405:f080:2289::/48
|
||||
- 2405:f080:228a::/48
|
||||
- 2405:f080:228b::/48
|
||||
- 2405:f080:228c::/48
|
||||
- 2405:f080:228d::/48
|
||||
- 2405:f080:228e::/48
|
||||
- 2405:f080:228f::/48
|
||||
- 2405:f080:2400::/39
|
||||
- 2405:f080:2600::/39
|
||||
- 2405:f080:2800::/48
|
||||
- 2405:f080:2a00::/48
|
||||
- 2405:f080:2e00::/47
|
||||
- 2405:f080:3000::/38
|
||||
- 2405:f080:3000::/40
|
||||
- 2405:f080:3100::/40
|
||||
- 2405:f080:3200::/48
|
||||
- 2405:f080:3201::/48
|
||||
- 2405:f080:3202::/48
|
||||
- 2405:f080:3203::/48
|
||||
- 2405:f080:3204::/48
|
||||
- 2405:f080:3205::/48
|
||||
- 2405:f080:3400::/38
|
||||
- 2405:f080:3400::/40
|
||||
- 2405:f080:3500::/40
|
||||
- 2405:f080:3600::/48
|
||||
- 2405:f080:3601::/48
|
||||
- 2405:f080:3602::/48
|
||||
- 2405:f080:3603::/48
|
||||
- 2405:f080:3604::/48
|
||||
- 2405:f080:3605::/48
|
||||
- 2405:f080:400::/39
|
||||
- 2405:f080:4000::/40
|
||||
- 2405:f080:4100::/48
|
||||
- 2405:f080:4102::/48
|
||||
- 2405:f080:4103::/48
|
||||
- 2405:f080:4104::/48
|
||||
- 2405:f080:4200::/40
|
||||
- 2405:f080:4300::/40
|
||||
- 2405:f080:600::/48
|
||||
- 2405:f080:800::/40
|
||||
- 2405:f080:810::/44
|
||||
- 2405:f080:a00::/39
|
||||
- 2405:f080:a11::/48
|
||||
- 2405:f080:e02::/48
|
||||
- 2405:f080:e03::/48
|
||||
- 2405:f080:e04::/47
|
||||
- 2405:f080:e05::/48
|
||||
- 2405:f080:e06::/48
|
||||
- 2405:f080:e07::/48
|
||||
- 2405:f080:e0e::/47
|
||||
- 2405:f080:e10::/47
|
||||
- 2405:f080:edff::/48
|
||||
- 27.106.0.0/20
|
||||
- 27.106.112.0/20
|
||||
- 27.106.16.0/20
|
||||
- 27.106.32.0/20
|
||||
- 27.106.48.0/20
|
||||
- 27.106.64.0/20
|
||||
- 27.106.80.0/20
|
||||
- 27.106.96.0/20
|
||||
- 27.255.0.0/23
|
||||
- 27.255.10.0/23
|
||||
- 27.255.12.0/23
|
||||
- 27.255.14.0/23
|
||||
- 27.255.16.0/23
|
||||
- 27.255.18.0/23
|
||||
- 27.255.2.0/23
|
||||
- 27.255.20.0/23
|
||||
- 27.255.22.0/23
|
||||
- 27.255.26.0/23
|
||||
- 27.255.28.0/23
|
||||
- 27.255.30.0/23
|
||||
- 27.255.32.0/23
|
||||
- 27.255.34.0/23
|
||||
- 27.255.36.0/23
|
||||
- 27.255.38.0/23
|
||||
- 27.255.4.0/23
|
||||
- 27.255.40.0/23
|
||||
- 27.255.42.0/23
|
||||
- 27.255.44.0/23
|
||||
- 27.255.46.0/23
|
||||
- 27.255.48.0/23
|
||||
- 27.255.50.0/23
|
||||
- 27.255.52.0/23
|
||||
- 27.255.54.0/23
|
||||
- 27.255.58.0/23
|
||||
- 27.255.6.0/23
|
||||
- 27.255.60.0/23
|
||||
- 27.255.62.0/23
|
||||
- 27.255.8.0/23
|
||||
- 42.201.128.0/20
|
||||
- 42.201.144.0/20
|
||||
- 42.201.160.0/20
|
||||
- 42.201.176.0/20
|
||||
- 42.201.192.0/20
|
||||
- 42.201.208.0/20
|
||||
- 42.201.224.0/20
|
||||
- 42.201.240.0/20
|
||||
- 43.225.140.0/22
|
||||
- 43.255.104.0/22
|
||||
- 45.194.104.0/21
|
||||
- 45.199.144.0/22
|
||||
- 45.202.128.0/19
|
||||
- 45.202.160.0/20
|
||||
- 45.202.176.0/21
|
||||
- 45.202.184.0/21
|
||||
- 45.203.40.0/21
|
||||
- 46.250.160.0/20
|
||||
- 46.250.176.0/20
|
||||
- 49.0.192.0/21
|
||||
- 49.0.200.0/21
|
||||
- 49.0.224.0/22
|
||||
- 49.0.228.0/22
|
||||
- 49.0.232.0/21
|
||||
- 49.0.240.0/20
|
||||
- 62.245.0.0/20
|
||||
- 62.245.16.0/20
|
||||
- 80.238.128.0/22
|
||||
- 80.238.132.0/22
|
||||
- 80.238.136.0/22
|
||||
- 80.238.140.0/22
|
||||
- 80.238.144.0/22
|
||||
- 80.238.148.0/22
|
||||
- 80.238.152.0/22
|
||||
- 80.238.156.0/22
|
||||
- 80.238.164.0/22
|
||||
- 80.238.164.0/24
|
||||
- 80.238.165.0/24
|
||||
- 80.238.168.0/22
|
||||
- 80.238.168.0/24
|
||||
- 80.238.169.0/24
|
||||
- 80.238.170.0/24
|
||||
- 80.238.171.0/24
|
||||
- 80.238.172.0/22
|
||||
- 80.238.176.0/22
|
||||
- 80.238.180.0/24
|
||||
- 80.238.181.0/24
|
||||
- 80.238.183.0/24
|
||||
- 80.238.184.0/24
|
||||
- 80.238.185.0/24
|
||||
- 80.238.186.0/24
|
||||
- 80.238.190.0/24
|
||||
- 80.238.192.0/20
|
||||
- 80.238.208.0/20
|
||||
- 80.238.224.0/20
|
||||
- 80.238.240.0/20
|
||||
- 83.101.0.0/21
|
||||
- 83.101.104.0/21
|
||||
- 83.101.16.0/21
|
||||
- 83.101.24.0/21
|
||||
- 83.101.32.0/21
|
||||
- 83.101.48.0/21
|
||||
- 83.101.56.0/23
|
||||
- 83.101.58.0/23
|
||||
- 83.101.64.0/21
|
||||
- 83.101.72.0/21
|
||||
- 83.101.8.0/23
|
||||
- 83.101.80.0/21
|
||||
- 83.101.88.0/24
|
||||
- 83.101.89.0/24
|
||||
- 83.101.96.0/21
|
||||
- 87.119.12.0/24
|
||||
- 89.150.192.0/20
|
||||
- 89.150.208.0/20
|
||||
- 94.244.128.0/20
|
||||
- 94.244.144.0/20
|
||||
- 94.244.160.0/20
|
||||
- 94.244.176.0/20
|
||||
- 94.45.160.0/19
|
||||
- 94.45.160.0/24
|
||||
- 94.45.161.0/24
|
||||
- 94.45.163.0/24
|
||||
- 94.74.112.0/21
|
||||
- 94.74.120.0/21
|
||||
- 94.74.64.0/20
|
||||
- 94.74.80.0/20
|
||||
- 94.74.96.0/20
|
||||
165
data/crawlers/tencent-cloud.yaml
Normal file
165
data/crawlers/tencent-cloud.yaml
Normal file
@@ -0,0 +1,165 @@
|
||||
# Tencent Cloud crawler IP ranges
|
||||
- name: tencent-cloud
|
||||
action: DENY
|
||||
remote_addresses:
|
||||
- 101.32.0.0/17
|
||||
- 101.32.176.0/20
|
||||
- 101.32.192.0/18
|
||||
- 101.33.116.0/22
|
||||
- 101.33.120.0/21
|
||||
- 101.33.16.0/20
|
||||
- 101.33.2.0/23
|
||||
- 101.33.32.0/19
|
||||
- 101.33.4.0/22
|
||||
- 101.33.64.0/19
|
||||
- 101.33.8.0/21
|
||||
- 101.33.96.0/20
|
||||
- 119.28.28.0/24
|
||||
- 119.29.29.0/24
|
||||
- 124.156.0.0/16
|
||||
- 129.226.0.0/18
|
||||
- 129.226.128.0/18
|
||||
- 129.226.224.0/19
|
||||
- 129.226.96.0/19
|
||||
- 150.109.0.0/18
|
||||
- 150.109.128.0/20
|
||||
- 150.109.160.0/19
|
||||
- 150.109.192.0/18
|
||||
- 150.109.64.0/20
|
||||
- 150.109.80.0/21
|
||||
- 150.109.88.0/22
|
||||
- 150.109.96.0/19
|
||||
- 162.14.60.0/22
|
||||
- 162.62.0.0/18
|
||||
- 162.62.128.0/20
|
||||
- 162.62.144.0/21
|
||||
- 162.62.152.0/22
|
||||
- 162.62.172.0/22
|
||||
- 162.62.176.0/20
|
||||
- 162.62.192.0/19
|
||||
- 162.62.255.0/24
|
||||
- 162.62.80.0/20
|
||||
- 162.62.96.0/19
|
||||
- 170.106.0.0/16
|
||||
- 43.128.0.0/14
|
||||
- 43.132.0.0/22
|
||||
- 43.132.12.0/22
|
||||
- 43.132.128.0/17
|
||||
- 43.132.16.0/22
|
||||
- 43.132.28.0/22
|
||||
- 43.132.32.0/22
|
||||
- 43.132.40.0/22
|
||||
- 43.132.52.0/22
|
||||
- 43.132.60.0/24
|
||||
- 43.132.64.0/22
|
||||
- 43.132.69.0/24
|
||||
- 43.132.70.0/23
|
||||
- 43.132.72.0/21
|
||||
- 43.132.80.0/21
|
||||
- 43.132.88.0/22
|
||||
- 43.132.92.0/23
|
||||
- 43.132.96.0/19
|
||||
- 43.133.0.0/16
|
||||
- 43.134.0.0/16
|
||||
- 43.135.0.0/17
|
||||
- 43.135.128.0/18
|
||||
- 43.135.192.0/19
|
||||
- 43.152.0.0/21
|
||||
- 43.152.11.0/24
|
||||
- 43.152.12.0/22
|
||||
- 43.152.128.0/22
|
||||
- 43.152.133.0/24
|
||||
- 43.152.134.0/23
|
||||
- 43.152.136.0/21
|
||||
- 43.152.144.0/20
|
||||
- 43.152.160.0/22
|
||||
- 43.152.16.0/21
|
||||
- 43.152.164.0/23
|
||||
- 43.152.166.0/24
|
||||
- 43.152.168.0/21
|
||||
- 43.152.178.0/23
|
||||
- 43.152.180.0/22
|
||||
- 43.152.184.0/21
|
||||
- 43.152.192.0/18
|
||||
- 43.152.24.0/22
|
||||
- 43.152.31.0/24
|
||||
- 43.152.32.0/23
|
||||
- 43.152.35.0/24
|
||||
- 43.152.36.0/22
|
||||
- 43.152.40.0/21
|
||||
- 43.152.48.0/20
|
||||
- 43.152.74.0/23
|
||||
- 43.152.76.0/22
|
||||
- 43.152.80.0/22
|
||||
- 43.152.8.0/23
|
||||
- 43.152.92.0/23
|
||||
- 43.153.0.0/16
|
||||
- 43.154.0.0/15
|
||||
- 43.156.0.0/15
|
||||
- 43.158.0.0/16
|
||||
- 43.159.0.0/20
|
||||
- 43.159.128.0/17
|
||||
- 43.159.64.0/23
|
||||
- 43.159.70.0/23
|
||||
- 43.159.72.0/21
|
||||
- 43.159.81.0/24
|
||||
- 43.159.82.0/23
|
||||
- 43.159.85.0/24
|
||||
- 43.159.86.0/23
|
||||
- 43.159.88.0/21
|
||||
- 43.159.96.0/19
|
||||
- 43.160.0.0/15
|
||||
- 43.162.0.0/16
|
||||
- 43.163.0.0/17
|
||||
- 43.163.128.0/18
|
||||
- 43.163.192.255/32
|
||||
- 43.163.193.0/24
|
||||
- 43.163.194.0/23
|
||||
- 43.163.196.0/22
|
||||
- 43.163.200.0/21
|
||||
- 43.163.208.0/20
|
||||
- 43.163.224.0/19
|
||||
- 43.164.0.0/18
|
||||
- 43.164.128.0/17
|
||||
- 43.165.0.0/16
|
||||
- 43.166.128.0/18
|
||||
- 43.166.224.0/19
|
||||
- 43.168.0.0/20
|
||||
- 43.168.16.0/21
|
||||
- 43.168.24.0/22
|
||||
- 43.168.255.0/24
|
||||
- 43.168.32.0/19
|
||||
- 43.168.64.0/20
|
||||
- 43.168.80.0/22
|
||||
- 43.169.0.0/16
|
||||
- 43.170.0.0/16
|
||||
- 43.174.0.0/18
|
||||
- 43.174.128.0/17
|
||||
- 43.174.64.0/22
|
||||
- 43.174.68.0/23
|
||||
- 43.174.71.0/24
|
||||
- 43.174.74.0/23
|
||||
- 43.174.76.0/22
|
||||
- 43.174.80.0/20
|
||||
- 43.174.96.0/19
|
||||
- 43.175.0.0/20
|
||||
- 43.175.113.0/24
|
||||
- 43.175.114.0/23
|
||||
- 43.175.116.0/22
|
||||
- 43.175.120.0/21
|
||||
- 43.175.128.0/18
|
||||
- 43.175.16.0/22
|
||||
- 43.175.192.0/20
|
||||
- 43.175.20.0/23
|
||||
- 43.175.208.0/21
|
||||
- 43.175.216.0/22
|
||||
- 43.175.220.0/23
|
||||
- 43.175.22.0/24
|
||||
- 43.175.222.0/24
|
||||
- 43.175.224.0/20
|
||||
- 43.175.25.0/24
|
||||
- 43.175.26.0/23
|
||||
- 43.175.28.0/22
|
||||
- 43.175.32.0/19
|
||||
- 43.175.64.0/19
|
||||
- 43.175.96.0/20
|
||||
6
data/crawlers/yandexbot.yaml
Normal file
6
data/crawlers/yandexbot.yaml
Normal file
@@ -0,0 +1,6 @@
|
||||
- name: yandexbot
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- userAgent.matches("\\+http\\://yandex\\.com/bots")
|
||||
- verifyFCrDNS(remoteAddress, "^.*\\.yandex\\.(ru|com|net)$")
|
||||
@@ -3,6 +3,6 @@ package data
|
||||
import "embed"
|
||||
|
||||
var (
|
||||
//go:embed botPolicies.yaml botPolicies.json all:apps all:bots all:clients all:common all:crawlers all:meta
|
||||
//go:embed botPolicies.yaml all:apps all:bots all:clients all:common all:crawlers all:meta all:services
|
||||
BotPolicies embed.FS
|
||||
)
|
||||
|
||||
38
data/embed_test.go
Normal file
38
data/embed_test.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestBotPoliciesEmbed ensures all YAML files in the directory tree
|
||||
// are accessible in the embedded BotPolicies filesystem.
|
||||
func TestBotPoliciesEmbed(t *testing.T) {
|
||||
yamlFiles, err := filepath.Glob("./**/*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to glob YAML files: %v", err)
|
||||
}
|
||||
|
||||
if len(yamlFiles) == 0 {
|
||||
t.Fatal("No YAML files found in directory tree")
|
||||
}
|
||||
|
||||
t.Logf("Found %d YAML files to verify", len(yamlFiles))
|
||||
|
||||
for _, filePath := range yamlFiles {
|
||||
embeddedPath := strings.TrimPrefix(filePath, "./")
|
||||
|
||||
t.Run(embeddedPath, func(t *testing.T) {
|
||||
content, err := BotPolicies.ReadFile(embeddedPath)
|
||||
if err != nil {
|
||||
t.Errorf("Failed to read %s from embedded filesystem: %v", embeddedPath, err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(content) == 0 {
|
||||
t.Errorf("File %s exists in embedded filesystem but is empty", embeddedPath)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
88
data/meta/default-config.yaml
Normal file
88
data/meta/default-config.yaml
Normal file
@@ -0,0 +1,88 @@
|
||||
- # Pathological bots to deny
|
||||
# This correlates to data/bots/_deny-pathological.yaml in the source tree
|
||||
# https://github.com/TecharoHQ/anubis/blob/main/data/bots/_deny-pathological.yaml
|
||||
import: (data)/bots/_deny-pathological.yaml
|
||||
- import: (data)/bots/aggressive-brazilian-scrapers.yaml
|
||||
|
||||
# Aggressively block AI/LLM related bots/agents by default
|
||||
- import: (data)/meta/ai-block-aggressive.yaml
|
||||
|
||||
# Consider replacing the aggressive AI policy with more selective policies:
|
||||
# - import: (data)/meta/ai-block-moderate.yaml
|
||||
# - import: (data)/meta/ai-block-permissive.yaml
|
||||
|
||||
# Search engine crawlers to allow, defaults to:
|
||||
# - Google (so they don't try to bypass Anubis)
|
||||
# - Apple
|
||||
# - Bing
|
||||
# - DuckDuckGo
|
||||
# - Qwant
|
||||
# - The Internet Archive
|
||||
# - Kagi
|
||||
# - Marginalia
|
||||
# - Mojeek
|
||||
- import: (data)/crawlers/_allow-good.yaml
|
||||
# Challenge Firefox AI previews
|
||||
- import: (data)/clients/x-firefox-ai.yaml
|
||||
|
||||
# Allow common "keeping the internet working" routes (well-known, favicon, robots.txt)
|
||||
- import: (data)/common/keep-internet-working.yaml
|
||||
|
||||
# # Punish any bot with "bot" in the user-agent string
|
||||
# # This is known to have a high false-positive rate, use at your own risk
|
||||
# - name: generic-bot-catchall
|
||||
# user_agent_regex: (?i:bot|crawler)
|
||||
# action: CHALLENGE
|
||||
# challenge:
|
||||
# difficulty: 16 # impossible
|
||||
# algorithm: slow # intentionally waste CPU cycles and time
|
||||
|
||||
# Requires a subscription to Thoth to use, see
|
||||
# https://anubis.techaro.lol/docs/admin/thoth#geoip-based-filtering
|
||||
- name: countries-with-aggressive-scrapers
|
||||
action: WEIGH
|
||||
geoip:
|
||||
countries:
|
||||
- BR
|
||||
- CN
|
||||
weight:
|
||||
adjust: 10
|
||||
|
||||
# Requires a subscription to Thoth to use, see
|
||||
# https://anubis.techaro.lol/docs/admin/thoth#asn-based-filtering
|
||||
- name: aggressive-asns-without-functional-abuse-contact
|
||||
action: WEIGH
|
||||
asns:
|
||||
match:
|
||||
- 13335 # Cloudflare
|
||||
- 136907 # Huawei Cloud
|
||||
- 45102 # Alibaba Cloud
|
||||
weight:
|
||||
adjust: 10
|
||||
|
||||
# ## System load based checks.
|
||||
# # If the system is under high load, add weight.
|
||||
# - name: high-load-average
|
||||
# action: WEIGH
|
||||
# expression: load_1m >= 10.0 # make sure to end the load comparison in a .0
|
||||
# weight:
|
||||
# adjust: 20
|
||||
|
||||
## If your backend service is running on the same operating system as Anubis,
|
||||
## you can uncomment this rule to make the challenge easier when the system is
|
||||
## under low load.
|
||||
##
|
||||
## If it is not, remove weight.
|
||||
# - name: low-load-average
|
||||
# action: WEIGH
|
||||
# expression: load_15m <= 4.0 # make sure to end the load comparison in a .0
|
||||
# weight:
|
||||
# adjust: -10
|
||||
|
||||
# Generic catchall rule
|
||||
- name: generic-browser
|
||||
user_agent_regex: >-
|
||||
Mozilla|Opera
|
||||
action: WEIGH
|
||||
weight:
|
||||
adjust: 10
|
||||
2
data/meta/messengers-preview.yaml
Normal file
2
data/meta/messengers-preview.yaml
Normal file
@@ -0,0 +1,2 @@
|
||||
- import: (data)/clients/telegram-preview.yaml
|
||||
- import: (data)/clients/vk-preview.yaml
|
||||
@@ -13,7 +13,13 @@ func Zilch[T any]() T {
|
||||
// Impl is a lazy key->value map. It's a wrapper around a map and a mutex. If values exceed their time-to-live, they are pruned at Get time.
|
||||
type Impl[K comparable, V any] struct {
|
||||
data map[K]decayMapEntry[V]
|
||||
lock sync.RWMutex
|
||||
|
||||
// deleteCh receives decay-deletion requests from readers.
|
||||
deleteCh chan deleteReq[K]
|
||||
// stopCh stops the background cleanup worker.
|
||||
stopCh chan struct{}
|
||||
wg sync.WaitGroup
|
||||
lock sync.RWMutex
|
||||
}
|
||||
|
||||
type decayMapEntry[V any] struct {
|
||||
@@ -21,30 +27,38 @@ type decayMapEntry[V any] struct {
|
||||
expiry time.Time
|
||||
}
|
||||
|
||||
// deleteReq is a request to remove a key if its expiry timestamp still matches
|
||||
// the observed one. This prevents racing with concurrent Set updates.
|
||||
type deleteReq[K comparable] struct {
|
||||
key K
|
||||
expiry time.Time
|
||||
}
|
||||
|
||||
// New creates a new DecayMap of key type K and value type V.
|
||||
//
|
||||
// Key types must be comparable to work with maps.
|
||||
func New[K comparable, V any]() *Impl[K, V] {
|
||||
return &Impl[K, V]{
|
||||
data: make(map[K]decayMapEntry[V]),
|
||||
m := &Impl[K, V]{
|
||||
data: make(map[K]decayMapEntry[V]),
|
||||
deleteCh: make(chan deleteReq[K], 1024),
|
||||
stopCh: make(chan struct{}),
|
||||
}
|
||||
m.wg.Add(1)
|
||||
go m.cleanupWorker()
|
||||
return m
|
||||
}
|
||||
|
||||
// expire forcibly expires a key by setting its time-to-live one second in the past.
|
||||
func (m *Impl[K, V]) expire(key K) bool {
|
||||
m.lock.RLock()
|
||||
// Use a single write lock to avoid RUnlock->Lock convoy.
|
||||
m.lock.Lock()
|
||||
defer m.lock.Unlock()
|
||||
val, ok := m.data[key]
|
||||
m.lock.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
m.lock.Lock()
|
||||
val.expiry = time.Now().Add(-1 * time.Second)
|
||||
m.data[key] = val
|
||||
m.lock.Unlock()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -53,19 +67,14 @@ func (m *Impl[K, V]) expire(key K) bool {
|
||||
// If the value does not exist, return false. Return true after
|
||||
// deletion.
|
||||
func (m *Impl[K, V]) Delete(key K) bool {
|
||||
m.lock.RLock()
|
||||
_, ok := m.data[key]
|
||||
m.lock.RUnlock()
|
||||
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
|
||||
// Use a single write lock to avoid RUnlock->Lock convoy.
|
||||
m.lock.Lock()
|
||||
delete(m.data, key)
|
||||
m.lock.Unlock()
|
||||
|
||||
return true
|
||||
defer m.lock.Unlock()
|
||||
_, ok := m.data[key]
|
||||
if ok {
|
||||
delete(m.data, key)
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// Get gets a value from the DecayMap by key.
|
||||
@@ -81,13 +90,12 @@ func (m *Impl[K, V]) Get(key K) (V, bool) {
|
||||
}
|
||||
|
||||
if time.Now().After(value.expiry) {
|
||||
m.lock.Lock()
|
||||
// Since previously reading m.data[key], the value may have been updated.
|
||||
// Delete the entry only if the expiry time is still the same.
|
||||
if m.data[key].expiry.Equal(value.expiry) {
|
||||
delete(m.data, key)
|
||||
// Defer decay deletion to the background worker to avoid convoy.
|
||||
select {
|
||||
case m.deleteCh <- deleteReq[K]{key: key, expiry: value.expiry}:
|
||||
default:
|
||||
// Channel full: drop request; a future Cleanup() or Get will retry.
|
||||
}
|
||||
m.lock.Unlock()
|
||||
|
||||
return Zilch[V](), false
|
||||
}
|
||||
@@ -125,3 +133,64 @@ func (m *Impl[K, V]) Len() int {
|
||||
defer m.lock.RUnlock()
|
||||
return len(m.data)
|
||||
}
|
||||
|
||||
// Close stops the background cleanup worker. It's optional to call; maps live
|
||||
// for the process lifetime in many cases. Call in tests or when you know you no
|
||||
// longer need the map to avoid goroutine leaks.
|
||||
func (m *Impl[K, V]) Close() {
|
||||
close(m.stopCh)
|
||||
m.wg.Wait()
|
||||
}
|
||||
|
||||
// cleanupWorker batches decay deletions to minimize lock contention.
|
||||
func (m *Impl[K, V]) cleanupWorker() {
|
||||
defer m.wg.Done()
|
||||
batch := make([]deleteReq[K], 0, 64)
|
||||
ticker := time.NewTicker(10 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
flush := func() {
|
||||
if len(batch) == 0 {
|
||||
return
|
||||
}
|
||||
m.applyDeletes(batch)
|
||||
// reset batch without reallocating
|
||||
batch = batch[:0]
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case req := <-m.deleteCh:
|
||||
batch = append(batch, req)
|
||||
case <-ticker.C:
|
||||
flush()
|
||||
case <-m.stopCh:
|
||||
// Drain any remaining requests then exit
|
||||
for {
|
||||
select {
|
||||
case req := <-m.deleteCh:
|
||||
batch = append(batch, req)
|
||||
default:
|
||||
flush()
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *Impl[K, V]) applyDeletes(batch []deleteReq[K]) {
|
||||
now := time.Now()
|
||||
m.lock.Lock()
|
||||
for _, req := range batch {
|
||||
entry, ok := m.data[req.key]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
// Only delete if the expiry is unchanged and already past.
|
||||
if entry.expiry.Equal(req.expiry) && now.After(entry.expiry) {
|
||||
delete(m.data, req.key)
|
||||
}
|
||||
}
|
||||
m.lock.Unlock()
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
|
||||
func TestImpl(t *testing.T) {
|
||||
dm := New[string, string]()
|
||||
t.Cleanup(dm.Close)
|
||||
|
||||
dm.Set("test", "hi", 5*time.Minute)
|
||||
|
||||
@@ -28,10 +29,24 @@ func TestImpl(t *testing.T) {
|
||||
if ok {
|
||||
t.Error("got value even though it was supposed to be expired")
|
||||
}
|
||||
|
||||
// Deletion of expired entries after Get is deferred to a background worker.
|
||||
// Assert it eventually disappears from the map.
|
||||
deadline := time.Now().Add(200 * time.Millisecond)
|
||||
for time.Now().Before(deadline) {
|
||||
if dm.Len() == 0 {
|
||||
break
|
||||
}
|
||||
time.Sleep(5 * time.Millisecond)
|
||||
}
|
||||
if dm.Len() != 0 {
|
||||
t.Fatalf("expected background cleanup to remove expired key; len=%d", dm.Len())
|
||||
}
|
||||
}
|
||||
|
||||
func TestCleanup(t *testing.T) {
|
||||
dm := New[string, string]()
|
||||
t.Cleanup(dm.Close)
|
||||
|
||||
dm.Set("test1", "hi1", 1*time.Second)
|
||||
dm.Set("test2", "hi2", 2*time.Second)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
FROM docker.io/library/node AS build
|
||||
FROM docker.io/library/node:lts AS build
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
@@ -161,7 +161,7 @@ One of the first issues in Anubis before it was moved to the [TecharoHQ org](htt
|
||||
|
||||
When Anubis decides it needs to send a challenge to your browser, it sends a challenge page. Historically, this challenge page is [an HTML template](https://github.com/TecharoHQ/anubis/blob/main/web/index.templ) that kicks off some JavaScript, reads the challenge information out of the page body, and then solves it as fast as possible in order to let users see the website they want to visit.
|
||||
|
||||
In v1.20.0, Anubis has a challenge registry to hold [different client challenge implementations](/docs/category/challenges). This allows us to implement anything we want as long as it can render a page to show a challenge and then check if the result is correct. This is going to be used to implement a WebAssembly-based proof of work option (one that will be way more efficient than the existing browser JS version), but as a proof of concept I implemented a simple challenge using [HTML `<meta refresh>`](https://en.wikipedia.org/wiki/Meta_refresh).
|
||||
In v1.20.0, Anubis has a challenge registry to hold [different client challenge implementations](/docs/admin/configuration/challenges/). This allows us to implement anything we want as long as it can render a page to show a challenge and then check if the result is correct. This is going to be used to implement a WebAssembly-based proof of work option (one that will be way more efficient than the existing browser JS version), but as a proof of concept I implemented a simple challenge using [HTML `<meta refresh>`](https://en.wikipedia.org/wiki/Meta_refresh).
|
||||
|
||||
In my testing, this has worked with every browser I have thrown it at (including CLI browsers, the browser embedded in emacs, etc.). The default configuration of Anubis does use the [meta refresh challenge](/docs/admin/configuration/challenges/metarefresh) for [clients with a very low suspicion](/docs/admin/configuration/thresholds), but by default clients will be sent an [easy proof of work challenge](/docs/admin/configuration/challenges/proof-of-work).
|
||||
|
||||
|
||||
@@ -213,7 +213,7 @@ When combined with [weight thresholds](/docs/admin/configuration/thresholds), th
|
||||
|
||||
## Challenge flow v2
|
||||
|
||||
The main goal of Anubis is to weigh the risks of incoming requests in order to protect upstream resources against abusive clients like badly written scrapers. In order to separate "good" clients (like users wanting to learn from a website's content) from "bad" clients, Anubis issues [challenges](/docs/category/challenges).
|
||||
The main goal of Anubis is to weigh the risks of incoming requests in order to protect upstream resources against abusive clients like badly written scrapers. In order to separate "good" clients (like users wanting to learn from a website's content) from "bad" clients, Anubis issues [challenges](/docs/admin/configuration/challenges/).
|
||||
|
||||
Previously the Anubis challenge flow looked like this:
|
||||
|
||||
|
||||
BIN
docs/blog/2025-08-18-funding-update/around-the-bend.webp
Normal file
BIN
docs/blog/2025-08-18-funding-update/around-the-bend.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
60
docs/blog/2025-08-18-funding-update/index.mdx
Normal file
60
docs/blog/2025-08-18-funding-update/index.mdx
Normal file
@@ -0,0 +1,60 @@
|
||||
---
|
||||
slug: 2025/funding-update
|
||||
title: Funding update
|
||||
authors: [xe]
|
||||
tags: [funding]
|
||||
image: around-the-bend.webp
|
||||
---
|
||||
|
||||

|
||||
|
||||
As we finish up work on [all of the features in the next release of Anubis](/docs/CHANGELOG#unreleased), I took a moment to add up the financials and here's an update on the recurring revenue of the project. Once I reach the [$5000 per month](https://github.com/TecharoHQ/anubis/discussions/278) mark, I can start reducing hours at my dayjob and start to make working on Anubis my full time job.
|
||||
|
||||
{/* truncate */}
|
||||
|
||||
Note that this only counts _recurring_ revenue (subscriptions to [BotStopper](/docs/admin/botstopper) and monthly repeating donations). Every one of the one-time donations I get is a gift and I am grateful for them, but I cannot make critically important financial decisions off of sporadic one-time donations.
|
||||
|
||||
:::note
|
||||
|
||||
All currency figures in this article are USD (United States Dollars) unless denoted otherwise.
|
||||
|
||||
:::
|
||||
|
||||
Here's the funding breakdown by income stream:
|
||||
|
||||
```mermaid
|
||||
pie title Funding update August 2025
|
||||
"GitHub Sponsors" : 3500
|
||||
"Patreon" : 1500
|
||||
"Liberapay" : 100
|
||||
"Remaining" : 4800
|
||||
```
|
||||
|
||||
Assuming that some of my private support contracts and other sales effort go through, this will slightly change the shapes of this (a new pie chart segment will emerge for "Manual invoices"), but I am halfway there. This is a huge bar to pass and as it stands right now this is just enough income to pay for my monthly rent (not accounting for tax).
|
||||
|
||||
As a reminder, here's the rough plan for the phases I want to hit based on the _recurring_ donation totals:
|
||||
|
||||
| Monthly donations | Details |
|
||||
| :-------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| $0-5,000 per month | Anubis is a nights and weekends project based on how much spare time and energy I have. |
|
||||
| $5,000-10,000 per month | Anubis gets 1-2 days per week of my time put into it consistently and I go part-time at my dayjob. |
|
||||
| $10,000-15,000 per month | Anubis becomes my full time job. Features that are currently exclusive to [BotStopper](/docs/admin/botstopper/) start to trickle down to the open source version of Anubis. |
|
||||
| $15,000 per month and above | I start planning hiring for Techaro. |
|
||||
|
||||
If your organization benefits from Anubis, please consider donating to the project in order to make this sustainable. The fewer financial problems I have means the more that Anubis can become better.
|
||||
|
||||
## New funding platform: Liberapay
|
||||
|
||||
After many comments about the funding options, I have set up [Liberapay](https://liberapay.com/Xe/) as an option to receive donations. Additional funding targets will be added to Liberapay as soon as I hear back from my accountant with more information. All money received via Liberapay goes directly towards supporting the project.
|
||||
|
||||
## Next goals
|
||||
|
||||
Here's my short term goals for the immediate future:
|
||||
|
||||
1. Finish [Thoth](/docs/admin/thoth/) and run a backfill to mass issue API keys.
|
||||
2. Document and publish the writeup for the multi-region Google Cloud spot instance setup that Thoth is built upon.
|
||||
3. Release v1.22.0 of Anubis with Traefik support and other important fixes.
|
||||
4. Continue growing the project into a sustainable business.
|
||||
5. Work through the [blog backlog](https://github.com/TecharoHQ/anubis/issues?q=is%3Aissue%20state%3Aopen%20label%3Ablog) to document the thoughts behind Anubis and how parts of it work.
|
||||
|
||||
Thank you for supporting Anubis! It's only going to get better from here.
|
||||
214
docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/index.jsx
Normal file
214
docs/blog/2025-08-28-cpu-core-odd/ProofOfWorkDiagram/index.jsx
Normal file
@@ -0,0 +1,214 @@
|
||||
import React, { useState, useEffect, useMemo } from 'react';
|
||||
import styles from './styles.module.css';
|
||||
|
||||
// A helper function to perform SHA-256 hashing.
|
||||
// It takes a string, encodes it, hashes it, and returns a hex string.
|
||||
async function sha256(message) {
|
||||
try {
|
||||
const msgBuffer = new TextEncoder().encode(message);
|
||||
const hashBuffer = await crypto.subtle.digest('SHA-256', msgBuffer);
|
||||
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
||||
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
||||
return hashHex;
|
||||
} catch (error) {
|
||||
console.error("Hashing failed:", error);
|
||||
return "Error hashing data";
|
||||
}
|
||||
}
|
||||
|
||||
// Generates a random hex string of a given byte length
|
||||
const generateRandomHex = (bytes = 16) => {
|
||||
const buffer = new Uint8Array(bytes);
|
||||
crypto.getRandomValues(buffer);
|
||||
return Array.from(buffer)
|
||||
.map(byte => byte.toString(16).padStart(2, '0'))
|
||||
.join('');
|
||||
};
|
||||
|
||||
|
||||
// Icon components for better visual feedback
|
||||
const CheckIcon = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconGreen} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const XCircleIcon = () => (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconRed} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// Main Application Component
|
||||
export default function App() {
|
||||
// State for the challenge, initialized with a random 16-byte hex string.
|
||||
const [challenge, setChallenge] = useState(() => generateRandomHex(16));
|
||||
// State for the nonce, which is the variable we can change
|
||||
const [nonce, setNonce] = useState(0);
|
||||
// State to store the resulting hash
|
||||
const [hash, setHash] = useState('');
|
||||
// A flag to indicate if the current hash is the "winning" one
|
||||
const [isMining, setIsMining] = useState(false);
|
||||
const [isFound, setIsFound] = useState(false);
|
||||
|
||||
// The mining difficulty, i.e., the required number of leading zeros
|
||||
const difficulty = "00";
|
||||
|
||||
// Memoize the combined data to avoid recalculating on every render
|
||||
const combinedData = useMemo(() => `${challenge}${nonce}`, [challenge, nonce]);
|
||||
|
||||
// This effect hook recalculates the hash whenever the combinedData changes.
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
const calculateHash = async () => {
|
||||
const calculatedHash = await sha256(combinedData);
|
||||
if (isMounted) {
|
||||
setHash(calculatedHash);
|
||||
setIsFound(calculatedHash.startsWith(difficulty));
|
||||
}
|
||||
};
|
||||
calculateHash();
|
||||
return () => { isMounted = false; };
|
||||
}, [combinedData, difficulty]);
|
||||
|
||||
// This effect handles the automatic mining process
|
||||
useEffect(() => {
|
||||
if (!isMining) return;
|
||||
|
||||
let miningNonce = nonce;
|
||||
let continueMining = true;
|
||||
|
||||
const mine = async () => {
|
||||
while (continueMining) {
|
||||
const currentData = `${challenge}${miningNonce}`;
|
||||
const currentHash = await sha256(currentData);
|
||||
|
||||
if (currentHash.startsWith(difficulty)) {
|
||||
setNonce(miningNonce);
|
||||
setIsMining(false);
|
||||
break;
|
||||
}
|
||||
|
||||
miningNonce++;
|
||||
// Update the UI periodically to avoid freezing the browser
|
||||
if (miningNonce % 100 === 0) {
|
||||
setNonce(miningNonce);
|
||||
await new Promise(resolve => setTimeout(resolve, 0)); // Yield to the browser
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
mine();
|
||||
|
||||
return () => {
|
||||
continueMining = false;
|
||||
}
|
||||
}, [isMining, challenge, nonce, difficulty]);
|
||||
|
||||
|
||||
const handleMineClick = () => {
|
||||
setIsMining(true);
|
||||
}
|
||||
|
||||
const handleStopClick = () => {
|
||||
setIsMining(false);
|
||||
}
|
||||
|
||||
const handleResetClick = () => {
|
||||
setIsMining(false);
|
||||
setNonce(0);
|
||||
}
|
||||
|
||||
const handleNewChallengeClick = () => {
|
||||
setIsMining(false);
|
||||
setChallenge(generateRandomHex(16));
|
||||
setNonce(0);
|
||||
}
|
||||
|
||||
// Helper to render the hash with colored leading characters
|
||||
const renderHash = () => {
|
||||
if (!hash) return <span>...</span>;
|
||||
const prefix = hash.substring(0, difficulty.length);
|
||||
const suffix = hash.substring(difficulty.length);
|
||||
const prefixColor = isFound ? styles.hashPrefixGreen : styles.hashPrefixRed;
|
||||
return (
|
||||
<>
|
||||
<span className={`${prefixColor} ${styles.hashPrefix}`}>{prefix}</span>
|
||||
<span className={styles.hashSuffix}>{suffix}</span>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<div className={styles.innerContainer}>
|
||||
<div className={styles.grid}>
|
||||
{/* Challenge Block */}
|
||||
<div className={styles.block}>
|
||||
<h2 className={styles.blockTitle}>1. Challenge</h2>
|
||||
<p className={styles.challengeText}>{challenge}</p>
|
||||
</div>
|
||||
|
||||
{/* Nonce Control Block */}
|
||||
<div className={styles.block}>
|
||||
<h2 className={styles.blockTitle}>2. Nonce</h2>
|
||||
<div className={styles.nonceControls}>
|
||||
<button onClick={() => setNonce(n => n - 1)} disabled={isMining} className={styles.nonceButton}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconSmall} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20 12H4" /></svg>
|
||||
</button>
|
||||
<span className={styles.nonceValue}>{nonce}</span>
|
||||
<button onClick={() => setNonce(n => n + 1)} disabled={isMining} className={styles.nonceButton}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconSmall} fill="none" viewBox="0 0 24 24" stroke="currentColor"><path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 4v16m8-8H4" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Combined Data Block */}
|
||||
<div className={styles.block}>
|
||||
<h2 className={styles.blockTitle}>3. Combined Data</h2>
|
||||
<p className={styles.combinedDataText}>{combinedData}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Arrow pointing down */}
|
||||
<div className={styles.arrowContainer}>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className={styles.iconGray} fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 14l-7 7m0 0l-7-7m7 7V3" />
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
{/* Hash Output Block */}
|
||||
<div className={`${styles.hashContainer} ${isFound ? styles.hashContainerSuccess : styles.hashContainerError}`}>
|
||||
<div className={styles.hashContent}>
|
||||
<div className={styles.hashText}>
|
||||
<h2 className={styles.blockTitle}>4. Resulting Hash (SHA-256)</h2>
|
||||
<p className={styles.hashValue}>{renderHash()}</p>
|
||||
</div>
|
||||
<div className={styles.hashIcon}>
|
||||
{isFound ? <CheckIcon /> : <XCircleIcon />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Mining Controls */}
|
||||
<div className={styles.buttonContainer}>
|
||||
{!isMining ? (
|
||||
<button onClick={handleMineClick} className={`${styles.button} ${styles.buttonCyan}`}>
|
||||
Auto-Mine
|
||||
</button>
|
||||
) : (
|
||||
<button onClick={handleStopClick} className={`${styles.button} ${styles.buttonYellow}`}>
|
||||
Stop Mining
|
||||
</button>
|
||||
)}
|
||||
<button onClick={handleNewChallengeClick} className={`${styles.button} ${styles.buttonIndigo}`}>
|
||||
New Challenge
|
||||
</button>
|
||||
<button onClick={handleResetClick} className={`${styles.button} ${styles.buttonGray}`}>
|
||||
Reset Nonce
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,366 @@
|
||||
/* Main container styles */
|
||||
.container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-family: ui-sans-serif, system-ui, sans-serif;
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.innerContainer {
|
||||
width: 100%;
|
||||
max-width: 56rem;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
/* Header styles */
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2.25rem;
|
||||
font-weight: 700;
|
||||
color: rgb(34 211 238);
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.125rem;
|
||||
color: rgb(156 163 175);
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Grid layout styles */
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Block styles */
|
||||
.block {
|
||||
background-color: rgb(31 41 55);
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.blockTitle {
|
||||
font-size: 1.125rem;
|
||||
font-weight: 600;
|
||||
color: rgb(34 211 238);
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.challengeText {
|
||||
font-size: 0.875rem;
|
||||
color: rgb(209 213 219);
|
||||
word-break: break-all;
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
}
|
||||
|
||||
.combinedDataText {
|
||||
font-size: 0.875rem;
|
||||
color: rgb(156 163 175);
|
||||
word-break: break-all;
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
}
|
||||
|
||||
/* Nonce control styles */
|
||||
.nonceControls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.nonceButton {
|
||||
background-color: rgb(55 65 81);
|
||||
border-radius: 9999px;
|
||||
padding: 0.5rem;
|
||||
transition: background-color 200ms;
|
||||
}
|
||||
|
||||
.nonceButton:hover:not(:disabled) {
|
||||
background-color: rgb(34 211 238);
|
||||
}
|
||||
|
||||
.nonceButton:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.nonceValue {
|
||||
font-size: 1.5rem;
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
width: 6rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Icon styles */
|
||||
.icon {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
}
|
||||
|
||||
.iconGreen {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
color: rgb(74 222 128);
|
||||
}
|
||||
|
||||
.iconRed {
|
||||
height: 2rem;
|
||||
width: 2rem;
|
||||
color: rgb(248 113 113);
|
||||
}
|
||||
|
||||
.iconSmall {
|
||||
height: 1.5rem;
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.iconGray {
|
||||
height: 2.5rem;
|
||||
width: 2.5rem;
|
||||
color: rgb(75 85 99);
|
||||
animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
/* Arrow animation */
|
||||
@keyframes pulse {
|
||||
0%,
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.arrowContainer {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 1.5rem 0;
|
||||
}
|
||||
|
||||
/* Hash output styles */
|
||||
.hashContainer {
|
||||
padding: 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
|
||||
transition: all 300ms;
|
||||
border: 2px solid;
|
||||
}
|
||||
|
||||
.hashContainerSuccess {
|
||||
background-color: rgb(20 83 45 / 0.5);
|
||||
border-color: rgb(74 222 128);
|
||||
}
|
||||
|
||||
.hashContainerError {
|
||||
background-color: rgb(127 29 29 / 0.5);
|
||||
border-color: rgb(248 113 113);
|
||||
}
|
||||
|
||||
.hashContent {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hashText {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.hashTextLg {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hashValue {
|
||||
font-size: 0.875rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.hashValueLg {
|
||||
font-size: 1rem;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.hashIcon {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.hashIconLg {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/* Hash highlighting */
|
||||
.hashPrefix {
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
}
|
||||
|
||||
.hashPrefixGreen {
|
||||
color: rgb(74 222 128);
|
||||
}
|
||||
|
||||
.hashPrefixRed {
|
||||
color: rgb(248 113 113);
|
||||
}
|
||||
|
||||
.hashSuffix {
|
||||
font-family: ui-monospace, SFMono-Regular, monospace;
|
||||
color: rgb(156 163 175);
|
||||
}
|
||||
|
||||
/* Button styles */
|
||||
.buttonContainer {
|
||||
margin-top: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.button {
|
||||
font-weight: 700;
|
||||
padding: 0.75rem 1.5rem;
|
||||
border-radius: 0.5rem;
|
||||
transition: transform 150ms;
|
||||
}
|
||||
|
||||
.button:hover {
|
||||
transform: scale(1.05);
|
||||
}
|
||||
|
||||
.buttonCyan {
|
||||
background-color: rgb(8 145 178);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.buttonCyan:hover {
|
||||
background-color: rgb(6 182 212);
|
||||
}
|
||||
|
||||
.buttonYellow {
|
||||
background-color: rgb(202 138 4);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.buttonYellow:hover {
|
||||
background-color: rgb(245 158 11);
|
||||
}
|
||||
|
||||
.buttonIndigo {
|
||||
background-color: rgb(79 70 229);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.buttonIndigo:hover {
|
||||
background-color: rgb(99 102 241);
|
||||
}
|
||||
|
||||
.buttonGray {
|
||||
background-color: rgb(55 65 81);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.buttonGray:hover {
|
||||
background-color: rgb(75 85 99);
|
||||
}
|
||||
|
||||
/* Responsive styles */
|
||||
@media (min-width: 768px) {
|
||||
.title {
|
||||
font-size: 3rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.hashContent {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
.hashText {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hashValue {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.hashIcon {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 767px) {
|
||||
.grid {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
.block {
|
||||
background-color: oklch(93% 0.034 272.788);
|
||||
}
|
||||
|
||||
.challengeText {
|
||||
color: oklch(12.9% 0.042 264.695);
|
||||
}
|
||||
|
||||
.combinedDataText {
|
||||
color: oklch(12.9% 0.042 264.695);
|
||||
}
|
||||
|
||||
.nonceButton {
|
||||
background-color: oklch(88.2% 0.059 254.128);
|
||||
}
|
||||
|
||||
.nonceValue {
|
||||
color: oklch(12.9% 0.042 264.695);
|
||||
}
|
||||
|
||||
.blockTitle {
|
||||
color: oklch(45% 0.085 224.283);
|
||||
}
|
||||
|
||||
.hashContainerSuccess {
|
||||
background-color: oklch(95% 0.052 163.051);
|
||||
border-color: rgb(74 222 128);
|
||||
}
|
||||
|
||||
.hashContainerError {
|
||||
background-color: oklch(94.1% 0.03 12.58);
|
||||
border-color: rgb(248 113 113);
|
||||
}
|
||||
|
||||
.hashPrefixGreen {
|
||||
color: oklch(53.2% 0.157 131.589);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hashPrefixRed {
|
||||
color: oklch(45.5% 0.188 13.697);
|
||||
}
|
||||
|
||||
.hashSuffix {
|
||||
color: oklch(27.9% 0.041 260.031);
|
||||
}
|
||||
}
|
||||
129
docs/blog/2025-08-28-cpu-core-odd/index.mdx
Normal file
129
docs/blog/2025-08-28-cpu-core-odd/index.mdx
Normal file
@@ -0,0 +1,129 @@
|
||||
---
|
||||
slug: 2025/cpu-core-odd
|
||||
title: Sometimes CPU cores are odd
|
||||
description: "TL;DR: all the assumptions you have about processor design are wrong and if you are unlucky you will never run into problems that users do through sheer chance."
|
||||
authors: [xe]
|
||||
tags:
|
||||
- bugfix
|
||||
- implementation
|
||||
image: parc-dsilence.webp
|
||||
---
|
||||
|
||||
import ProofOfWorkDiagram from "./ProofOfWorkDiagram";
|
||||
|
||||

|
||||
|
||||
One of the biggest lessons that I've learned in my career is that all software has bugs, and the more complicated your software gets the more complicated your bugs get. A lot of the time those bugs will be fairly obvious and easy to spot, validate, and replicate. Sometimes, the process of fixing it will uncover your core assumptions about how things work in ways that will leave you feeling like you just got trolled.
|
||||
|
||||
Today I'm going to talk about a single line fix that prevents people on a large number of devices from having weird irreproducible issues with Anubis rejecting people when it frankly shouldn't. Stick around, it's gonna be a wild ride.
|
||||
|
||||
{/* truncate */}
|
||||
|
||||
## How this happened
|
||||
|
||||
Anubis is a web application firewall that tries to make sure that the client is a browser. It uses a few [challenge methods](/docs/admin/configuration/challenges/) to do this determination, but the main method is the [proof of work](/docs/admin/configuration/challenges/proof-of-work/) challenge which makes clients grind away at cryptographic checksums in order to rate limit clients from connecting too eagerly.
|
||||
|
||||
:::note
|
||||
|
||||
In retrospect implementing the proof of work challenge may have been a mistake and it's likely to be supplanted by things like [Proof of React](https://github.com/TecharoHQ/anubis/pull/1038) or other methods that have yet to be developed. Your patience and polite behaviour in the bug tracker is appreciated.
|
||||
|
||||
:::
|
||||
|
||||
In order to make sure the proof of work challenge screen _goes away as fast as possible_, the [worker code](https://github.com/TecharoHQ/anubis/tree/main/web/js/worker) is optimized within an inch of its digital life. One of the main ways that this code is optimized is with how it's run. Over the last 10-20 years, the main way that CPUs have gotten fast is via increasing multicore performance. Anubis tries to make sure that it can use as many cores as possible in order to take advantage of your device's CPU as much as it can.
|
||||
|
||||
This strategy sometimes has some issues though, for one Firefox seems to get _much slower_ if you have Anubis try to absolutely saturate all of the cores on the system. It also has a fairly high overhead between JavaScript JIT code and [WebCrypto](https://developer.mozilla.org/en-US/docs/Web/API/Web_Crypto_API). I did some testing and found out that Firefox's point of diminishing returns was about half of the CPU cores.
|
||||
|
||||
## Another "invalid response" bug
|
||||
|
||||
One of the complaints I've been getting from users and administrators using Anubis is that they've been running into issues where users get randomly rejected with an error message only saying "invalid response". This happens when the challenge validating process fails. This issue has been blocking the release of the next version of Anubis.
|
||||
|
||||
In order to demonstrate this better, I've made a little interactive diagram for the proof of work process:
|
||||
|
||||
<ProofOfWorkDiagram />
|
||||
|
||||
I've fixed a lot of the easy bugs in Anubis by this point. A lot of what's left is the hard bugs, but also specifically the kinds of hard bugs that involve weird hardware configurations. In order to try and catch these issues before software hits prod, I test Anubis against a bunch of hardware I have locally. Any issues I find and fix before software ships are issues that you don't hit in production.
|
||||
|
||||
Let's consider [the line of code](https://github.com/TecharoHQ/anubis/blob/main/web/js/algorithms/fast.mjs) that was causing this issue:
|
||||
|
||||
```js
|
||||
threads = Math.max(navigator.hardwareConcurrency / 2, 1),
|
||||
```
|
||||
|
||||
This is intended to make your browser spawn a proof of work worker for _half_ of your available CPU cores. If you only have one CPU core, you should only have one worker. Each thread is given this number of threads and uses that to increment the nonce so that each thread doesn't try to find a solution that another worker has already performed.
|
||||
|
||||
One of the subtle problems here is that all of the parts of this assume that the thread ID and nonce are integers without a decimal portion. Famously, [all JavaScript numbers are IEEE 754 floating point numbers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number). Surely there wouldn't be a case where the thread count could be a _decimal_ number, right?
|
||||
|
||||
Here's all the devices I use to test Anubis _and their core counts_:
|
||||
|
||||
| Device Name | Core Count |
|
||||
| :--------------------------- | :--------- |
|
||||
| MacBook Pro M3 Max | 16 |
|
||||
| MacBook Pro M4 Max | 16 |
|
||||
| AMD Ryzen 9 7950x3D | 32 |
|
||||
| Google Pixel 9a (GrapheneOS) | 8 |
|
||||
| iPhone 15 Pro Max | 6 |
|
||||
| iPad Pro (M1) | 8 |
|
||||
| iPad mini | 6 |
|
||||
| Steam Deck | 8 |
|
||||
| Core i5 10600 (homelab) | 12 |
|
||||
| ROG Ally | 16 |
|
||||
|
||||
Notice something? All of those devices have an _even_ number of cores. Some devices such as the [Pixel 8 Pro](https://www.gsmarena.com/google_pixel_8_pro-12545.php) have an _odd_ number of cores. So what happens with that line of code as the JavaScript engine evaluates it?
|
||||
|
||||
Let's replace the [`navigator.hardwareConcurrency`](https://developer.mozilla.org/en-US/docs/Web/API/Navigator/hardwareConcurrency) with the Pixel 8 Pro's 9 cores:
|
||||
|
||||
```js
|
||||
threads = Math.max(9 / 2, 1),
|
||||
```
|
||||
|
||||
Then divide it by two:
|
||||
|
||||
```js
|
||||
threads = Math.max(4.5, 1),
|
||||
```
|
||||
|
||||
Oops, that's not ideal. However `4.5` is bigger than `1`, so [`Math.max`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/max) returns that:
|
||||
|
||||
```js
|
||||
threads = 4.5,
|
||||
```
|
||||
|
||||
This means that each time the proof of work equation is calculated, there is a 50% chance that a valid solution would include a nonce with a decimal portion in it. If the client finds a solution with such a nonce, then it would think the client was successful and submit the solution to the server, but the server only expects whole numbers back so it rejects that as an invalid response.
|
||||
|
||||
I keep telling more junior people that when you have the weirdest, most inconsistent bugs in software that it's going to boil down to the dumbest possible thing you can possibly imagine. People don't believe me, then they encounter bugs like this. Then they suddenly believe me.
|
||||
|
||||
Here is the fix:
|
||||
|
||||
```js
|
||||
threads = Math.trunc(Math.max(navigator.hardwareConcurrency / 2, 1)),
|
||||
```
|
||||
|
||||
This uses [`Math.trunc`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/trunc) to truncate away the decimal portion so that the Pixel 8 Pro has `4` workers instead of `4.5` workers.
|
||||
|
||||
## Today I learned this was possible
|
||||
|
||||
This was a total "today I learned" moment. I didn't actually think that hardware vendors shipped processors with an odd number of cores, however if you look at the core geometry of the Pixel 8 Pro, it has _three_ tiers of processor cores:
|
||||
|
||||
| Core type | Core model | Number |
|
||||
| :----------------- | :------------------- | :----- |
|
||||
| High performance | 3 Ghz Cortex X3 | 1 |
|
||||
| Medium performance | 2.45 Ghz Cortex A715 | 4 |
|
||||
| High efficiency | 2.15 Cortex A510 | 4 |
|
||||
| Total | | 9 |
|
||||
|
||||
I guess every assumption that developers have about CPU design is probably wrong.
|
||||
|
||||
This probably isn't helped by the fact that for most of my career, the core count in phones has been largely irrelevant and most of the desktop / laptop CPUs I've had (where core count does matter) uses [simultaneous multithreading](https://en.wikipedia.org/wiki/Simultaneous_multithreading) to "multiply" the core count by two.
|
||||
|
||||
The client side fix is a bit of an "emergency stop" button to try and mitigate the badness as early as possible. In general I'm quite aware of the terrible UX involved with this flow failing and I'm still noodling through ways to make that UX better and easier for users / administrators to debug.
|
||||
|
||||
I'm looking into the following:
|
||||
|
||||
1. This could have been prevented on the server side by doing less strict input validation in compliance with [Postel's Law](https://en.wikipedia.org/wiki/Robustness_principle). I feel nervous about making such a security-sensitive endpoint _more liberal_ with the inputs it can accept, but it may be fine? I need to consult with a security expert.
|
||||
2. Showing an encrypted error message on the "invalid response" page so that the user and administrator can work together to fix or report the issue. I remember Google doing this at least once, but I can't recall where I've seen it in the past. Either way, this is probably the most robust method even though it would require developing some additional tooling. I think it would be worth it.
|
||||
|
||||
I'm likely going to go with the second option. I will need to figure out a good flow for this. It's likely going to involve [age](https://github.com/FiloSottile/age). I'll say more about this when I have more to say.
|
||||
|
||||
In the meantime though, looks like I need to expense a used Pixel 8 Pro to add to the testing jungle for Anubis. If anyone has a deal out there, please let me know!
|
||||
|
||||
Thank you to the people that have been polite and helpful when trying to root cause and fix this issue.
|
||||
BIN
docs/blog/2025-08-28-cpu-core-odd/parc-dsilence.webp
Normal file
BIN
docs/blog/2025-08-28-cpu-core-odd/parc-dsilence.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 18 KiB |
BIN
docs/blog/2025-10-31-file-abuse-reports/goose-pond.webp
Normal file
BIN
docs/blog/2025-10-31-file-abuse-reports/goose-pond.webp
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
75
docs/blog/2025-10-31-file-abuse-reports/index.mdx
Normal file
75
docs/blog/2025-10-31-file-abuse-reports/index.mdx
Normal file
@@ -0,0 +1,75 @@
|
||||
---
|
||||
slug: 2025/file-abuse-reports
|
||||
title: Taking steps to end abusive traffic from cloud providers
|
||||
description: "Learn how to effectively file abuse reports with cloud providers to stop malicious traffic at its source and protect your services from automated abuse."
|
||||
authors: [xe]
|
||||
tags: [abuse, cloud, security, networking]
|
||||
image: goose-pond.webp
|
||||
---
|
||||
|
||||

|
||||
|
||||
As part of Anubis's ongoing development, I've been working to reduce friction for legitimate users by minimizing unnecessary challenge pages. While this improves the user experience, it can potentially expose services to increased abuse from public cloud infrastructure. To help administrators better protect their services, I want to share my strategies for filing abuse reports with IP space owners, enabling us to address malicious scraping at its source.
|
||||
|
||||
{/* truncate */}
|
||||
|
||||
In general, there are two kinds of IP addresses:
|
||||
|
||||
- Residential IP addresses: IP addresses that are allocated to residential customers such as home internet connections and cellular data plans. These IP addresses are increasingly shared between customers due to technologies like [CGNAT](https://en.wikipedia.org/wiki/Carrier-grade_NAT).
|
||||
- Commercial IP addresses: IP addresses that are allocated to commercial customers such as cloud providers, VPS providers, root server providers, and other such business to business companies. These IP addresses are almost always statically allocated to one customer for a very long period of time (typically the lifetime of the server unless they are using things like dedicated IP addresses).
|
||||
|
||||
In general, filing abuse reports to residential IP addresses is a waste of time. The administrators do appreciate knowing what kinds of abusive traffic is causing grief, but many times the users of those IP addresses don't know that their computer is sending abusive traffic to your services. A lot of malware botnets that used to be used with DDOS for hire services are now being used as residential proxies. Those "free VPN apps" are almost certainly making you pay for your usage by making your computer a zombie in a botnet. At some level I really respect the hustle as they manage to sell other people's bandwidth for rates as ludicrous as $1.00 per gigabyte ingressed and egressed.
|
||||
|
||||
:::note
|
||||
|
||||
Keep in mind, I'm talking about the things you can find by searching "free VPN", not infrastructure for the public good like the Tor browser or I2P.
|
||||
|
||||
:::
|
||||
|
||||
What you should really focus on is traffic from commercial IP addresses, such as cloud providers. That's a case where the cloud customer is in direct violation of the acceptable use policy of the provider. Filing abuse reports gets the abuse team of the cloud provider to reach out to that customer and demand corrective action under threat of contractual violence.
|
||||
|
||||
## How to make an abuse report
|
||||
|
||||
In general, the best abuse reports contain the following information:
|
||||
|
||||
- Time of abusive requests.
|
||||
- IP address, User-Agent header, or other unique identifiers that can help the abuse team educate the customer about their misbehaving infrastructure.
|
||||
- Does the abusive IP address request robots.txt? If not, be sure to include that information.
|
||||
- A brief description of the impact to your system such as high system load, pages not rendering, or database system crashes. This helps the provider establish the fact that their customer is causing you measurable harm.
|
||||
- Context as to what your service is, what it does, and why they should care.
|
||||
|
||||
For example, let's say that someone was giving the Anubis docs a series of requests that caused the server to fall over and experience extended downtime. Here's what I would write to the abuse contact:
|
||||
|
||||
> Hello,
|
||||
>
|
||||
> I have received abusive traffic from one of your customers that has resulted in a denial of service to the users of the Anubis documentation website. Anubis is a web application firewall that administrators use to protect their websites against mass scraping and this documentation website helps administrators get started.
|
||||
>
|
||||
> On or about Thursday, October 30th at 04:00 UTC, A flurry of requests from the IP range `127.34.0.0/24` started to hit the `/admin/` routes, which caused unreasonable database load and ended up crashing PostgreSQL. This caused the documentation website to go down for three hours as it happened while the administrators were asleep. Based on logs, this caused 353 distinct users to not be able to load the documentation and the users filed bugs about it.
|
||||
>
|
||||
> I have attached the HTTP frontend logs for the abusive requests from your IP range. To protect our systems in the meantime while we perform additional hardening, I have blocked that IP address range in both our IP firewall and web application firewall configuration. Based on these logs, your customer seems to not have requested the standard `robots.txt` file, which includes instructions to deny access to those routes.
|
||||
>
|
||||
> Please let me know what other information you need on your end.
|
||||
>
|
||||
> Sincerely,
|
||||
>
|
||||
> [normal email signature]
|
||||
|
||||
Then in order to figure out where to send it, look the IP addresses up in the `whois` database. For example, if you want to find the abuse contact for the IP address `1.1.1.1`, use the [whois command](https://packages.debian.org/sid/whois) to find the abuse contact:
|
||||
|
||||
```
|
||||
$ whois 1.1.1.1 | grep -i abuse
|
||||
% Abuse contact for '1.1.1.0 - 1.1.1.255' is 'helpdesk@apnic.net'
|
||||
abuse-c: AA1412-AP
|
||||
remarks: All Cloudflare abuse reporting can be done via
|
||||
remarks: resolver-abuse@cloudflare.com
|
||||
abuse-mailbox: helpdesk@apnic.net
|
||||
role: ABUSE APNICRANDNETAU
|
||||
abuse-mailbox: helpdesk@apnic.net
|
||||
mnt-by: APNIC-ABUSE
|
||||
```
|
||||
|
||||
The abuse contact will be named either `abuse-c` or `abuse-mailbox`. For greatest effect, I suggest including all listed email addresses in your email to the abuse contact.
|
||||
|
||||
Once you send your email, you should expect a response within 2 business days at most. If they don't get back to you, please feel free to [contact me](https://xeiaso.net/contact/) so that the default set of Anubis rules can be edited according to patterns I'm seeing across the ecosystem.
|
||||
|
||||
Just remember that many cloud providers do not know how bad the scraping problem is. Filing abuse complaints makes it their problem. They don't want it to be their problem.
|
||||
@@ -11,17 +11,295 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
- Add iplist2rule tool that lets admins turn an IP address blocklist into an Anubis ruleset.
|
||||
- Add Polish locale ([#1292](https://github.com/TecharoHQ/anubis/pull/1309))
|
||||
|
||||
<!-- This changes the project to: -->
|
||||
|
||||
## v1.24.0: Y'shtola Rhul
|
||||
|
||||
Anubis is back and better than ever! Lots of minor fixes with some big ones interspersed.
|
||||
|
||||
- Fix panic when validating challenges after privacy-mode browsers strip headers and the follow-up request matches an `ALLOW` threshold.
|
||||
- Expose WEIGHT rule matches as Prometheus metrics.
|
||||
- Allow more OCI registry clients [based on feedback](https://github.com/TecharoHQ/anubis/pull/1253#issuecomment-3506744184).
|
||||
- Expose services directory in the embedded `(data)` filesystem.
|
||||
- Add Ukrainian locale ([#1044](https://github.com/TecharoHQ/anubis/pull/1044)).
|
||||
- Allow Renovate as an OCI registry client.
|
||||
- Properly handle 4in6 addresses so that IP matching works with those addresses.
|
||||
- Add support to simple Valkey/Redis cluster mode
|
||||
- Open Graph passthrough now reuses the configured target Host/SNI/TLS settings, so metadata fetches succeed when the upstream certificate differs from the public domain. ([1283](https://github.com/TecharoHQ/anubis/pull/1283))
|
||||
- Stabilize the CVE-2025-24369 regression test by always submitting an invalid proof instead of relying on random POW failures.
|
||||
- Refine the check that ensures the presence of the Accept header to avoid breaking docker clients.
|
||||
- Removed rules intended to reward actual browsers due to abuse in the wild.
|
||||
|
||||
### Dataset poisoning
|
||||
|
||||
Anubis has the ability to engage in [dataset poisoning attacks](https://www.anthropic.com/research/small-samples-poison) using the [dataset poisoning subsystem](./admin/honeypot/overview.mdx). This allows every Anubis instance to be a honeypot to attract and flag abusive scrapers so that no administrator action is required to ban them.
|
||||
|
||||
There is much more information about this feature in [the dataset poisoning subsystem documentation](./admin/honeypot/overview.mdx). Administrators that are interested in learning how this feature works should consult that documentation.
|
||||
|
||||
### Deprecate `report_as` in challenge configuration
|
||||
|
||||
Previously Anubis let you lie to users about the difficulty of a challenge to interfere with operators of malicious scrapers as a psychological attack:
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
# Punish any bot with "bot" in the user-agent string
|
||||
# This is known to have a high false-positive rate, use at your own risk
|
||||
- name: generic-bot-catchall
|
||||
user_agent_regex: (?i:bot|crawler)
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
difficulty: 16 # impossible
|
||||
report_as: 4 # lie to the operator
|
||||
algorithm: slow # intentionally waste CPU cycles and time
|
||||
```
|
||||
|
||||
This has turned out to be a bad idea because it has caused massive user experience problems and has been removed. If you are using this setting, you will get a warning in your logs like this:
|
||||
|
||||
```json
|
||||
{
|
||||
"time": "2025-11-25T23:10:31.092201549-05:00",
|
||||
"level": "WARN",
|
||||
"source": {
|
||||
"function": "github.com/TecharoHQ/anubis/lib/policy.ParseConfig",
|
||||
"file": "/home/xe/code/TecharoHQ/anubis/lib/policy/policy.go",
|
||||
"line": 201
|
||||
},
|
||||
"msg": "use of deprecated report_as setting detected, please remove this from your policy file when possible",
|
||||
"at": "config-validate",
|
||||
"name": "mild-suspicion"
|
||||
}
|
||||
```
|
||||
|
||||
To remove this warning, remove this setting from your policy file.
|
||||
|
||||
### Logging customization
|
||||
|
||||
Anubis now supports the ability to log to multiple backends ("sinks"). This allows you to have Anubis [log to a file](./admin/policies.mdx#file-sink) instead of just logging to standard out. You can also customize the [logging level](./admin/policies.mdx#log-levels) in the policy file:
|
||||
|
||||
```yaml
|
||||
logging:
|
||||
level: "warn" # much less verbose logging
|
||||
sink: file # log to a file
|
||||
parameters:
|
||||
file: "./var/anubis.log"
|
||||
maxBackups: 3 # keep at least 3 old copies
|
||||
maxBytes: 67108864 # each file can have up to 64 Mi of logs
|
||||
maxAge: 7 # rotate files out every n days
|
||||
oldFileTimeFormat: 2006-01-02T15-04-05 # RFC 3339-ish
|
||||
compress: true # gzip-compress old log files
|
||||
useLocalTime: false # timezone for rotated files is UTC
|
||||
```
|
||||
|
||||
Additionally, information about [how Anubis uses each logging level](./admin/policies.mdx#log-levels) has been added to the documentation.
|
||||
|
||||
### DNS Features
|
||||
|
||||
- CEL expressions for:
|
||||
- FCrDNS checks
|
||||
- Forward DNS queries
|
||||
- Reverse DNS queries
|
||||
- `arpaReverseIP` to transform IPv4/6 addresses into ARPA reverse IP notation.
|
||||
- `regexSafe` to escape regex special characters (useful for including `remoteAddress` or headers in regular expressions).
|
||||
- DNS cache and other optimizations to minimize unnecessary DNS queries.
|
||||
|
||||
The DNS cache TTL can be changed in the bots config like this:
|
||||
|
||||
```yaml
|
||||
dns_ttl:
|
||||
forward: 600
|
||||
reverse: 600
|
||||
```
|
||||
|
||||
The default value for both forward and reverse queries is 300 seconds.
|
||||
|
||||
The `verifyFCrDNS` CEL function has two overloads:
|
||||
|
||||
- `(addr)`
|
||||
Simply verifies that the remote side has PTR records pointing to the target address.
|
||||
- `(addr, ptrPattern)`
|
||||
Verifies that the remote side refers to a specific domain and that this domain points to the target IP.
|
||||
|
||||
## v1.23.1: Lyse Hext - Echo 1
|
||||
|
||||
- Fix `SERVE_ROBOTS_TXT` setting after the double slash fix broke it.
|
||||
|
||||
### Potentially breaking changes
|
||||
|
||||
#### Remove default Tencent Cloud block rule
|
||||
|
||||
v1.23.0 added a default rule to block Tencent Cloud. After an email from their abuse team where they promised to take action to clean up their reputation, I have removed the default block rule. If this network causes you problems, please contact [abuse@tencent.com](mailto:abuse@tencent.com) and supply the following information:
|
||||
|
||||
- Time of abusive requests.
|
||||
- IP address, User-Agent header, or other unique identifiers that can help the abuse team educate the customer about their misbehaving infrastructure.
|
||||
- Does the abusive IP address request robots.txt? If not, be sure to include that information.
|
||||
- A brief description of the impact to your system such as high system load, pages not rendering, or database system crashes. This helps the provider establish the fact that their customer is causing you measurable harm.
|
||||
- Context as to what your service is, what it does, and why they should care.
|
||||
|
||||
Mention that you are using Anubis or BotStopper to protect your services. If they do not respond to you, please [contact me](https://xeiaso.net/contact) as soon as possible.
|
||||
|
||||
#### Docker / OCI registry clients
|
||||
|
||||
Anubis v1.23.0 accidentally blocked Docker / OCI registry clients. In order to explicitly allow them, add an import for `(data)/clients/docker-client.yaml`:
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
- import: (data)/meta/default-config.yaml
|
||||
- import: (data)/clients/docker-client.yaml
|
||||
```
|
||||
|
||||
This is technically a regression as these clients used to work in Anubis v1.22.0, however it is allowable to make this opt-in as most websites do not expect to be serving Docker / OCI registry client traffic.
|
||||
|
||||
## v1.23.0: Lyse Hext
|
||||
|
||||
- Add default tencent cloud DENY rule.
|
||||
- Added `(data)/meta/default-config.yaml` for importing the entire default configuration at once.
|
||||
- Add `-custom-real-ip-header` flag to get the original request IP from a different header than `x-real-ip`.
|
||||
- Add `contentLength` variable to bot expressions.
|
||||
- Add `COOKIE_SAME_SITE_MODE` to force anubis cookies SameSite value, and downgrade automatically from `None` to `Lax` if cookie is insecure.
|
||||
- Fix lock convoy problem in decaymap ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)).
|
||||
- Fix lock convoy problem in bbolt by implementing the actor pattern ([#1103](https://github.com/TecharoHQ/anubis/issues/1103)).
|
||||
- Remove bbolt actorify implementation due to causing production issues.
|
||||
- Document missing environment variables in installation guide: `SLOG_LEVEL`, `COOKIE_PREFIX`, `FORCED_LANGUAGE`, and `TARGET_DISABLE_KEEPALIVE` ([#1086](https://github.com/TecharoHQ/anubis/pull/1086)).
|
||||
- Add validation warning when persistent storage is used without setting signing keys.
|
||||
- Fixed `robots2policy` to properly group consecutive user agents into `any:` instead of only processing the last one ([#925](https://github.com/TecharoHQ/anubis/pull/925)).
|
||||
- Make the `fast` algorithm prefer purejs when running in an insecure context.
|
||||
- Add the [`s3api` storage backend](./admin/policies.mdx#s3api) to allow Anubis to use S3 API compatible object storage as its storage backend.
|
||||
- Fix a "stutter" in the cookie name prefix so the auth cookie is named `techaro.lol-anubis-auth` instead of `techaro.lol-anubis-auth-auth`.
|
||||
- Make `cmd/containerbuild` support commas for separating elements of the `--docker-tags` argument as well as newlines.
|
||||
- Add the `DIFFICULTY_IN_JWT` option, which allows one to add the `difficulty` field in the JWT claims which indicates the difficulty of the token ([#1063](https://github.com/TecharoHQ/anubis/pull/1063)).
|
||||
- Ported the client-side JS to TypeScript to avoid egregious errors in the future.
|
||||
- Fixes concurrency problems with very old browsers ([#1082](https://github.com/TecharoHQ/anubis/issues/1082)).
|
||||
- Randomly use the Refresh header instead of the meta refresh tag in the metarefresh challenge.
|
||||
- Update OpenRC service to truncate the runtime directory before starting Anubis.
|
||||
- Make the git client profile more strictly match how the git client behaves.
|
||||
- Make the default configuration reward users using normal browsers.
|
||||
- Allow multiple consecutive slashes in a row in application paths ([#754](https://github.com/TecharoHQ/anubis/issues/754)).
|
||||
- Add option to set `targetSNI` to special keyword 'auto' to indicate that it should be automatically set to the request Host name ([424](https://github.com/TecharoHQ/anubis/issues/424)).
|
||||
- The Preact challenge has been removed from the default configuration. It will be deprecated in the future.
|
||||
- An open redirect when in subrequest mode has been fixed.
|
||||
|
||||
### Potentially breaking changes
|
||||
|
||||
#### Multiple checks at once has and-like semantics instead of or-like semantics
|
||||
|
||||
Anubis lets you stack multiple checks at once with blocks like this:
|
||||
|
||||
```yaml
|
||||
name: allow-prometheus
|
||||
action: ALLOW
|
||||
user_agent_regex: ^prometheus-probe$
|
||||
remote_addresses:
|
||||
- 192.168.2.0/24
|
||||
```
|
||||
|
||||
Previously, this only returned ALLOW if _any one_ of the conditions matched. This behaviour has changed to only return ALLOW if _all_ of the conditions match. I expect this to have some issues with user configs, however this fix is grave enough that it's worth the risk of breaking configs. If this bites you, please let me know so we can make an escape hatch.
|
||||
|
||||
### Better error messages
|
||||
|
||||
In order to make it easier for legitimate clients to debug issues with their browser configuration and Anubis, Anubis will emit internal error detail in base 64 so that administrators can chase down issues. Future versions of this may also include a variant that encrypts the error detail messages.
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
Sometimes the enhanced temporal assurance in [#1038](https://github.com/TecharoHQ/anubis/pull/1038) and [#1068](https://github.com/TecharoHQ/anubis/pull/1068) could backfire because Chromium and its ilk randomize the amount of time they wait in order to avoid a timing side channel attack. This has been fixed by both increasing the amount of time a client has to wait for the metarefresh and preact challenges as well as making the server side logic more permissive.
|
||||
|
||||
## v1.22.0: Yda Hext
|
||||
|
||||
> Someone has to make an effort at reconciliation if these conflicts are ever going to end.
|
||||
|
||||
In this release, we finally fix the odd number of CPU cores bug, pave the way for lighter weight challenges, make Anubis more adaptable, and more.
|
||||
|
||||
### Big ticket items
|
||||
|
||||
#### Proof of React challenge
|
||||
|
||||
A new ["proof of React"](./admin/configuration/challenges/preact.mdx) has been added. It runs a simple app in React that has several chained hooks. It is much more lightweight than the proof of work check.
|
||||
|
||||
#### Smaller features
|
||||
|
||||
- The [`segments`](./admin/configuration/expressions.mdx#segments) function was added for splitting a path into its slash-separated segments.
|
||||
- Added possibility to disable HTTP keep-alive to support backends not properly handling it.
|
||||
- When issuing a challenge, Anubis stores information about that challenge into the store. That stored information is later used to validate challenge responses. This works around nondeterminism in bot rules. ([#917](https://github.com/TecharoHQ/anubis/issues/917))
|
||||
- One of the biggest sources of lag in Firefox has been eliminated: the use of WebCrypto. Now whenever Anubis detects the client is using Firefox (or Pale Moon), it will swap over to a pure-JS implementation of SHA-256 for speed.
|
||||
- Proof of work solving has had a complete overhaul and rethink based on feedback from browser engine developers, frontend experts, and overall performance profiling.
|
||||
- Optimize the performance of the pure-JS Anubis solver.
|
||||
- Web Workers are stored as dedicated JavaScript files in `static/js/workers/*.mjs`.
|
||||
- Pave the way for non-SHA256 solver methods and eventually one that uses WebAssembly (or WebAssembly code compiled to JS for those that disable WebAssembly).
|
||||
- Legacy JavaScript code has been eliminated.
|
||||
- When parsing [Open Graph tags](./admin/configuration/open-graph.mdx), add any URLs found in the responses to a temporary "allow cache" so that social preview images work.
|
||||
- The hard dependency on WebCrypto has been removed, allowing a proof of work challenge to work over plain (unencrypted) HTTP.
|
||||
- The Anubis version number is put in the footer of every page.
|
||||
- Add a default block rule for Huawei Cloud.
|
||||
- Add a default block rule for Alibaba Cloud.
|
||||
- Added support to use Traefik forwardAuth middleware.
|
||||
- Add X-Request-URI support so that Subrequest Authentication has path support.
|
||||
- Added glob matching for `REDIRECT_DOMAINS`. You can pass `*.bugs.techaro.lol` to allow redirecting to anything ending with `.bugs.techaro.lol`. There is a limit of 4 wildcards.
|
||||
|
||||
### Fixes
|
||||
|
||||
#### Odd numbers of CPU cores are properly supported
|
||||
|
||||
Some phones have an odd number of CPU cores. This caused [interesting issues](https://anubis.techaro.lol/blog/2025/cpu-core-odd). This was fixed by [using `Math.trunc` to convert the number of CPU cores back into an integer](https://github.com/TecharoHQ/anubis/issues/1043).
|
||||
|
||||
#### Smaller fixes
|
||||
|
||||
- A standard library HTTP server log message about HTTP pipelining not working has been filtered out of Anubis' logs. There is no action that can be taken about it.
|
||||
- Added a missing link to the Caddy installation environment in the installation documentation.
|
||||
- Downstream consumers can change the default [log/slog#Logger](https://pkg.go.dev/log/slog#Logger) instance that Anubis uses by setting `opts.Logger` to your slog instance of choice ([#864](https://github.com/TecharoHQ/anubis/issues/864)).
|
||||
- The [Thoth client](https://anubis.techaro.lol/docs/admin/thoth) is now public in the repo instead of being an internal package.
|
||||
- [Custom-AsyncHttpClient](https://github.com/AsyncHttpClient/async-http-client)'s default User-Agent has an increased weight by default ([#852](https://github.com/TecharoHQ/anubis/issues/852)).
|
||||
- The [`segments`](./admin/configuration/expressions.mdx#segments) function was added for splitting a path into its slash-separated segments.
|
||||
- When issuing a challenge, Anubis stores information about that challenge into the store. That stored information is later used to validate challenge responses. This works around nondeterminism in bot rules. ([#917](https://github.com/TecharoHQ/anubis/issues/917))
|
||||
- When parsing [Open Graph tags](./admin/configuration/open-graph.mdx), add any URLs found in the responses to a temporary "allow cache" so that social preview images work.
|
||||
- Add option for replacing the default explanation text with a custom one ([#747](https://github.com/TecharoHQ/anubis/pull/747))
|
||||
- The contact email in the LibreJS header has been changed.
|
||||
- Firefox for Android support has been fixed by embedding the challenge ID into the pass-challenge route. This also fixes some inconsistent issues with other mobile browsers.
|
||||
- The default `favicon` pattern in `data/common/keep-internet-working.yaml` has been updated to permit requests for png/gif/jpg/svg files as well as ico.
|
||||
- The `--cookie-prefix` flag has been fixed so that it is fully respected.
|
||||
- The default patterns in `data/common/keep-internet-working.yaml` have been updated to appropriately escape the '.' character in the regular expression patterns.
|
||||
- Add optional restrictions for JWT based on the value of a header ([#697](https://github.com/TecharoHQ/anubis/pull/697))
|
||||
- The word "hack" has been removed from the translation strings for Anubis due to incidents involving people misunderstanding that word and sending particularly horrible things to the project lead over email.
|
||||
- Bump AI-robots.txt to version 1.39
|
||||
- Inject adversarial input to break AI coding assistants.
|
||||
- Add better logging when using Subrequest Authentication.
|
||||
|
||||
### Security-relevant changes
|
||||
|
||||
- Add a server-side check for the meta-refresh challenge that makes sure clients have waited for at least 95% of the time that they should.
|
||||
|
||||
#### Fix potential double-spend for challenges
|
||||
|
||||
Anubis operates by issuing a challenge and having the client present a solution for that challenge. Challenges are identified by a unique UUID, which is stored in the database.
|
||||
|
||||
The problem is that a challenge could potentially be used twice by a dedicated attacker making a targeted attack against Anubis. Challenge records did not have a "spent" or "used" field. In total, a dedicated attacker could solve a challenge once and reuse that solution across multiple sessions in order to mint additional tokens.
|
||||
|
||||
This was fixed by adding a "spent" field to challenges in the data store. When a challenge is solved, that "spent" field gets set to `true`. If a future attempt to solve this challenge is observed, it gets rejected.
|
||||
|
||||
With the advent of store based challenge issuance in [#749](https://github.com/TecharoHQ/anubis/pull/749), this means that these challenge IDs are [only good for 30 minutes](https://github.com/TecharoHQ/anubis/blob/e8dfff635015d6c906dddd49cb0eaf591326092a/lib/anubis.go#L130-L135d). Websites using the most recent version of Anubis have limited exposure to this problem.
|
||||
|
||||
Websites using older versions of Anubis have a much more increased exposure to this problem and are encouraged to keep this software updated as often and as frequently as possible.
|
||||
|
||||
Thanks to [@taviso](https://github.com/taviso) for reporting this issue.
|
||||
|
||||
### Breaking changes
|
||||
|
||||
- The "slow" frontend solver has been removed in order to reduce maintenance burden. Any existing uses of it will still work, but issue a warning upon startup asking administrators to upgrade to the "fast" frontend solver.
|
||||
- The legacy JSON based policy file example has been removed and all documentation for how to write a policy file in JSON has been deleted. JSON based policy files will still work, but YAML is the superior option for Anubis configuration.
|
||||
|
||||
### New Locales
|
||||
|
||||
- Lithuanian [#972](https://github.com/TecharoHQ/anubis/pull/972)
|
||||
- Vietnamese [#926](https://github.com/TecharoHQ/anubis/pull/926)
|
||||
|
||||
## v1.21.3: Minfilia Warde - Echo 3
|
||||
|
||||
### Added
|
||||
|
||||
#### New locales
|
||||
|
||||
Anubis now supports these new languages:
|
||||
|
||||
- [Swedish](https://github.com/TecharoHQ/anubis/pull/913)
|
||||
|
||||
### Fixes
|
||||
|
||||
#### Fixes a problem with nonstandard URLs and redirects
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
title: Proof-of-Work Algorithm Selection
|
||||
---
|
||||
|
||||
Anubis offers two proof-of-work algorithms:
|
||||
|
||||
- `"fast"`: highly optimized JavaScript that will run as fast as your computer lets it
|
||||
- `"slow"`: intentionally slow JavaScript that will waste time and memory
|
||||
|
||||
The fast algorithm is used by default to limit impacts on users' computers. Administrators may configure individual bot policy rules to use the slow algorithm in order to make known malicious clients waitloop and do nothing useful.
|
||||
|
||||
Generally, you should use the fast algorithm unless you have a good reason not to.
|
||||
@@ -126,6 +126,34 @@ Your directory tree should look like this, assuming your data is in `./your_fold
|
||||
|
||||
For an example directory tree using some off-the-shelf images the Tango icon set, see the [testdata](https://github.com/TecharoHQ/botstopper/tree/main/testdata/static/img) folder.
|
||||
|
||||
### Header-based overlay dispatch
|
||||
|
||||
If you run BotStopper in a multi-tenant environment where each tenant needs its own branding, BotStopper supports the ability to use request header values to direct asset reads to different folders under your `OVERLAY_FOLDER`. One of the most common ways to do this is based on the HTTP Host of the request. For example, if you set `ASSET_LOOKUP_HEADER=Host` in BotStopper's environment:
|
||||
|
||||
```text
|
||||
$OVERLAY_FOLDER
|
||||
├── static
|
||||
│ ├── css
|
||||
│ │ ├── custom.css
|
||||
│ │ └── eyesore.css
|
||||
│ └── img
|
||||
│ ├── happy.webp
|
||||
│ ├── pensive.webp
|
||||
│ └── reject.webp
|
||||
└── test.anubis.techaro.lol
|
||||
└── static
|
||||
├── css
|
||||
│ └── custom.css
|
||||
└── img
|
||||
├── happy.webp
|
||||
├── pensive.webp
|
||||
└── reject.webp
|
||||
```
|
||||
|
||||
Requests to `test.anubis.techaro.lol` will load assets in `$OVERLAY_FOLDER/test.anubis.techaro.lol/static` and all other requests will load them from `$OVERLAY_FOLDER/static`.
|
||||
|
||||
For an example, look at [the testdata folder in the BotStopper repo](https://github.com/TecharoHQ/botstopper/tree/main/testdata).
|
||||
|
||||
### Custom CSS
|
||||
|
||||
CSS customization is done mainly with CSS variables. View [the example custom CSS file](https://github.com/TecharoHQ/botstopper/blob/main/testdata/static/css/custom.css) for more information about what can be customized.
|
||||
@@ -197,6 +225,104 @@ $ du -hs *
|
||||
8.0K reject.webp
|
||||
```
|
||||
|
||||
## Custom HTML templates
|
||||
|
||||
If you need to completely control the HTML layout of all Anubis pages, you can customize the entire page with `USE_TEMPLATES=true`. This uses Go's standard library [html/template](https://pkg.go.dev/html/template) package to template HTML responses. Your templates can contain whatever HTML you want. The only catch is that you MUST include `{{ .Head }}` in the `<head>` element for challenge pages, and you MUST include `{{ .Body }}` in the `<body>` element for all pages.
|
||||
|
||||
In order to use this, you must define the following templates:
|
||||
|
||||
| Template path | Usage |
|
||||
| :----------------------------------------- | :---------------------------------------------- |
|
||||
| `$OVERLAY_FOLDER/templates/challenge.tmpl` | Challenge pages |
|
||||
| `$OVERLAY_FOLDER/templates/error.tmpl` | Error pages |
|
||||
| `$OVERLAY_FOLDER/templates/impressum.tmpl` | [Impressum](./configuration/impressum.mdx) page |
|
||||
|
||||
:::note
|
||||
|
||||
Currently HTML templates don't work together with [Header-based overlay dispatch](#header-based-overlay-dispatch). This is a known issue that will be fixed soon. If you enable header-based overlay dispatch, BotStopper will use the global `templates` folder instead of using the templates present in the overlay.
|
||||
|
||||
:::
|
||||
|
||||
Here are minimal (but working) examples for each template:
|
||||
|
||||
<details>
|
||||
<summary>`challenge.tmpl`</summary>
|
||||
|
||||
:::note
|
||||
|
||||
You **MUST** include the `{{.Head}}` segment in a `<head>` tag. It contains important information for challenges to execute. If you don't include this, no clients will be able to pass challenges.
|
||||
|
||||
:::
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Lang }}">
|
||||
<head>
|
||||
{{ .Head }}
|
||||
</head>
|
||||
<body>
|
||||
{{ .Body }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>`error.tmpl`</summary>
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Lang }}">
|
||||
<body>
|
||||
{{ .Body }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>`impressum.tmpl`</summary>
|
||||
|
||||
```html
|
||||
<!DOCTYPE html>
|
||||
<html lang="{{ .Lang }}">
|
||||
<body>
|
||||
{{ .Body }}
|
||||
</body>
|
||||
</html>
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
### Template functions
|
||||
|
||||
In order to make life easier, the following template functions are defined:
|
||||
|
||||
#### `Asset`
|
||||
|
||||
Constructs the path for a static asset in the [overlay folder](#custom-images-and-css)'s `static` directory.
|
||||
|
||||
```go
|
||||
func Asset(string) string
|
||||
```
|
||||
|
||||
Usage:
|
||||
|
||||
```html
|
||||
<link rel="stylesheet" href="{{ Asset "css/example.css" }}" />
|
||||
```
|
||||
|
||||
Generates:
|
||||
|
||||
```html
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="/.within.website/x/cmd/anubis/static/css/example.css"
|
||||
/>
|
||||
```
|
||||
|
||||
## Customizing messages
|
||||
|
||||
You can customize messages using the following environment variables:
|
||||
|
||||
27
docs/docs/admin/caveats-xff.mdx
Normal file
27
docs/docs/admin/caveats-xff.mdx
Normal file
@@ -0,0 +1,27 @@
|
||||
# Client IP Headers
|
||||
|
||||
Currently Anubis will always flatten the `X-Forwarded-For` when it contains multiple IP addresses. From right to left, the first IP address that is not in one of the following categories will be set as `X-Forwarded-For` in the request passed to the upstream.
|
||||
|
||||
- Private (`XFF_STRIP_PRIVATE`, enabled by default)
|
||||
- CGNAT (always stripped)
|
||||
- Link-local Unicast (always stripped)
|
||||
|
||||
```
|
||||
Incoming: X-Forwarded-For: 1.2.3.4, 5.6.7.8, 10.0.0.1
|
||||
Upstream: X-Forwarded-For: 5.6.7.8
|
||||
```
|
||||
|
||||
This behavior will cause problems if the proxy in front of Anubis is from a public IP, such as Cloudflare, because Anubis will use the Cloudflare IP instead of your client's real IP. You will likely see all requests from your browser being blocked and/or an infinite challenge loop.
|
||||
|
||||
```
|
||||
Incoming: X-Forwarded-For: REAL_CLIENT_IP, CF_IP
|
||||
Upstream: X-Forwarded-For: CF_IP
|
||||
```
|
||||
|
||||
As a workaround, you should configure your web server to parse an alternative source (such as `CF-Connecting-IP`), or pre-process the incoming `X-Forwarded-For` with your web server to ensure it only contains the real client IP address, then pass it to Anubis as `X-Forwarded-For`.
|
||||
|
||||
If you do not control the web server upstream of Anubis, the `custom-real-ip-header` command line flag accepts a header value that Anubis will read the real client IP address from. Anubis will set the `X-Real-IP` header to the IP address found in the custom header.
|
||||
|
||||
The `X-Real-IP` header will be automatically inferred from `X-Forwarded-For` if not set, setting it explicitly is not necessary as long as `X-Forwarded-For` contains only the real client IP. However setting it explicitly can eliminate spoofed values if your web server doesn't set this.
|
||||
|
||||
See [Cloudflare](environments/cloudflare.mdx) for an example configuration.
|
||||
@@ -1,8 +1,5 @@
|
||||
{
|
||||
"label": "Challenges",
|
||||
"position": 10,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "The different challenge methods that Anubis supports."
|
||||
}
|
||||
"link": null
|
||||
}
|
||||
9
docs/docs/admin/configuration/challenges/index.mdx
Normal file
9
docs/docs/admin/configuration/challenges/index.mdx
Normal file
@@ -0,0 +1,9 @@
|
||||
# Challenge Methods
|
||||
|
||||
Anubis supports multiple challenge methods:
|
||||
|
||||
- [Meta Refresh](./metarefresh.mdx)
|
||||
- [Preact](./preact.mdx)
|
||||
- [Proof of Work](./proof-of-work.mdx)
|
||||
|
||||
Read the documentation to know which method is best for you.
|
||||
@@ -12,7 +12,6 @@ To use it in your Anubis configuration:
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
difficulty: 1 # Number of seconds to wait before refreshing the page
|
||||
report_as: 4 # Unused by this challenge method
|
||||
algorithm: metarefresh # Specify a non-JS challenge method
|
||||
```
|
||||
|
||||
|
||||
18
docs/docs/admin/configuration/challenges/preact.mdx
Normal file
18
docs/docs/admin/configuration/challenges/preact.mdx
Normal file
@@ -0,0 +1,18 @@
|
||||
# Preact
|
||||
|
||||
The `preact` challenge sends the browser a simple challenge that makes it run very lightweight JavaScript that proves the client is able to execute client-side JavaScript. It uses [Preact](https://www.npmjs.com/package/preact) (a lightweight client side web framework in the vein of React) to do this.
|
||||
|
||||
To use it in your Anubis configuration:
|
||||
|
||||
```yaml
|
||||
# Generic catchall rule
|
||||
- name: generic-browser
|
||||
user_agent_regex: >-
|
||||
Mozilla|Opera
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
difficulty: 1 # Number of seconds to wait before refreshing the page
|
||||
algorithm: preact
|
||||
```
|
||||
|
||||
This is the default challenge method for most clients.
|
||||
@@ -103,6 +103,7 @@ Anubis exposes the following variables to expressions:
|
||||
| :-------------- | :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------------- |
|
||||
| `headers` | `map[string, string]` | The [headers](https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers) of the request being processed. | `{"User-Agent": "Mozilla/5.0 Gecko/20100101 Firefox/137.0"}` |
|
||||
| `host` | `string` | The [HTTP hostname](https://web.dev/articles/url-parts#host) the request is targeted to. | `anubis.techaro.lol` |
|
||||
| `contentLength` | `int64` | The numerical value of the `Content-Length` header. |
|
||||
| `load_1m` | `double` | The current system load average over the last one minute. This is useful for making [load-based checks](#using-the-system-load-average). |
|
||||
| `load_5m` | `double` | The current system load average over the last five minutes. This is useful for making [load-based checks](#using-the-system-load-average). |
|
||||
| `load_15m` | `double` | The current system load average over the last fifteen minutes. This is useful for making [load-based checks](#using-the-system-load-average). |
|
||||
@@ -232,6 +233,27 @@ This is best applied when doing explicit block rules, eg:
|
||||
|
||||
It seems counter-intuitive to allow known bad clients through sometimes, but this allows you to confuse attackers by making Anubis' behavior random. Adjust the thresholds and numbers as facts and circumstances demand.
|
||||
|
||||
### `regexSafe`
|
||||
|
||||
Available in `bot` expressions.
|
||||
|
||||
```ts
|
||||
function regexSafe(input: string): string;
|
||||
```
|
||||
|
||||
`regexSafe` takes a string and escapes it for safe use inside of a regular expression. This is useful when you are creating regular expressions from headers or variables such as `remoteAddress`.
|
||||
|
||||
| Input | Output |
|
||||
| :------------------------ | :------------------------------ |
|
||||
| `regexSafe("1.2.3.4")` | `1\\.2\\.3\\.4` |
|
||||
| `regexSafe("techaro.lol")` | `techaro\\.lol` |
|
||||
| `regexSafe("star*")` | `star\\*` |
|
||||
| `regexSafe("plus+")` | `plus\\+` |
|
||||
| `regexSafe("{braces}")` | `\\{braces\\}` |
|
||||
| `regexSafe("start^")` | `start\\^` |
|
||||
| `regexSafe("back\\slash")` | `back\\\\slash` |
|
||||
| `regexSafe("dash-dash")` | `dash\\-dash` |
|
||||
|
||||
### `segments`
|
||||
|
||||
Available in `bot` expressions.
|
||||
@@ -265,6 +287,99 @@ This is useful if you want to write rules that allow requests that have no query
|
||||
- size(segments(path)) < 2
|
||||
```
|
||||
|
||||
### DNS Functions
|
||||
|
||||
Anubis can also perform DNS lookups as a part of its expression evaluation. This can be useful for doing things like checking for a valid [Forward-confirmed reverse DNS (FCrDNS)](https://en.wikipedia.org/wiki/Forward-confirmed_reverse_DNS) record.
|
||||
|
||||
#### `arpaReverseIP`
|
||||
|
||||
Available in `bot` expressions.
|
||||
|
||||
```ts
|
||||
function arpaReverseIP(ip: string): string;
|
||||
```
|
||||
|
||||
`arpaReverseIP` takes an IP address and returns its value in [ARPA notation](https://www.ietf.org/rfc/rfc2317.html). This can be useful when matching PTR record patterns.
|
||||
|
||||
| Input | Output |
|
||||
| :----------------------------- | :------------------------------------------------------------------- |
|
||||
| `arpaReverseIP("1.2.3.4")` | `4.3.2.1` |
|
||||
| `arpaReverseIP("2001:db8::1")` | `1.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.0.8.b.d.0.1.0.0.2` |
|
||||
|
||||
#### `lookupHost`
|
||||
|
||||
Available in `bot` expressions.
|
||||
|
||||
```ts
|
||||
function lookupHost(host: string): string[];
|
||||
```
|
||||
|
||||
`lookupHost` performs a DNS lookup for the given hostname and returns a list of IP addresses.
|
||||
|
||||
```yaml
|
||||
- name: cloudflare-ip-in-host-header
|
||||
action: DENY
|
||||
expression: '"104.16.0.0" in lookupHost(headers["Host"])'
|
||||
```
|
||||
|
||||
#### `reverseDNS`
|
||||
|
||||
Available in `bot` expressions.
|
||||
|
||||
```ts
|
||||
function reverseDNS(ip: string): string[];
|
||||
```
|
||||
|
||||
`reverseDNS` takes an IP address and returns the DNS names associated with it. This is useful when you want to check PTR records of an IP address.
|
||||
|
||||
```yaml
|
||||
- name: allow-googlebot
|
||||
action: ALLOW
|
||||
expression: 'reverseDNS(remoteAddress).endsWith(".googlebot.com")'
|
||||
```
|
||||
|
||||
::: warning
|
||||
|
||||
Do not use this for validating the legitimacy of an IP address. It is possible for DNS records to be out of date or otherwise manipulated. Use [`verifyFCrDNS`](#verifyfcrdns) instead for a more reliable result.
|
||||
|
||||
:::
|
||||
|
||||
#### `verifyFCrDNS`
|
||||
|
||||
Available in `bot` expressions.
|
||||
|
||||
```ts
|
||||
function verifyFCrDNS(ip: string): bool;
|
||||
function verifyFCrDNS(ip: string, pattern: string): bool;
|
||||
```
|
||||
|
||||
`verifyFCrDNS` checks if the reverse DNS of an IP address matches its forward DNS. This is a common technique to filter out spam and bot traffic. `verifyFCrDNS` comes in two forms:
|
||||
|
||||
- `verifyFCrDNS(remoteAddress)` will check that the reverse DNS of the remote address resolves back to the remote address. If no PTR records, returns true.
|
||||
- `verifyFCrDNS(remoteAddress, pattern)` will check that the reverse DNS of the remote address is matching with pattern and that name resolves back to the remote address.
|
||||
|
||||
This is best used in rules like this:
|
||||
|
||||
```yaml
|
||||
- name: require-fcrdns-for-post
|
||||
action: DENY
|
||||
expression:
|
||||
all:
|
||||
- method == "POST"
|
||||
- "!verifyFCrDNS(remoteAddress)"
|
||||
```
|
||||
|
||||
Here is an another example that allows requests from telegram:
|
||||
|
||||
```yaml
|
||||
- name: telegrambot
|
||||
action: ALLOW
|
||||
expression:
|
||||
all:
|
||||
- userAgent.matches("TelegramBot")
|
||||
- verifyFCrDNS(remoteAddress, "ptr\\.telegram\\.org$")
|
||||
```
|
||||
|
||||
## Life advice
|
||||
|
||||
Expressions are very powerful. This is a benefit and a burden. If you are not careful with your expression targeting, you will be liable to get yourself into trouble. If you are at all in doubt, throw a `CHALLENGE` over a `DENY`. Legitimate users can easily work around a `CHALLENGE` result with a [proof of work challenge](../../design/why-proof-of-work.mdx). Bots are less likely to be able to do this.
|
||||
|
||||
@@ -7,25 +7,6 @@ Anubis has the ability to let you import snippets of configuration into the main
|
||||
|
||||
EG:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON">
|
||||
|
||||
```json
|
||||
{
|
||||
"bots": [
|
||||
{
|
||||
"import": "(data)/bots/ai-catchall.yaml"
|
||||
},
|
||||
{
|
||||
"import": "(data)/bots/cloudflare-workers.yaml"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML" default>
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
# Pathological bots to deny
|
||||
@@ -34,30 +15,8 @@ bots:
|
||||
- import: (data)/bots/cloudflare-workers.yaml
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Of note, a bot rule can either have inline bot configuration or import a bot config snippet. You cannot do both in a single bot rule.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON">
|
||||
|
||||
```json
|
||||
{
|
||||
"bots": [
|
||||
{
|
||||
"import": "(data)/bots/ai-catchall.yaml",
|
||||
"name": "generic-browser",
|
||||
"user_agent_regex": "Mozilla|Opera\n",
|
||||
"action": "CHALLENGE"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML" default>
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
- import: (data)/bots/ai-catchall.yaml
|
||||
@@ -67,9 +26,6 @@ bots:
|
||||
action: CHALLENGE
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This will return an error like this:
|
||||
|
||||
```text
|
||||
@@ -83,30 +39,11 @@ Paths can either be prefixed with `(data)` to import from the [the data folder i
|
||||
|
||||
You can also import from an imported file in case you want to import an entire folder of rules at once.
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON">
|
||||
|
||||
```json
|
||||
{
|
||||
"bots": [
|
||||
{
|
||||
"import": "(data)/bots/_deny-pathological.yaml"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML" default>
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
- import: (data)/bots/_deny-pathological.yaml
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This lets you import an entire ruleset at once:
|
||||
|
||||
```yaml
|
||||
@@ -124,22 +61,6 @@ Snippets can be written in either JSON or YAML, with a preference for YAML. When
|
||||
|
||||
Here is an example snippet that allows [IPv6 Unique Local Addresses](https://en.wikipedia.org/wiki/Unique_local_address) through Anubis:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON">
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"name": "ipv6-ula",
|
||||
"action": "ALLOW",
|
||||
"remote_addresses": ["fc00::/7"]
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML" default>
|
||||
|
||||
```yaml
|
||||
- name: ipv6-ula
|
||||
action: ALLOW
|
||||
@@ -147,9 +68,6 @@ Here is an example snippet that allows [IPv6 Unique Local Addresses](https://en.
|
||||
- fc00::/7
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## Extracting Anubis' embedded filesystem
|
||||
|
||||
You can always extract the list of rules embedded into the Anubis binary with this command:
|
||||
|
||||
@@ -32,7 +32,7 @@ sequenceDiagram
|
||||
participant Validation
|
||||
participant Evil Site
|
||||
|
||||
Hacker->>+User: Click on yoursite.com with this solution
|
||||
Hacker->>+User: Click on example.org with this solution
|
||||
User->>+Validation: Here's a solution, send me to evilsite.com
|
||||
Validation->>+User: Here's a cookie, go to evilsite.com
|
||||
User->>+Evil Site: GET evilsite.com
|
||||
@@ -46,11 +46,14 @@ Redirect domain not allowed
|
||||
|
||||
## Configuring allowed redirect domains
|
||||
|
||||
By default, Anubis will limit redirects to be on the same HTTP Host that Anubis is running on (EG: requests to yoursite.com cannot redirect outside of yoursite.com). If you need to set more than one domain, fill the `REDIRECT_DOMAINS` environment variable with a comma-separated list of domain names that Anubis should allow redirects to.
|
||||
By default, Anubis may redirect to any domain which could cause security issues in the unlikely case that an attacker passes a challenge for your browser and then tricks you into clicking a link to your domain.
|
||||
One can restrict the domains that Anubis can redirect to when passing a challenge by setting up `REDIRECT_DOMAINS` environment variable.
|
||||
If you need to set more than one domain, fill the environment variable with a comma-separated list of domain names.
|
||||
There is also glob matching support. You can pass `*.bugs.techaro.lol` to allow redirecting to anything ending with `.bugs.techaro.lol`. There is a limit of 4 wildcards.
|
||||
|
||||
:::note
|
||||
|
||||
These domains are _an exact string match_, they do not support wildcard matches.
|
||||
If you are hosting Anubis on a non-standard port (`https://example:com:8443`, `http://www.example.net:8080`, etc.), you must also include the port number here.
|
||||
|
||||
:::
|
||||
|
||||
@@ -60,7 +63,7 @@ These domains are _an exact string match_, they do not support wildcard matches.
|
||||
```shell
|
||||
# anubis.env
|
||||
|
||||
REDIRECT_DOMAINS="yoursite.com,secretplans.yoursite.com"
|
||||
REDIRECT_DOMAINS="example.org,secretplans.example.org,*.test.example.org"
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -72,7 +75,7 @@ services:
|
||||
anubis-nginx:
|
||||
image: ghcr.io/techarohq/anubis:latest
|
||||
environment:
|
||||
REDIRECT_DOMAINS: "yoursite.com,secretplans.yoursite.com"
|
||||
REDIRECT_DOMAINS: "example.org,secretplans.example.org,*.test.example.org"
|
||||
# ...
|
||||
```
|
||||
|
||||
@@ -86,7 +89,7 @@ Inside your Deployment, StatefulSet, or Pod:
|
||||
image: ghcr.io/techarohq/anubis:latest
|
||||
env:
|
||||
- name: REDIRECT_DOMAINS
|
||||
value: "yoursite.com,secretplans.yoursite.com"
|
||||
value: "example.org,secretplans.example.org,*.test.example.org"
|
||||
# ...
|
||||
```
|
||||
|
||||
|
||||
@@ -156,3 +156,68 @@ server {
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Caddy
|
||||
|
||||
Anubis can be used with the [`forward_auth`](https://caddyserver.com/docs/caddyfile/directives/forward_auth) directive in Caddy.
|
||||
|
||||
First, the `TARGET` environment variable in Anubis must be set to a space, eg:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="env-file" label="Environment file" default>
|
||||
|
||||
```shell
|
||||
# anubis.env
|
||||
|
||||
TARGET=" "
|
||||
# ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="docker-compose" label="Docker Compose">
|
||||
|
||||
```yaml
|
||||
services:
|
||||
anubis-caddy:
|
||||
image: ghcr.io/techarohq/anubis:latest
|
||||
environment:
|
||||
TARGET: " "
|
||||
# ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="k8s" label="Kubernetes">
|
||||
|
||||
Inside your Deployment, StatefulSet, or Pod:
|
||||
|
||||
```yaml
|
||||
- name: anubis
|
||||
image: ghcr.io/techarohq/anubis:latest
|
||||
env:
|
||||
- name: TARGET
|
||||
value: " "
|
||||
# ...
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Then configure the necessary directives in your site block:
|
||||
|
||||
```caddy
|
||||
route {
|
||||
# Assumption: Anubis is running in the same network namespace as
|
||||
# caddy on localhost TCP port 8923
|
||||
reverse_proxy /.within.website/* 127.0.0.1:8923
|
||||
forward_auth 127.0.0.1:8923 {
|
||||
uri /.within.website/x/cmd/anubis/api/check
|
||||
trusted_proxies private_ranges
|
||||
@unauthorized status 401
|
||||
handle_response @unauthorized {
|
||||
redir * /.within.website/?redir={uri} 307
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
If you want to use this for multiple sites, you can create a [snippet](https://caddyserver.com/docs/caddyfile/concepts#snippets) and import it in multiple site blocks.
|
||||
|
||||
@@ -41,7 +41,6 @@ thresholds:
|
||||
challenge:
|
||||
algorithm: metarefresh
|
||||
difficulty: 1
|
||||
report_as: 1
|
||||
|
||||
- name: moderate-suspicion
|
||||
expression:
|
||||
@@ -52,7 +51,6 @@ thresholds:
|
||||
challenge:
|
||||
algorithm: fast
|
||||
difficulty: 2
|
||||
report_as: 2
|
||||
|
||||
- name: extreme-suspicion
|
||||
expression: weight >= 20
|
||||
@@ -60,7 +58,6 @@ thresholds:
|
||||
challenge:
|
||||
algorithm: fast
|
||||
difficulty: 4
|
||||
report_as: 4
|
||||
```
|
||||
|
||||
This defines a suite of 4 thresholds:
|
||||
@@ -130,7 +127,6 @@ action: CHALLENGE
|
||||
challenge:
|
||||
algorithm: metarefresh
|
||||
difficulty: 1
|
||||
report_as: 1
|
||||
```
|
||||
|
||||
</td>
|
||||
|
||||
@@ -92,6 +92,11 @@ Assuming you are protecting `anubistest.techaro.lol`, you need the following ser
|
||||
DocumentRoot /var/www/anubistest.techaro.lol
|
||||
ErrorLog /var/log/httpd/anubistest.techaro.lol_error.log
|
||||
CustomLog /var/log/httpd/anubistest.techaro.lol_access.log combined
|
||||
|
||||
# Pass the remote IP to the proxied application instead of 127.0.0.1
|
||||
# This requires mod_remoteip
|
||||
RemoteIPHeader X-Real-IP
|
||||
RemoteIPTrustedProxy 127.0.0.1/32
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
|
||||
26
docs/docs/admin/environments/cloudflare.mdx
Normal file
26
docs/docs/admin/environments/cloudflare.mdx
Normal file
@@ -0,0 +1,26 @@
|
||||
# Cloudflare
|
||||
|
||||
If you are using Cloudflare, you should configure your server to use `CF-Connecting-IP` as the source of the real client IP, and pass that address to Anubis as `X-Forwarded-For`. Read [Client IP Headers](../caveats-xff.mdx) for details.
|
||||
|
||||
Example configuration with Caddy:
|
||||
|
||||
```Caddyfile
|
||||
{
|
||||
servers {
|
||||
# Cloudflare IP ranges from https://www.cloudflare.com/en-gb/ips/
|
||||
trusted_proxies static 173.245.48.0/20 103.21.244.0/22 103.22.200.0/22 103.31.4.0/22 141.101.64.0/18 108.162.192.0/18 190.93.240.0/20 188.114.96.0/20 197.234.240.0/22 198.41.128.0/17 162.158.0.0/15 104.16.0.0/13 104.24.0.0/14 172.64.0.0/13 131.0.72.0/22 2400:cb00::/32 2606:4700::/32 2803:f800::/32 2405:b500::/32 2405:8100::/32 2a06:98c0::/29 2c0f:f248::/32
|
||||
# Use CF-Connecting-IP to determine the client IP instead of XFF
|
||||
# https://caddyserver.com/docs/caddyfile/options#client-ip-headers
|
||||
client_ip_headers CF-Connecting-IP
|
||||
}
|
||||
}
|
||||
|
||||
example.com {
|
||||
reverse_proxy http://anubis:3000 {
|
||||
# Pass the client IP read from CF-Connecting-IP
|
||||
header_up X-Forwarded-For {client_ip}
|
||||
header_up X-Real-IP {client_ip}
|
||||
header_up X-Http-Version {http.request.proto}
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -1,5 +1,9 @@
|
||||
# Kubernetes
|
||||
|
||||
:::note
|
||||
Leave the `PUBLIC_URL` environment variable unset in this sidecar/standalone setup. Setting it here makes redirect construction fail (`redir=null`).
|
||||
:::
|
||||
|
||||
When setting up Anubis in Kubernetes, you want to make sure that you thread requests through Anubis kinda like this:
|
||||
|
||||
```mermaid
|
||||
@@ -90,8 +94,10 @@ containers:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
|
||||
```
|
||||
|
||||
|
||||
Then add a Service entry for Anubis:
|
||||
|
||||
```yaml
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Nginx
|
||||
|
||||
import CodeBlock from "@theme/CodeBlock";
|
||||
|
||||
Anubis is intended to be a filter proxy. The way to integrate this with nginx is to break your configuration up into two parts: TLS termination and then HTTP routing. Consider this diagram:
|
||||
|
||||
```mermaid
|
||||
@@ -36,106 +38,26 @@ These examples assume that you are using a setup where your nginx configuration
|
||||
|
||||
Assuming that we are protecting `anubistest.techaro.lol`, here's what the server configuration file would look like:
|
||||
|
||||
```nginx
|
||||
# /etc/nginx/conf.d/server-anubistest-techaro-lol.conf
|
||||
import anubisTest from "!!raw-loader!./nginx/server-anubistest-techaro-lol.conf";
|
||||
|
||||
# HTTP - Redirect all HTTP traffic to HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name anubistest.techaro.lol;
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# TLS termination server, this will listen over TLS (https) and then
|
||||
# proxy all traffic to the target via Anubis.
|
||||
server {
|
||||
# Listen on TCP port 443 with TLS (https) and HTTP/2
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Http-Version $server_protocol;
|
||||
proxy_pass http://anubis;
|
||||
}
|
||||
|
||||
server_name anubistest.techaro.lol;
|
||||
|
||||
ssl_certificate /path/to/your/certs/anubistest.techaro.lol.crt;
|
||||
ssl_certificate_key /path/to/your/certs/anubistest.techaro.lol.key;
|
||||
}
|
||||
|
||||
# Backend server, this is where your webapp should actually live.
|
||||
server {
|
||||
listen unix:/run/nginx/nginx.sock;
|
||||
|
||||
server_name anubistest.techaro.lol;
|
||||
root "/srv/http/anubistest.techaro.lol";
|
||||
index index.html;
|
||||
|
||||
# Get the visiting IP from the TLS termination server
|
||||
set_real_ip_from unix:;
|
||||
real_ip_header X-Real-IP;
|
||||
|
||||
# Your normal configuration can go here
|
||||
# location .php { fastcgi...} etc.
|
||||
}
|
||||
```
|
||||
<CodeBlock language="nginx">{anubisTest}</CodeBlock>
|
||||
|
||||
:::tip
|
||||
|
||||
You can copy the `location /` block into a separate file named something like `conf-anubis.inc` and then include it inline to other `server` blocks:
|
||||
|
||||
```nginx
|
||||
# /etc/nginx/conf.d/conf-anubis.inc
|
||||
import anubisInclude from "!!raw-loader!./nginx/conf-anubis.inc";
|
||||
|
||||
# Forward to anubis
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_pass http://anubis;
|
||||
}
|
||||
```
|
||||
<CodeBlock language="nginx">{anubisInclude}</CodeBlock>
|
||||
|
||||
Then in a server block:
|
||||
|
||||
<details>
|
||||
<summary>Full nginx config</summary>
|
||||
|
||||
```nginx
|
||||
# /etc/nginx/conf.d/server-mimi-techaro-lol.conf
|
||||
import mimiTecharoLol from "!!raw-loader!./nginx/server-mimi-techaro-lol.conf";
|
||||
|
||||
server {
|
||||
# Listen on 443 with SSL
|
||||
listen 443 ssl http2;
|
||||
listen [::]:443 ssl http2;
|
||||
|
||||
# Slipstream via Anubis
|
||||
include "conf-anubis.inc";
|
||||
|
||||
server_name mimi.techaro.lol;
|
||||
|
||||
ssl_certificate /path/to/your/certs/mimi.techaro.lol.crt;
|
||||
ssl_certificate_key /path/to/your/certs/mimi.techaro.lol.key;
|
||||
}
|
||||
|
||||
server {
|
||||
listen unix:/run/nginx/nginx.sock;
|
||||
|
||||
server_name mimi.techaro.lol;
|
||||
root "/srv/http/mimi.techaro.lol";
|
||||
index index.html;
|
||||
|
||||
# Your normal configuration can go here
|
||||
# location .php { fastcgi...} etc.
|
||||
}
|
||||
```
|
||||
<CodeBlock language="nginx">{mimiTecharoLol}</CodeBlock>
|
||||
|
||||
</details>
|
||||
|
||||
@@ -143,24 +65,9 @@ server {
|
||||
|
||||
Create an upstream for Anubis.
|
||||
|
||||
```nginx
|
||||
# /etc/nginx/conf.d/upstream-anubis.conf
|
||||
import anubisUpstream from "!!raw-loader!./nginx/upstream-anubis.conf";
|
||||
|
||||
upstream anubis {
|
||||
# Make sure this matches the values you set for `BIND` and `BIND_NETWORK`.
|
||||
# If this does not match, your services will not be protected by Anubis.
|
||||
|
||||
# Try anubis first over a UNIX socket
|
||||
server unix:/run/anubis/nginx.sock;
|
||||
#server 127.0.0.1:8923;
|
||||
|
||||
# Optional: fall back to serving the websites directly. This allows your
|
||||
# websites to be resilient against Anubis failing, at the risk of exposing
|
||||
# them to the raw internet without protection. This is a tradeoff and can
|
||||
# be worth it in some edge cases.
|
||||
#server unix:/run/nginx.sock backup;
|
||||
}
|
||||
```
|
||||
<CodeBlock language="nginx">{anubisUpstream}</CodeBlock>
|
||||
|
||||
This can be repeated for multiple sites. Anubis does not care about the HTTP `Host` header and will happily cope with multiple websites via the same instance.
|
||||
|
||||
|
||||
8
docs/docs/admin/environments/nginx/conf-anubis.inc
Normal file
8
docs/docs/admin/environments/nginx/conf-anubis.inc
Normal file
@@ -0,0 +1,8 @@
|
||||
# /etc/nginx/conf-anubis.inc
|
||||
|
||||
# Forward to anubis
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_pass http://anubis;
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
# /etc/nginx/conf.d/server-anubistest-techaro-lol.conf
|
||||
|
||||
# HTTP - Redirect all HTTP traffic to HTTPS
|
||||
server {
|
||||
listen 80;
|
||||
listen [::]:80;
|
||||
|
||||
server_name anubistest.techaro.lol;
|
||||
|
||||
location / {
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
}
|
||||
|
||||
# TLS termination server, this will listen over TLS (https) and then
|
||||
# proxy all traffic to the target via Anubis.
|
||||
server {
|
||||
# Listen on TCP port 443 with TLS (https) and HTTP/2
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
|
||||
location / {
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Http-Version $server_protocol;
|
||||
proxy_pass http://anubis;
|
||||
}
|
||||
|
||||
server_name anubistest.techaro.lol;
|
||||
|
||||
ssl_certificate /path/to/your/certs/anubistest.techaro.lol.crt;
|
||||
ssl_certificate_key /path/to/your/certs/anubistest.techaro.lol.key;
|
||||
}
|
||||
|
||||
# Backend server, this is where your webapp should actually live.
|
||||
server {
|
||||
listen unix:/run/nginx/nginx.sock;
|
||||
|
||||
server_name anubistest.techaro.lol;
|
||||
root "/srv/http/anubistest.techaro.lol";
|
||||
index index.html;
|
||||
|
||||
# Get the visiting IP from the TLS termination server
|
||||
set_real_ip_from unix:;
|
||||
real_ip_header X-Real-IP;
|
||||
|
||||
# Your normal configuration can go here
|
||||
# location .php { fastcgi...} etc.
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
# /etc/nginx/conf.d/server-mimi-techaro-lol.conf
|
||||
|
||||
server {
|
||||
# Listen on 443 with SSL
|
||||
listen 443 ssl;
|
||||
listen [::]:443 ssl;
|
||||
http2 on;
|
||||
|
||||
# Slipstream via Anubis
|
||||
include "conf-anubis.inc";
|
||||
|
||||
server_name mimi.techaro.lol;
|
||||
|
||||
ssl_certificate /path/to/your/certs/mimi.techaro.lol.crt;
|
||||
ssl_certificate_key /path/to/your/certs/mimi.techaro.lol.key;
|
||||
}
|
||||
|
||||
server {
|
||||
listen unix:/run/nginx/nginx.sock;
|
||||
|
||||
server_name mimi.techaro.lol;
|
||||
|
||||
port_in_redirect off;
|
||||
root "/srv/http/mimi.techaro.lol";
|
||||
index index.html;
|
||||
|
||||
# Your normal configuration can go here
|
||||
# location .php { fastcgi...} etc.
|
||||
}
|
||||
16
docs/docs/admin/environments/nginx/upstream-anubis.conf
Normal file
16
docs/docs/admin/environments/nginx/upstream-anubis.conf
Normal file
@@ -0,0 +1,16 @@
|
||||
# /etc/nginx/conf.d/upstream-anubis.conf
|
||||
|
||||
upstream anubis {
|
||||
# Make sure this matches the values you set for `BIND` and `BIND_NETWORK`.
|
||||
# If this does not match, your services will not be protected by Anubis.
|
||||
|
||||
# Try anubis first over a UNIX socket
|
||||
server unix:/run/anubis/nginx.sock;
|
||||
#server 127.0.0.1:8923;
|
||||
|
||||
# Optional: fall back to serving the websites directly. This allows your
|
||||
# websites to be resilient against Anubis failing, at the risk of exposing
|
||||
# them to the raw internet without protection. This is a tradeoff and can
|
||||
# be worth it in some edge cases.
|
||||
#server unix:/run/nginx.sock backup;
|
||||
}
|
||||
@@ -10,10 +10,6 @@ but it also applies to docker cli options.
|
||||
|
||||
:::
|
||||
|
||||
Currently, Anubis doesn't have any Traefik middleware,
|
||||
so you need to manually route it between Traefik and your target service.
|
||||
This routing is done per labels in Traefik.
|
||||
|
||||
In this example, we will use 4 Containers:
|
||||
|
||||
- `traefik` - the Traefik instance
|
||||
@@ -21,12 +17,6 @@ In this example, we will use 4 Containers:
|
||||
- `target` - our service to protect (`traefik/whoami` in this case)
|
||||
- `target2` - a second service that isn't supposed to be protected (`traefik/whoami` in this case)
|
||||
|
||||
There are 3 steps we need to follow:
|
||||
|
||||
1. Create a new exclusive Traefik endpoint for Anubis
|
||||
2. Pass all unspecified requests to Anubis
|
||||
3. Let Anubis pass all verified requests back to Traefik on its exclusive endpoint
|
||||
|
||||
## Diagram of Flow
|
||||
|
||||
This is a small diagram depicting the flow.
|
||||
@@ -40,74 +30,16 @@ anubis[Anubis]
|
||||
target[Target]
|
||||
|
||||
user-->|:443 - Requesting Service|traefik
|
||||
traefik-->|:8080 - Passing to Anubis|anubis
|
||||
anubis-->|:3923 - Passing back to Traefik|traefik
|
||||
traefik-->|:8080 - Check authorization to Anubis|anubis
|
||||
anubis-->|redirect if failed|traefik
|
||||
user-->|:8080 - make the challenge|traefik
|
||||
anubis-->|redirect back to target|traefik
|
||||
traefik-->|:80 - Passing to the target|target
|
||||
```
|
||||
|
||||
## Create an Exclusive Anubis Endpoint in Traefik
|
||||
|
||||
There are 2 ways of registering a new endpoint in Traefik.
|
||||
Which one to use depends on how you configured your Traefik so far.
|
||||
|
||||
**CLI Options:**
|
||||
|
||||
```yml
|
||||
--entrypoints.anubis.address=:3923
|
||||
```
|
||||
|
||||
**traefik.yml:**
|
||||
|
||||
```yml
|
||||
entryPoints:
|
||||
anubis:
|
||||
address: ":3923"
|
||||
```
|
||||
|
||||
It is important that the specified port isn't actually reachable from the outside,
|
||||
but only exposed in the Docker network.
|
||||
Exposing the Anubis port on Traefik directly will allow direct unprotected access to all containers behind it.
|
||||
|
||||
## Passing all unspecified Web Requests to Anubis
|
||||
|
||||
There are cases where you want Traefik to still route some requests without protection, just like before.
|
||||
To achieve this, we can register Anubis as the default handler for non-protected requests.
|
||||
|
||||
We also don't want users to get SSL Errors during the checking phase,
|
||||
thus we also need to let Traefik provide SSL Certs for our endpoint.
|
||||
This example expects an TLS cert resolver called `le`.
|
||||
|
||||
We also expect there to be an endpoint called `websecure` for HTTPS in this example.
|
||||
|
||||
This is an example of the required labels to configure Traefik on the Anubis container:
|
||||
|
||||
```yml
|
||||
labels:
|
||||
- traefik.enable=true # Enabling Traefik
|
||||
- traefik.docker.network=traefik # Telling Traefik which network to use
|
||||
- traefik.http.routers.anubis.priority=1 # Setting Anubis to the lowest priority, so it only takes the slack
|
||||
- traefik.http.routers.anubis.rule=PathRegexp(`.*`) # Wildcard match every path
|
||||
- traefik.http.routers.anubis.entrypoints=websecure # Listen on HTTPS
|
||||
- traefik.http.services.anubis.loadbalancer.server.port=8080 # Telling Traefik to which port it should route requests
|
||||
- traefik.http.routers.anubis.service=anubis # Telling Traefik to use the above specified port
|
||||
- traefik.http.routers.anubis.tls.certresolver=le # Telling Traefik to resolve a Cert for Anubis
|
||||
```
|
||||
|
||||
## Passing all Verified Requests Back Correctly to Traefik
|
||||
|
||||
To pass verified requests back to Traefik,
|
||||
we only need to configure Anubis using its environment variables:
|
||||
|
||||
```yml
|
||||
environment:
|
||||
- BIND=:8080
|
||||
- TARGET=http://traefik:3923
|
||||
```
|
||||
|
||||
## Full Example Config
|
||||
|
||||
Now that we know how to pass all requests back and forth, here is the example.
|
||||
This example contains 2 services: one that is protected and the other one that is not.
|
||||
This example contains 3 services: anubis, one that is protected and the other one that is not.
|
||||
|
||||
**compose.yml**
|
||||
|
||||
@@ -128,6 +60,8 @@ services:
|
||||
# Enable Traefik
|
||||
- traefik.enable=true
|
||||
- traefik.docker.network=traefik
|
||||
# Anubis middleware
|
||||
- traefik.http.middlewares.anubis.forwardauth.address=http://anubis:8080/.within.website/x/cmd/anubis/api/check
|
||||
# Redirect any HTTP to HTTPS
|
||||
- traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
|
||||
- traefik.http.routers.web.rule=PathPrefix(`/`)
|
||||
@@ -140,17 +74,22 @@ services:
|
||||
environment:
|
||||
# Telling Anubis, where to listen for Traefik
|
||||
- BIND=:8080
|
||||
# Telling Anubis to point to Traefik via the Docker network
|
||||
- TARGET=http://traefik:3923
|
||||
# Telling Anubis to do redirect — ensure there is a space after '='
|
||||
- 'TARGET= '
|
||||
# Specifies which domains Anubis is allowed to redirect to.
|
||||
- REDIRECT_DOMAINS=example.com
|
||||
# Should be the full external URL for Anubis (including scheme)
|
||||
- PUBLIC_URL=https://anubis.example.com
|
||||
# Should match your domain for proper cookie scoping
|
||||
- COOKIE_DOMAIN=example.com
|
||||
networks:
|
||||
- traefik
|
||||
labels:
|
||||
- traefik.enable=true # Enabling Traefik
|
||||
- traefik.docker.network=traefik # Telling Traefik which network to use
|
||||
- traefik.http.routers.anubis.priority=1 # Setting Anubis to the lowest priority, so it only takes the slack
|
||||
- traefik.http.routers.anubis.rule=PathRegexp(`.*`) # wildcard match anything
|
||||
- traefik.http.routers.anubis.rule=Host(`anubis.example.com`) # Only Matching Requests for example.com
|
||||
- traefik.http.routers.anubis.entrypoints=websecure # Listen on HTTPS
|
||||
- traefik.http.services.anubis.loadbalancer.server.port=8080 # Telling Traefik to which port it should route requests
|
||||
- traefik.http.services.anubis.loadbalancer.server.port=8080 # Telling Traefik where to receive requests
|
||||
- traefik.http.routers.anubis.service=anubis # Telling Traefik to use the above specified port
|
||||
- traefik.http.routers.anubis.tls.certresolver=le # Telling Traefik to resolve a Cert for Anubis
|
||||
|
||||
@@ -163,9 +102,11 @@ services:
|
||||
- traefik.enable=true # Enabling Traefik
|
||||
- traefik.docker.network=traefik # Telling Traefik which network to use
|
||||
- traefik.http.routers.target.rule=Host(`example.com`) # Only Matching Requests for example.com
|
||||
- traefik.http.routers.target.entrypoints=anubis # Listening on the exclusive Anubis Network
|
||||
- traefik.http.routers.target.entrypoints=websecure # Listening on the exclusive Anubis Network
|
||||
- traefik.http.services.target.loadbalancer.server.port=80 # Telling Traefik where to receive requests
|
||||
- traefik.http.routers.target.service=target # Telling Traefik to use the above specified port
|
||||
- traefik.http.routers.target.tls.certresolver=le # Telling Traefik to resolve a Cert for Anubis
|
||||
- traefik.http.routers.target.middlewares=anubis@docker # Use the Anubis middleware
|
||||
|
||||
# Not Protected by Anubis
|
||||
target2:
|
||||
@@ -175,7 +116,7 @@ services:
|
||||
labels:
|
||||
- traefik.enable=true # Enabling Traefik
|
||||
- traefik.docker.network=traefik # Telling Traefik which network to use
|
||||
- traefik.http.routers.target2.rule=Host(`another.com`) # Only Matching Requests for example.com
|
||||
- traefik.http.routers.target2.rule=Host(`another.example.com`) # Only Matching Requests for example.com
|
||||
- traefik.http.routers.target2.entrypoints=websecure # Listening on the exclusive Anubis Network
|
||||
- traefik.http.services.target2.loadbalancer.server.port=80 # Telling Traefik where to receive requests
|
||||
- traefik.http.routers.target2.service=target2 # Telling Traefik to use the above specified port
|
||||
@@ -198,9 +139,6 @@ entryPoints:
|
||||
address: ":80"
|
||||
websecure:
|
||||
address: ":443"
|
||||
# Anubis
|
||||
anubis:
|
||||
address: ":3923"
|
||||
|
||||
certificatesResolvers:
|
||||
le:
|
||||
|
||||
@@ -7,27 +7,6 @@ import TabItem from "@theme/TabItem";
|
||||
|
||||
To work around this, you can make a custom [expression](../configuration/expressions.mdx) rule that allows HTMX requests if the user has passed a challenge in the past:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON">
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "allow-htmx-iff-already-passed-challenge",
|
||||
"action": "ALLOW",
|
||||
"expression": {
|
||||
"all": [
|
||||
"\"Cookie\" in headers",
|
||||
"headers[\"Cookie\"].contains(\"anubis-auth\")",
|
||||
"\"Hx-Request\" in headers",
|
||||
"headers[\"Hx-Request\"] == \"true\""
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML" default>
|
||||
|
||||
```yaml
|
||||
- name: allow-htmx-iff-already-passed-challenge
|
||||
action: ALLOW
|
||||
@@ -39,7 +18,4 @@ To work around this, you can make a custom [expression](../configuration/express
|
||||
- 'headers["Hx-Request"] == "true"'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This will reduce some security because it does not assert the validity of the Anubis auth cookie, however in trade it improves the experience for existing users.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Wordpress
|
||||
# WordPress
|
||||
|
||||
Wordpress is the most popular blog engine on the planet.
|
||||
WordPress is the most popular blog engine on the planet.
|
||||
|
||||
## Using a multi-site setup with Anubis
|
||||
|
||||
@@ -27,7 +27,7 @@ flowchart LR
|
||||
US --> |whatever you're doing| B
|
||||
```
|
||||
|
||||
Wordpress may not realize that the underlying connection is being done over HTTPS. This could lead to a redirect loop in the `/wp-admin/` routes. In order to fix this, add the following to your `wp-config.php` file:
|
||||
WordPress may not realize that the underlying connection is being done over HTTPS. This could lead to a redirect loop in the `/wp-admin/` routes. In order to fix this, add the following to your `wp-config.php` file:
|
||||
|
||||
```php
|
||||
if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] === 'https') {
|
||||
@@ -36,4 +36,4 @@ if (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROT
|
||||
}
|
||||
```
|
||||
|
||||
This will make Wordpress think that your connection is over HTTPS instead of plain HTTP.
|
||||
This will make WordPress think that your connection is over HTTPS instead of plain HTTP.
|
||||
|
||||
8
docs/docs/admin/honeypot/_category_.json
Normal file
8
docs/docs/admin/honeypot/_category_.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Honeypot",
|
||||
"position": 40,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Honeypot features in Anubis, allowing Anubis to passively detect malicious crawlers."
|
||||
}
|
||||
}
|
||||
40
docs/docs/admin/honeypot/overview.mdx
Normal file
40
docs/docs/admin/honeypot/overview.mdx
Normal file
@@ -0,0 +1,40 @@
|
||||
---
|
||||
title: Dataset poisoning
|
||||
---
|
||||
|
||||
Anubis offers the ability to participate in [dataset poisoning](https://www.anthropic.com/research/small-samples-poison) attacks similar to what [iocaine](https://iocaine.madhouse-project.org/) and other similar tools offer. Currently this is in a preview state where a lot of details are hard-coded in order to test the viability of this approach.
|
||||
|
||||
In essence, when Anubis challenge and error pages are rendered they include a small bit of HTML code that browsers will ignore but scrapers will interpret as a link to ingest. This will then create a small forest of recursive nothing pages that are designed according to the following principles:
|
||||
|
||||
- These pages are _cheap_ to render, rendering in at most ten milliseconds on decently specced hardware.
|
||||
- These pages are _vacuous_, meaning that they essentially are devoid of content such that a human would find it odd and click away, but a scraper would not be able to know that and would continue through the forest.
|
||||
- These pages are _fairly large_ so that scrapers don't think that the pages are error pages or are otherwise devoid of content.
|
||||
- These pages are _fully self-contained_ so that they load fast without incurring additional load from resource fetches.
|
||||
|
||||
In this limited preview state, Anubis generates pages using [spintax](https://outboundly.ai/blogs/what-is-spintax-and-how-to-use-it/). Spintax is a syntax that is used to create different variants of utterances for use in marketing messages and email spam that evades word filtering. In its current form, Anubis' dataset poisoning has AI generated spintax that generates vapid LinkedIn posts with some western occultism thrown in for good measure. This results in utterances like the following:
|
||||
|
||||
> There's a moment when visionaries are being called to realize that the work can't be reduced to optimization, but about resonance. We don't transform products by grinding endlessly, we do it by holding the vision. Because meaning can't be forced, it unfolds over time when culture are in integrity. This moment represents a fundamental reimagining in how we think about work. This isn't a framework, it's a lived truth that requires courage. When we get honest, we activate nonlinear growth that don't show up in dashboards, but redefine success anyway.
|
||||
|
||||
This should be fairly transparent to humans that this is pseudoprofound anti-content and is a signal to click away.
|
||||
|
||||
## Plans
|
||||
|
||||
Future versions of this feature will allow for more customization. In the near future this will be configurable via the following mechanisms:
|
||||
|
||||
- WebAssembly logic for customizing how the poisoning data is generated (with examples including the existing spintax method).
|
||||
- Weight thresholds and logic for how they are interpreted by Anubis.
|
||||
- Other configuration settings as facts and circumstances dictate.
|
||||
|
||||
## Implementation notes
|
||||
|
||||
In its current implementation, the Anubis dataset poisoning feature has the following flaws that may hinder production deployments:
|
||||
|
||||
- All Anubis instances use the same method for generating dataset poisoning information. This may be easy for malicious actors to detect and ignore.
|
||||
- Anubis dataset poisoning routes are under the `/.within.website/x/cmd/anubis` URL hierarchy. This may be easy for malicious actors to detect and ignore.
|
||||
|
||||
Right now Anubis assigns 30 weight points if the following criteria are met:
|
||||
|
||||
- A client's User-Agent has been observed in the dataset poisoning maze at least 25 times.
|
||||
- The network-clamped IP address (/24 for IPv4 and /48 for IPv6) has been observed in the dataset poisoning maze at least 25 times.
|
||||
|
||||
Additionally, when any given client by both User-Agent and network-clamped IP address has been observed, Anubis will emit log lines warning about it so that administrative action can be taken up to and including [filing abuse reports with the network owner](/blog/2025/file-abuse-reports).
|
||||
@@ -2,8 +2,16 @@
|
||||
title: Setting up Anubis
|
||||
---
|
||||
|
||||
import EnterpriseOnly from "@site/src/components/EnterpriseOnly";
|
||||
import RandomKey from "@site/src/components/RandomKey";
|
||||
|
||||
export const EO = () => (
|
||||
<>
|
||||
<EnterpriseOnly link="./botstopper/" />
|
||||
<div style={{ marginBottom: "0.5rem" }} />
|
||||
</>
|
||||
);
|
||||
|
||||
Anubis is meant to sit between your reverse proxy (such as Nginx or Caddy) and your target service. One instance of Anubis must be used per service you are protecting.
|
||||
|
||||
<center>
|
||||
@@ -58,33 +66,46 @@ Currently the following settings are configurable via the policy file:
|
||||
|
||||
Anubis uses these environment variables for configuration:
|
||||
|
||||
| Environment Variable | Default value | Explanation |
|
||||
| :----------------------------- | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `BASE_PREFIX` | unset | If set, adds a global prefix to all Anubis endpoints (everything starting with `/.within.website/x/anubis/`). For example, setting this to `/myapp` would make Anubis accessible at `/myapp/` instead of `/`. This is useful when running Anubis behind a reverse proxy that routes based on path prefixes. |
|
||||
| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
|
||||
| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. |
|
||||
| `COOKIE_DOMAIN` | unset | The domain the Anubis challenge pass cookie should be set to. This should be set to the domain you bought from your registrar (EG: `techaro.lol` if your webapp is running on `anubis.techaro.lol`). See this [stackoverflow explanation of cookies](https://stackoverflow.com/a/1063760) for more information.<br/><br/>Note that unlike `REDIRECT_DOMAINS`, you should never include a port number in this variable. |
|
||||
| `COOKIE_DYNAMIC_DOMAIN` | false | If set to true, automatically set cookie domain fields based on the hostname of the request. EG: if you are making a request to `anubis.techaro.lol`, the Anubis cookie will be valid for any subdomain of `techaro.lol`. |
|
||||
| `COOKIE_EXPIRATION_TIME` | `168h` | The amount of time the authorization cookie is valid for. |
|
||||
| `COOKIE_PARTITIONED` | `false` | If set to `true`, enables the [partitioned (CHIPS) flag](https://developers.google.com/privacy-sandbox/cookies/chips), meaning that Anubis inside an iframe has a different set of cookies than the domain hosting the iframe. |
|
||||
| `COOKIE_SECURE` | `true` | If set to `true`, enables the [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies), meaning that the cookies will only be transmitted over HTTPS. If Anubis is used in an unsecure context (plain HTTP), this will be need to be set to false |
|
||||
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
||||
| `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. When running multiple instances on the same base domain, the key must be the same across all instances. See below for details. |
|
||||
| `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. |
|
||||
| `METRICS_BIND` | `:9090` | The network address that Anubis serves Prometheus metrics on. See `BIND` for more information. |
|
||||
| `METRICS_BIND_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. |
|
||||
| `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_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. |
|
||||
| `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. |
|
||||
| `REDIRECT_DOMAINS` | unset | If set, restrict the domains that Anubis can redirect to when passing a challenge.<br/><br/>If this is unset, Anubis may redirect to any domain which could cause security issues in the unlikely case that an attacker passes a challenge for your browser and then tricks you into clicking a link to your domain.<br/><br/>Note that if you are hosting Anubis on a non-standard port (`https://example:com:8443`, `http://www.example.net:8080`, etc.), you must also include the port number here. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
| `STRIP_BASE_PREFIX` | `false` | If set to `true`, strips the base prefix from request paths when forwarding to the target server. This is useful when your target service expects to receive requests without the base prefix. For example, with `BASE_PREFIX=/foo` and `STRIP_BASE_PREFIX=true`, a request to `/foo/bar` would be forwarded to the target as `/bar`. |
|
||||
| `TARGET` | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`. |
|
||||
| `USE_REMOTE_ADDRESS` | unset | If set to `true`, Anubis will take the client's IP from the network socket. For production deployments, it is expected that a reverse proxy is used in front of Anubis, which pass the IP using headers, instead. |
|
||||
| `WEBMASTER_EMAIL` | unset | If set, shows a contact email address when rendering error pages. This email address will be how users can get in contact with administrators. |
|
||||
| `XFF_STRIP_PRIVATE` | `true` | If set, strip private addresses from `X-Forwarded-For` headers. To unset this, you must set `XFF_STRIP_PRIVATE=false` or `--xff-strip-private=false`. |
|
||||
| Environment Variable | Default value | Explanation |
|
||||
|:-------------------------------|:------------------------|:-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `ASSET_LOOKUP_HEADER` | unset | <EO /> If set, use the contents of this header in requests when looking up custom assets in `OVERLAY_FOLDER`. See [Header-based overlay dispatch](./botstopper.mdx#header-based-overlay-dispatch) for more details. |
|
||||
| `BASE_PREFIX` | unset | If set, adds a global prefix to all Anubis endpoints (everything starting with `/.within.website/x/anubis/`). For example, setting this to `/myapp` would make Anubis accessible at `/myapp/` instead of `/`. This is useful when running Anubis behind a reverse proxy that routes based on path prefixes. |
|
||||
| `BIND` | `:8923` | The network address that Anubis listens on. For `unix`, set this to a path: `/run/anubis/instance.sock` |
|
||||
| `BIND_NETWORK` | `tcp` | The address family that Anubis listens on. Accepts `tcp`, `unix` and anything Go's [`net.Listen`](https://pkg.go.dev/net#Listen) supports. |
|
||||
| `CHALLENGE_TITLE` | unset | <EO /> If set, override the translation stack to show a custom title for challenge pages such as "Making sure your connection is secure!". See [Customizing messages](./botstopper.mdx#customizing-messages) for more details. |
|
||||
| `COOKIE_DOMAIN` | unset | The domain the Anubis challenge pass cookie should be set to. This should be set to the domain you bought from your registrar (EG: `techaro.lol` if your webapp is running on `anubis.techaro.lol`). See this [stackoverflow explanation of cookies](https://stackoverflow.com/a/1063760) for more information.<br/><br/>Note that unlike `REDIRECT_DOMAINS`, you should never include a port number in this variable. |
|
||||
| `COOKIE_DYNAMIC_DOMAIN` | false | If set to true, automatically set cookie domain fields based on the hostname of the request. EG: if you are making a request to `anubis.techaro.lol`, the Anubis cookie will be valid for any subdomain of `techaro.lol`. |
|
||||
| `COOKIE_EXPIRATION_TIME` | `168h` | The amount of time the authorization cookie is valid for. |
|
||||
| `CUSTOM_REAL_IP_HEADER` | unset | If set, Anubis will read the client's real IP address from this header, and set it in `X-Real-IP` header. |
|
||||
| `COOKIE_PARTITIONED` | `false` | If set to `true`, enables the [partitioned (CHIPS) flag](https://developers.google.com/privacy-sandbox/cookies/chips), meaning that Anubis inside an iframe has a different set of cookies than the domain hosting the iframe. |
|
||||
| `COOKIE_PREFIX` | `anubis-cookie` | The prefix used for browser cookies created by Anubis. Useful for customization or avoiding conflicts with other applications. |
|
||||
| `COOKIE_SECURE` | `true` | If set to `true`, enables the [Secure flag](https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#block_access_to_your_cookies), meaning that the cookies will only be transmitted over HTTPS. If Anubis is used in an unsecure context (plain HTTP), this will be need to be set to false |
|
||||
| `COOKIE_SAME_SITE` | `None` | Controls the cookie’s [`SameSite` attribute](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value). Allowed: `None`, `Lax`, `Strict`, `Default`. `None` permits cross-site use but modern browsers require it to be **Secure**—so if `COOKIE_SECURE=false` or you serve over plain HTTP, use `Lax` (recommended) or `Strict` or the cookie will be rejected. `Default` uses the Go runtime’s `SameSiteDefaultMode`. `None` will be downgraded to `Lax` automatically if cookie is set NOT to be secure. |
|
||||
| `DIFFICULTY` | `4` | The difficulty of the challenge, or the number of leading zeroes that must be in successful responses. |
|
||||
| `DIFFICULTY_IN_JWT` | `false` | If set to `true`, adds the `difficulty` field into JWT claims, which indicates the difficulty the token has been generated. This may be useful for statistics and debugging. |
|
||||
| `ED25519_PRIVATE_KEY_HEX` | unset | The hex-encoded ed25519 private key used to sign Anubis responses. If this is not set, Anubis will generate one for you. This should be exactly 64 characters long. **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. See below for details. |
|
||||
| `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. |
|
||||
| `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_NETWORK` | `tcp` | The address family that the Anubis metrics server listens on. See `BIND_NETWORK` for more information. |
|
||||
| `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_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. |
|
||||
| `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`. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
| `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. |
|
||||
| `STRIP_BASE_PREFIX` | `false` | If set to `true`, strips the base prefix from request paths when forwarding to the target server. This is useful when your target service expects to receive requests without the base prefix. For example, with `BASE_PREFIX=/foo` and `STRIP_BASE_PREFIX=true`, a request to `/foo/bar` would be forwarded to the target as `/bar`. |
|
||||
| `TARGET` | `http://localhost:3923` | The URL of the service that Anubis should forward valid requests to. Supports Unix domain sockets, set this to a URI like so: `unix:///path/to/socket.sock`. |
|
||||
| `USE_REMOTE_ADDRESS` | unset | If set to `true`, Anubis will take the client's IP from the network socket. For production deployments, it is expected that a reverse proxy is used in front of Anubis, which pass the IP using headers, instead. |
|
||||
| `USE_SIMPLIFIED_EXPLANATION` | false | If set to `true`, replaces the text when clicking "Why am I seeing this?" with a more simplified text for a non-tech-savvy audience. |
|
||||
| `USE_TEMPLATES` | false | <EO /> If set to `true`, enable [custom HTML template support](./botstopper.mdx#custom-html-templates), allowing you to completely rewrite how BotStopper renders its HTML pages. |
|
||||
| `WEBMASTER_EMAIL` | unset | If set, shows a contact email address when rendering error pages. This email address will be how users can get in contact with administrators. |
|
||||
| `XFF_STRIP_PRIVATE` | `true` | If set, strip private addresses from `X-Forwarded-For` headers. To unset this, you must set `XFF_STRIP_PRIVATE=false` or `--xff-strip-private=false`. |
|
||||
|
||||
<details>
|
||||
<summary>Advanced configuration settings</summary>
|
||||
@@ -95,12 +116,14 @@ If you don't know or understand what these settings mean, ignore them. These are
|
||||
|
||||
:::
|
||||
|
||||
| Environment Variable | Default value | Explanation |
|
||||
| :---------------------------- | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `TARGET_SNI` | unset | If set, overrides the TLS handshake hostname in requests forwarded to `TARGET`. |
|
||||
| `TARGET_HOST` | unset | If set, overrides the Host header in requests forwarded to `TARGET`. |
|
||||
| `TARGET_INSECURE_SKIP_VERIFY` | `false` | If `true`, skip TLS certificate validation for targets that listen over `https`. If your backend does not listen over `https`, ignore this setting. |
|
||||
| `HS512_SECRET` | unset | Secret string for JWT HS512 algorithm. If this is not set, Anubis will use ED25519 as defined via the variables above. The longer the better; 128 chars should suffice. |
|
||||
| Environment Variable | Default value | Explanation |
|
||||
| :---------------------------- | :------------ | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `FORCED_LANGUAGE` | unset | If set, forces Anubis to display challenge pages in the specified language instead of using the browser's Accept-Language header. Use ISO 639-1 language codes (e.g., `de` for German, `fr` for French). |
|
||||
| `HS512_SECRET` | unset | Secret string for JWT HS512 algorithm. If this is not set, Anubis will use ED25519 as defined via the variables above. The longer the better; 128 chars should suffice. **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. |
|
||||
| `TARGET_DISABLE_KEEPALIVE` | `false` | If `true`, disables HTTP keep-alive for connections to the target backend. Useful for backends that don't handle keep-alive properly. |
|
||||
| `TARGET_HOST` | unset | If set, overrides the Host header in requests forwarded to `TARGET`. |
|
||||
| `TARGET_INSECURE_SKIP_VERIFY` | `false` | If `true`, skip TLS certificate validation for targets that listen over `https`. If your backend does not listen over `https`, ignore this setting. |
|
||||
| `TARGET_SNI` | unset | If set, TLS handshake hostname when forwarding requests to the `TARGET`. If set to auto, use Host header. |
|
||||
|
||||
</details>
|
||||
|
||||
@@ -175,6 +198,7 @@ Alternatively here is a key generated by your browser:
|
||||
To get Anubis filtering your traffic, you need to make sure it's added to your HTTP load balancer or platform configuration. See the [environments category](/docs/category/environments) for detailed information on individual environments.
|
||||
|
||||
- [Apache](./environments/apache.mdx)
|
||||
- [Caddy](./environments/caddy.mdx)
|
||||
- [Docker compose](./environments/docker-compose.mdx)
|
||||
- [Kubernetes](./environments/kubernetes.mdx)
|
||||
- [Nginx](./environments/nginx.mdx)
|
||||
|
||||
50
docs/docs/admin/iplist2rule.mdx
Normal file
50
docs/docs/admin/iplist2rule.mdx
Normal file
@@ -0,0 +1,50 @@
|
||||
---
|
||||
title: iplist2rule CLI tool
|
||||
---
|
||||
|
||||
The `iplist2rule` tool converts IP blocklists into Anubis challenge policies. It reads common IP block list formats and generates the appropriate Anubis policy file for IP address filtering.
|
||||
|
||||
## Installation
|
||||
|
||||
Install directly with Go
|
||||
|
||||
```bash
|
||||
go install github.com/TecharoHQ/anubis/utils/cmd/iplist2rule@latest
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Basic conversion from URL:
|
||||
|
||||
```bash
|
||||
iplist2rule https://raw.githubusercontent.com/7c/torfilter/refs/heads/main/lists/txt/torfilter-1m-flat.txt filter-tor.yaml
|
||||
```
|
||||
|
||||
Explicitly allow every IP address on a list:
|
||||
|
||||
```bash
|
||||
iplist2rule --action ALLOW https://raw.githubusercontent.com/7c/torfilter/refs/heads/main/lists/txt/torfilter-1m-flat.txt filter-tor.yaml
|
||||
```
|
||||
|
||||
Add weight to requests matching IP addresses on a list:
|
||||
|
||||
```bash
|
||||
iplist2rule --action WEIGH --weight 20 https://raw.githubusercontent.com/7c/torfilter/refs/heads/main/lists/txt/torfilter-1m-flat.txt filter-tor.yaml
|
||||
```
|
||||
|
||||
## Options
|
||||
|
||||
| Flag | Description | Default |
|
||||
| :------------ | :----------------------------------------------------------------------------------------------- | :-------------------------------- |
|
||||
| `--action` | The Anubis action to take for the IP address in question, must be in ALL CAPS. | `DENY` (forbids traffic) |
|
||||
| `--rule-name` | The name for the generated Anubis rule, should be in kebab-case. | (not set, inferred from filename) |
|
||||
| `--weight` | When `--action=WEIGH`, how many weight points should be added or removed from matching requests? | 0 (not set) |
|
||||
|
||||
## Using the Generated Policy
|
||||
|
||||
Save the output and import it in your main policy file:
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
- import: "./filter-tor.yaml"
|
||||
```
|
||||
@@ -7,6 +7,10 @@ import TabItem from "@theme/TabItem";
|
||||
|
||||
Out of the box, Anubis is pretty heavy-handed. It will aggressively challenge everything that might be a browser (usually indicated by having `Mozilla` in its user agent). However, some bots are smart enough to get past the challenge. Some things that look like bots may actually be fine (IE: RSS readers). Some resources need to be visible no matter what. Some resources and remotes are fine to begin with.
|
||||
|
||||
Anubis lets you customize its configuration with a Policy File. This is a YAML document that spells out what actions Anubis should take when evaluating requests. The [default configuration](https://github.com/TecharoHQ/anubis/blob/main/data/botPolicies.yaml) explains everything, but this page contains an overview of everything you can do with it.
|
||||
|
||||
## Bot Policies
|
||||
|
||||
Bot policies let you customize the rules that Anubis uses to allow, deny, or challenge incoming requests. Currently you can set policies by the following matches:
|
||||
|
||||
- Request path
|
||||
@@ -18,75 +22,18 @@ As of version v1.17.0 or later, configuration can be written in either JSON or Y
|
||||
|
||||
Here's an example rule that denies [Amazonbot](https://developer.amazon.com/en/amazonbot):
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON" default>
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "amazonbot",
|
||||
"user_agent_regex": "Amazonbot",
|
||||
"action": "DENY"
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML">
|
||||
|
||||
```yaml
|
||||
- name: amazonbot
|
||||
user_agent_regex: Amazonbot
|
||||
action: DENY
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
When this rule is evaluated, Anubis will check the `User-Agent` string of the request. If it contains `Amazonbot`, Anubis will send an error page to the user saying that access is denied, but in such a way that makes scrapers think they have correctly loaded the webpage.
|
||||
|
||||
Right now the only kinds of policies you can write are bot policies. Other forms of policies will be added in the future.
|
||||
|
||||
Here is a minimal policy file that will protect against most scraper bots:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON" default>
|
||||
|
||||
```json
|
||||
{
|
||||
"bots": [
|
||||
{
|
||||
"name": "cloudflare-workers",
|
||||
"headers_regex": {
|
||||
"CF-Worker": ".*"
|
||||
},
|
||||
"action": "DENY"
|
||||
},
|
||||
{
|
||||
"name": "well-known",
|
||||
"path_regex": "^/.well-known/.*$",
|
||||
"action": "ALLOW"
|
||||
},
|
||||
{
|
||||
"name": "favicon",
|
||||
"path_regex": "^/favicon.ico$",
|
||||
"action": "ALLOW"
|
||||
},
|
||||
{
|
||||
"name": "robots-txt",
|
||||
"path_regex": "^/robots.txt$",
|
||||
"action": "ALLOW"
|
||||
},
|
||||
{
|
||||
"name": "generic-browser",
|
||||
"user_agent_regex": "Mozilla",
|
||||
"action": "CHALLENGE"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML">
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
- name: cloudflare-workers
|
||||
@@ -107,22 +54,20 @@ bots:
|
||||
action: CHALLENGE
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This allows requests to [`/.well-known`](https://en.wikipedia.org/wiki/Well-known_URI), `/favicon.ico`, `/robots.txt`, and challenges any request that has the word `Mozilla` in its User-Agent string. The [default policy file](https://github.com/TecharoHQ/anubis/blob/main/data/botPolicies.json) is a bit more cohesive, but this should be more than enough for most users.
|
||||
This allows requests to [`/.well-known`](https://en.wikipedia.org/wiki/Well-known_URI), `/favicon.ico`, `/robots.txt`, and challenges any request that has the word `Mozilla` in its User-Agent string. The [default policy file](https://github.com/TecharoHQ/anubis/blob/main/data/botPolicies.yaml) is a bit more cohesive, but this should be more than enough for most users.
|
||||
|
||||
If no rules match the request, it is allowed through. For more details on this default behavior and its implications, see [Default allow behavior](./default-allow-behavior.mdx).
|
||||
|
||||
## Writing your own rules
|
||||
### Writing your own rules
|
||||
|
||||
There are three actions that can be returned from a rule:
|
||||
There are four actions that can be returned from a rule:
|
||||
|
||||
| Action | Effects |
|
||||
| :---------- | :-------------------------------------------------------------------------------- |
|
||||
| `ALLOW` | Bypass all further checks and send the request to the backend. |
|
||||
| `DENY` | Deny the request and send back an error message that scrapers think is a success. |
|
||||
| `CHALLENGE` | Show a challenge page and/or validate that clients have passed a challenge. |
|
||||
| Action | Effects |
|
||||
| :---------- | :---------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `ALLOW` | Bypass all further checks and send the request to the backend. |
|
||||
| `DENY` | Deny the request and send back an error message that scrapers think is a success. |
|
||||
| `CHALLENGE` | Show a challenge page and/or validate that clients have passed a challenge. |
|
||||
| `WEIGH` | Change the [request weight](#request-weight) for this request. See the [request weight](#request-weight) docs for more information. |
|
||||
|
||||
Name your rules in lower case using kebab-case. Rule names will be exposed in Prometheus metrics.
|
||||
|
||||
@@ -130,27 +75,6 @@ Name your rules in lower case using kebab-case. Rule names will be exposed in Pr
|
||||
|
||||
Rules can also have their own challenge settings. These are customized using the `"challenge"` key. For example, here is a rule that makes challenges artificially hard for connections with the substring "bot" in their user agent:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON" default>
|
||||
|
||||
This rule has been known to have a high false positive rate in testing. Please use this with care.
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "generic-bot-catchall",
|
||||
"user_agent_regex": "(?i:bot|crawler)",
|
||||
"action": "CHALLENGE",
|
||||
"challenge": {
|
||||
"difficulty": 16,
|
||||
"report_as": 4,
|
||||
"algorithm": "slow"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML">
|
||||
|
||||
This rule has been known to have a high false positive rate in testing. Please use this with care.
|
||||
|
||||
```yaml
|
||||
@@ -160,20 +84,15 @@ This rule has been known to have a high false positive rate in testing. Please u
|
||||
action: CHALLENGE
|
||||
challenge:
|
||||
difficulty: 16 # impossible
|
||||
report_as: 4 # lie to the operator
|
||||
algorithm: slow # intentionally waste CPU cycles and time
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
Challenges can be configured with these settings:
|
||||
|
||||
| Key | Example | Description |
|
||||
| :----------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `difficulty` | `4` | The challenge difficulty (number of leading zeros) for proof-of-work. See [Why does Anubis use Proof-of-Work?](/docs/design/why-proof-of-work) for more details. |
|
||||
| `report_as` | `4` | What difficulty the UI should report to the user. Useful for messing with industrial-scale scraping efforts. |
|
||||
| `algorithm` | `"fast"` | The algorithm used on the client to run proof-of-work calculations. This must be set to `"fast"` or `"slow"`. See [Proof-of-Work Algorithm Selection](./algorithm-selection) for more details. |
|
||||
| Key | Example | Description |
|
||||
| :----------- | :------- | :--------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `difficulty` | `4` | The challenge difficulty (number of leading zeros) for proof-of-work. See [Why does Anubis use Proof-of-Work?](/docs/design/why-proof-of-work) for more details. |
|
||||
| `algorithm` | `"fast"` | The challenge method to use. See [the list of challenge methods](./configuration/challenges/) for more information. |
|
||||
|
||||
### Remote IP based filtering
|
||||
|
||||
@@ -181,21 +100,6 @@ The `remote_addresses` field of a Bot rule allows you to set the IP range that t
|
||||
|
||||
For example, you can allow a search engine to connect if and only if its IP address matches the ones they published:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON" default>
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "qwantbot",
|
||||
"user_agent_regex": "\\+https\\:\\/\\/help\\.qwant\\.com/bot/",
|
||||
"action": "ALLOW",
|
||||
"remote_addresses": ["91.242.162.0/24"]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML">
|
||||
|
||||
```yaml
|
||||
- name: qwantbot
|
||||
user_agent_regex: \+https\://help\.qwant\.com/bot/
|
||||
@@ -204,25 +108,8 @@ For example, you can allow a search engine to connect if and only if its IP addr
|
||||
remote_addresses: ["91.242.162.0/24"]
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This also works at an IP range level without any other checks:
|
||||
|
||||
<Tabs>
|
||||
<TabItem value="json" label="JSON" default>
|
||||
|
||||
```json
|
||||
{
|
||||
"name": "internal-network",
|
||||
"action": "ALLOW",
|
||||
"remote_addresses": ["100.64.0.0/10"]
|
||||
}
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="yaml" label="YAML">
|
||||
|
||||
```yaml
|
||||
name: internal-network
|
||||
action: ALLOW
|
||||
@@ -230,9 +117,6 @@ remote_addresses:
|
||||
- 100.64.0.0/10
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
## 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.
|
||||
@@ -310,13 +194,90 @@ store:
|
||||
path: /data/anubis.bdb
|
||||
```
|
||||
|
||||
### `s3api`
|
||||
|
||||
A network-backed storage layer backed by [object storage](https://en.wikipedia.org/wiki/Object_storage), specifically using the [S3 API](https://docs.aws.amazon.com/AmazonS3/latest/API/Type_API_Reference.html). This can be backed by any S3-compatible object storage service such as:
|
||||
|
||||
- [AWS S3](https://aws.amazon.com/s3/)
|
||||
- [Cloudflare R2](https://www.cloudflare.com/developer-platform/products/r2/)
|
||||
- [Hetzner Object Storage](https://www.hetzner.com/storage/object-storage/)
|
||||
- [Minio](https://www.min.io/)
|
||||
- [Tigris](https://www.tigrisdata.com/)
|
||||
|
||||
If you are using a cloud platform, they likely provide an S3 compatible object storage service. If not, you may want to choose [one of the fastest options](https://www.tigrisdata.com/blog/benchmark-small-objects/).
|
||||
|
||||
| Should I use this backend? | Yes/no |
|
||||
| :------------------------------------------------------------ | :----- |
|
||||
| Are you running only one instance of Anubis for this service? | 🚫 No |
|
||||
| Does your service get a lot of traffic? | ✅ Yes |
|
||||
| Do you want to store data persistently when Anubis restarts? | ✅ Yes |
|
||||
| Do you run Anubis without mutable filesystem storage? | ✅ Yes |
|
||||
|
||||
:::note
|
||||
|
||||
Using this backend will cause a lot of S3 operations, at least one for creating challenges, one for invalidating challenges, one for updating challenges to prevent double-spends, and one for removing challenges.
|
||||
|
||||
:::
|
||||
|
||||
#### Configuration
|
||||
|
||||
The `s3api` backend takes the following configuration options:
|
||||
|
||||
| Name | Type | Example | Description |
|
||||
| :----------- | :------ | :------------ | :------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `bucketName` | string | `anubis-data` | (Required) The name of the dedicated bucket for Anubis to store information in. |
|
||||
| `pathStyle` | boolean | `false` | If true, use path-style S3 API operations. Please consult your storage provider's documentation if you don't know what you should put here. |
|
||||
|
||||
:::note
|
||||
|
||||
You should probably enable a lifecycle expiration rule for buckets containing Anubis data. Here is an example policy:
|
||||
|
||||
```json
|
||||
{
|
||||
"Rules": [
|
||||
{
|
||||
"Status": "Enabled",
|
||||
"Expiration": {
|
||||
"Days": 7
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
Adjust this as facts and circumstances demand, but 7 days should be enough for anyone.
|
||||
|
||||
:::
|
||||
|
||||
Example:
|
||||
|
||||
Assuming your environment looks like this:
|
||||
|
||||
```sh
|
||||
# All of the following are fake credentials that look like real ones.
|
||||
AWS_ACCESS_KEY_ID=accordingToAllKnownRulesOfAviation
|
||||
AWS_SECRET_ACCESS_KEY=thereIsNoWayABeeShouldBeAbleToFly
|
||||
AWS_REGION=yow
|
||||
AWS_ENDPOINT_URL_S3=https://yow.s3.probably-not-malware.lol
|
||||
```
|
||||
|
||||
Then your configuration would look like this:
|
||||
|
||||
```yaml
|
||||
store:
|
||||
backend: s3api
|
||||
parameters:
|
||||
bucketName: techaro-prod-anubis
|
||||
pathStyle: false
|
||||
```
|
||||
|
||||
### `valkey`
|
||||
|
||||
[Valkey](https://valkey.io/) is an in-memory key/value store that clients access over the network. This allows multiple instances of Anubis to share information and does not require each instance of Anubis to have persistent filesystem storage.
|
||||
|
||||
:::note
|
||||
|
||||
You can also use [Redis](http://redis.io/) with Anubis.
|
||||
You can also use [Redis™](http://redis.io/) with Anubis.
|
||||
|
||||
:::
|
||||
|
||||
@@ -328,15 +289,17 @@ This backend is ideal if you are running multiple instances of Anubis in a worke
|
||||
| Does your service get a lot of traffic? | ✅ Yes |
|
||||
| Do you want to store data persistently when Anubis restarts? | ✅ Yes |
|
||||
| Do you run Anubis without mutable filesystem storage? | ✅ Yes |
|
||||
| Do you have Redis or Valkey installed? | ✅ Yes |
|
||||
| Do you have Redis™ or Valkey installed? | ✅ Yes |
|
||||
|
||||
#### Configuration
|
||||
|
||||
The `valkey` backend takes the following configuration options:
|
||||
|
||||
| Name | Type | Example | Description |
|
||||
| :---- | :----- | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `url` | string | `redis://valkey:6379/0` | The URL for the instance of Redis or Valkey that Anubis should store data in. This is in the same format as `REDIS_URL` in many cloud providers. |
|
||||
| Name | Type | Example | Description |
|
||||
| :--------- | :----- | :---------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| `cluster` | bool | `false` | If true, use [Redis™ Clustering](https://redis.io/topics/cluster-spec) for storing Anubis data. |
|
||||
| `sentinel` | object | `{}` | See [Redis™ Sentinel docs](#redis-sentinel) for more detail and examples |
|
||||
| `url` | string | `redis://valkey:6379/0` | The URL for the instance of Redis™ or Valkey that Anubis should store data in. This is in the same format as `REDIS_URL` in many cloud providers. |
|
||||
|
||||
Example:
|
||||
|
||||
@@ -351,6 +314,96 @@ store:
|
||||
|
||||
This would have the Valkey client connect to host `valkey.int.techaro.lol` on port `6379` with database `0` (the default database).
|
||||
|
||||
#### Redis™ Sentinel
|
||||
|
||||
If you are using [Redis™ Sentinel](https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel/) for a high availability setup, you need to configure the `sentinel` object. This object takes the following configuration options:
|
||||
|
||||
| Name | Type | Example | Description |
|
||||
| :----------- | :----------------------- | :-------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `addr` | string or list of string | `10.43.208.130:26379` | (Required) The host and port of the Redis™ Sentinel server. When possible, use DNS names for this. If you have multiple addresses, supply a list of them. |
|
||||
| `clientName` | string | `Anubis` | The client name reported to Redis™ Sentinel. Set this if you want to track Anubis connections to your Redis™ Sentinel. |
|
||||
| `masterName` | string | `mymaster` | (Required) The name of the master in the Redis™ Sentinel configuration. This is used to discover where to find client connection hosts/ports. |
|
||||
| `username` | string | `azurediamond` | The username used to authenticate against the Redis™ Sentinel and Redis™ servers. |
|
||||
| `password` | string | `hunter2` | The password used to authenticate against the Redis™ Sentinel and Redis™ servers. |
|
||||
|
||||
## Logging management
|
||||
|
||||
Anubis has very verbose logging out of the box. This is intentional and allows administrators to be sure that it is working merely by watching it work in real time. Some administrators may not appreciate this level of logging out of the box. As such, Anubis lets you customize details about how it logs data.
|
||||
|
||||
Anubis uses a practice called [structured logging](https://stackify.com/what-is-structured-logging-and-why-developers-need-it/) to emit log messages with key-value pair context. In order to make analyzing large amounts of log messages easier, Anubis encodes all logs in JSON. This allows you to use any tool that can parse JSON to perform analytics or monitor for issues.
|
||||
|
||||
Anubis exposes the following logging settings in the policy file:
|
||||
|
||||
| Name | Type | Example | Description |
|
||||
| :----------- | :----------------------- | :-------------- | :--------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `level` | [log level](#log-levels) | `info` | The logging level threshold. Any logs that are at or above this threshold will be drained to the sink. Any other logs will be discarded. |
|
||||
| `sink` | string | `stdio`, `file` | The sink where the logs drain to as they are being recorded in Anubis. |
|
||||
| `parameters` | object | | Parameters for the given logging sink. This will vary based on the logging sink of choice. See below for more information. |
|
||||
|
||||
Anubis supports the following logging sinks:
|
||||
|
||||
1. `file`: logs are emitted to a file that is rotated based on size and age. Old log files are compressed with gzip to save space. This allows for better integration with users that decide to use legacy service managers (OpenRC, FreeBSD's init, etc).
|
||||
2. `stdio`: logs are emitted to the standard error stream of the Anubis process. This allows runtimes such as Docker, Podman, Systemd, and Kubernetes to capture logs with their native logging subsystems without any additional configuration.
|
||||
|
||||
### Log levels
|
||||
|
||||
Anubis uses Go's [standard library `log/slog` package](https://pkg.go.dev/log/slog) to emit structured logs. By default, Anubis logs at the [Info level](https://pkg.go.dev/log/slog#Level), which is fairly verbose out of the box. Here are the possible logging levels in Anubis:
|
||||
|
||||
| Log level | Use in Anubis |
|
||||
| :-------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `DEBUG` | The raw unfiltered torrent of doom. Only use this if you are actively working on Anubis or have very good reasons to use it. |
|
||||
| `INFO` | The default logging level, fairly verbose in order to make it easier for automation to parse. |
|
||||
| `WARN` | A "more silent" logging level. Much less verbose. Some things that are now at the `info` level need to be moved up to the `warn` level in future patches. |
|
||||
| `ERROR` | Only log error messages. |
|
||||
|
||||
Additionally, you can set a "slightly higher" log level if you need to, such as:
|
||||
|
||||
```yaml
|
||||
logging:
|
||||
sink: stdio
|
||||
level: "INFO+1"
|
||||
```
|
||||
|
||||
This isn't currently used by Anubis, but will be in the future for "slightly important" information.
|
||||
|
||||
### `file` sink
|
||||
|
||||
The `file` sink makes Anubis write its logs to the filesystem and rotate them out when the log file meets certain thresholds. This logging sink takes the following parameters:
|
||||
|
||||
| Name | Type | Example | Description |
|
||||
| :------------- | :-------------- | :-------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `file` | string | `/var/log/anubis.log` | The file where Anubis logs should be written to. Make sure the user Anubis is running as has write and file creation permissions to this directory. |
|
||||
| `maxBackups` | number | `3` | The number of old log files that should be maintained when log files are rotated out. |
|
||||
| `maxBytes` | number of bytes | `67108864` (64Mi) | The maximum size of each log file before it is rotated out. |
|
||||
| `maxAge` | number of days | `7` | If a log file is more than this many days old, rotate it out. |
|
||||
| `compress` | boolean | `true` | If true, compress old log files with gzip. This should be set to `true` and is only exposed as an option for dealing with legacy workflows where there is magical thinking about log files at play. |
|
||||
| `useLocalTime` | boolean | `false` | If true, use the system local time zone to create log filenames instead of UTC. This should almost always be set to `false` and is only exposed for legacy workflows where there is magical thinking about time zones at play. |
|
||||
|
||||
```yaml
|
||||
logging:
|
||||
sink: file
|
||||
parameters:
|
||||
file: "./var/anubis.log"
|
||||
maxBackups: 3 # keep at least 3 old copies
|
||||
maxBytes: 67108864 # each file can have up to 64 Mi of logs
|
||||
maxAge: 7 # rotate files out every n days
|
||||
compress: true # gzip-compress old log files
|
||||
useLocalTime: false # timezone for rotated files is UTC
|
||||
```
|
||||
|
||||
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).
|
||||
|
||||
### `stdio` sink
|
||||
|
||||
By default, Anubis logs everything to the standard error stream of its process. This requires no configuration:
|
||||
|
||||
```yaml
|
||||
logging:
|
||||
sink: stdio
|
||||
```
|
||||
|
||||
If you use a service orchestration platform that does not capture the standard error stream of processes, you need to use a different logging sink.
|
||||
|
||||
## Risk calculation for downstream services
|
||||
|
||||
In case your service needs it for risk calculation reasons, Anubis exposes information about the rules that any requests match using a few headers:
|
||||
|
||||
@@ -12,6 +12,7 @@ Install directly with Go:
|
||||
```bash
|
||||
go install github.com/TecharoHQ/anubis/cmd/robots2policy@latest
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
Basic conversion from URL:
|
||||
@@ -35,8 +36,8 @@ robots2policy -input robots.txt -action DENY -format json
|
||||
## Options
|
||||
|
||||
| Flag | Description | Default |
|
||||
|-----------------------|--------------------------------------------------------------------|---------------------|
|
||||
| `-input` | robots.txt file path or URL (use `-` for stdin) | *required* |
|
||||
| --------------------- | ------------------------------------------------------------------ | ------------------- |
|
||||
| `-input` | robots.txt file path or URL (use `-` for stdin) | _required_ |
|
||||
| `-output` | Output file (use `-` for stdout) | stdout |
|
||||
| `-format` | Output format: `yaml` or `json` | `yaml` |
|
||||
| `-action` | Action for disallowed paths: `ALLOW`, `DENY`, `CHALLENGE`, `WEIGH` | `CHALLENGE` |
|
||||
@@ -47,6 +48,7 @@ robots2policy -input robots.txt -action DENY -format json
|
||||
## Example
|
||||
|
||||
Input robots.txt:
|
||||
|
||||
```txt
|
||||
User-agent: *
|
||||
Disallow: /admin/
|
||||
@@ -57,6 +59,7 @@ Disallow: /
|
||||
```
|
||||
|
||||
Generated policy:
|
||||
|
||||
```yaml
|
||||
- name: robots-txt-policy-disallow-1
|
||||
action: CHALLENGE
|
||||
@@ -77,8 +80,8 @@ Generated policy:
|
||||
Save the output and import it in your main policy file:
|
||||
|
||||
```yaml
|
||||
import:
|
||||
- path: "./robots-policy.yaml"
|
||||
bots:
|
||||
- import: "./robots-policy.yaml"
|
||||
```
|
||||
|
||||
The tool handles wildcard patterns, user-agent specific rules, and blacklisted bots automatically.
|
||||
|
||||
8
docs/docs/admin/roles/_category_.json
Normal file
8
docs/docs/admin/roles/_category_.json
Normal file
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"label": "Server Roles",
|
||||
"position": 40,
|
||||
"link": {
|
||||
"type": "generated-index",
|
||||
"description": "Various server roles you will need to keep in mind with Anubis."
|
||||
}
|
||||
}
|
||||
10
docs/docs/admin/roles/oci-registry.mdx
Normal file
10
docs/docs/admin/roles/oci-registry.mdx
Normal file
@@ -0,0 +1,10 @@
|
||||
# OCI Registries
|
||||
|
||||
If you are serving an OCI registry behind Anubis, you will need to import the `(data)/clients/docker-client.yaml` file in order to make sure that OCI registry clients can download images:
|
||||
|
||||
```yaml
|
||||
bots:
|
||||
- import: (data)/meta/default-config.yaml
|
||||
- import: (data)/clients/docker-client.yaml
|
||||
# ... the rest of your config
|
||||
```
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user