mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-04-27 10:32:42 +00:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9b3cce4d4f | |||
| 56ec19d2da | |||
| 11ab78ec33 | |||
| 11f944128f | |||
| dfeb02b4ae | |||
| b66630df74 | |||
| 63e6a15280 | |||
| 888c477933 | |||
| cda06f8c71 |
@@ -38,4 +38,3 @@ Samsung
|
|||||||
wenet
|
wenet
|
||||||
qwertiko
|
qwertiko
|
||||||
setuplistener
|
setuplistener
|
||||||
mba
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY,,}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||||
with:
|
with:
|
||||||
context: ./docs
|
context: ./docs
|
||||||
cache-to: type=gha
|
cache-to: type=gha
|
||||||
@@ -53,14 +53,14 @@ jobs:
|
|||||||
push: true
|
push: true
|
||||||
|
|
||||||
- name: Apply k8s manifests to limsa lominsa
|
- name: Apply k8s manifests to limsa lominsa
|
||||||
uses: actions-hub/kubectl@f8645c756533365a9fc1ae9aad8980b2a892d2c2 # v1.36.0
|
uses: actions-hub/kubectl@934aaa4354bbbc3d2176ae8d7cae92d515032dff # v1.35.3
|
||||||
env:
|
env:
|
||||||
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
||||||
with:
|
with:
|
||||||
args: apply -k docs/manifest
|
args: apply -k docs/manifest
|
||||||
|
|
||||||
- name: Apply k8s manifests to limsa lominsa
|
- name: Apply k8s manifests to limsa lominsa
|
||||||
uses: actions-hub/kubectl@f8645c756533365a9fc1ae9aad8980b2a892d2c2 # v1.36.0
|
uses: actions-hub/kubectl@934aaa4354bbbc3d2176ae8d7cae92d515032dff # v1.35.3
|
||||||
env:
|
env:
|
||||||
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
|
||||||
with:
|
with:
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
|
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
|
||||||
with:
|
with:
|
||||||
context: ./docs
|
context: ./docs
|
||||||
cache-to: type=gha
|
cache-to: type=gha
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
@@ -32,7 +32,7 @@ jobs:
|
|||||||
go-version: "stable"
|
go-version: "stable"
|
||||||
|
|
||||||
- name: Cache playwright binaries
|
- name: Cache playwright binaries
|
||||||
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
|
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||||
id: playwright-cache
|
id: playwright-cache
|
||||||
with:
|
with:
|
||||||
path: |
|
path: |
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ jobs:
|
|||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get install -y build-essential
|
sudo apt-get install -y build-essential
|
||||||
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
@@ -41,7 +41,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
go tool yeet
|
go tool yeet
|
||||||
|
|
||||||
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||||
with:
|
with:
|
||||||
name: packages
|
name: packages
|
||||||
path: var/*
|
path: var/*
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.11.0"
|
node-version: "24.11.0"
|
||||||
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
|
||||||
@@ -57,7 +57,7 @@ jobs:
|
|||||||
run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV
|
run: echo "ARTIFACT_NAME=${{ matrix.test }}" | sed 's|/|-|g' >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Upload artifact
|
- name: Upload artifact
|
||||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
|
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
|
||||||
if: always()
|
if: always()
|
||||||
with:
|
with:
|
||||||
name: ${{ env.ARTIFACT_NAME }}
|
name: ${{ env.ARTIFACT_NAME }}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ jobs:
|
|||||||
persist-credentials: false
|
persist-credentials: false
|
||||||
|
|
||||||
- name: Install the latest version of uv
|
- name: Install the latest version of uv
|
||||||
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
uses: astral-sh/setup-uv@cec208311dfd045dd5311c1add060b2062131d57 # v8.0.0
|
||||||
|
|
||||||
- name: Run zizmor 🌈
|
- name: Run zizmor 🌈
|
||||||
run: uvx zizmor --format sarif . > results.sarif
|
run: uvx zizmor --format sarif . > results.sarif
|
||||||
@@ -29,7 +29,7 @@ jobs:
|
|||||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
- name: Upload SARIF file
|
- name: Upload SARIF file
|
||||||
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
|
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
sarif_file: results.sarif
|
||||||
category: zizmor
|
category: zizmor
|
||||||
|
|||||||
@@ -175,13 +175,6 @@ status_codes:
|
|||||||
# bind: ":9090"
|
# bind: ":9090"
|
||||||
# network: "tcp"
|
# network: "tcp"
|
||||||
#
|
#
|
||||||
# # To protect your metrics server with basic auth, set credentials below:
|
|
||||||
# #
|
|
||||||
# # https://anubis.techaro.lol/docs/admin/policies#http-basic-authentication
|
|
||||||
# basicAuth:
|
|
||||||
# username: ""
|
|
||||||
# password: ""
|
|
||||||
#
|
|
||||||
# # To serve metrics over TLS, set the path to the right TLS certificate and key
|
# # To serve metrics over TLS, set the path to the right TLS certificate and key
|
||||||
# # here. When the files change on disk, they will automatically be reloaded.
|
# # here. When the files change on disk, they will automatically be reloaded.
|
||||||
# #
|
# #
|
||||||
|
|||||||
@@ -20,11 +20,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
- Fixed mixed tab/space indentation in Caddy documentation code block
|
- Fixed mixed tab/space indentation in Caddy documentation code block
|
||||||
- Improve error messages and fix broken REDIRECT_DOMAINS link in docs ([#1193](https://github.com/TecharoHQ/anubis/issues/1193))
|
- Improve error messages and fix broken REDIRECT_DOMAINS link in docs ([#1193](https://github.com/TecharoHQ/anubis/issues/1193))
|
||||||
- Add Bulgarian locale ([#1394](https://github.com/TecharoHQ/anubis/pull/1394))
|
- Add Bulgarian locale ([#1394](https://github.com/TecharoHQ/anubis/pull/1394))
|
||||||
- Fixed case-sensitivity mismatch in geoipchecker.go
|
|
||||||
- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465)).
|
- Fix CEL internal errors when iterating `headers`/`query` map wrappers by implementing map iterators for `HTTPHeaders` and `URLValues` ([#1465](https://github.com/TecharoHQ/anubis/pull/1465)).
|
||||||
- Enable [metrics serving via TLS](./admin/policies.mdx#tls), including [mutual TLS (mTLS)](./admin/policies.mdx#mtls).
|
- Enable [metrics serving via TLS](./admin/policies.mdx#tls), including [mutual TLS (mTLS)](./admin/policies.mdx#mtls).
|
||||||
- Enable [HTTP basic auth](./admin/policies.mdx#http-basic-authentication) for the metrics server.
|
|
||||||
- Fix a bug in the dataset poisoning maze that could allow denial of service [#1580](https://github.com/TecharoHQ/anubis/issues/1580).
|
|
||||||
|
|
||||||
## v1.25.0: Necron
|
## v1.25.0: Necron
|
||||||
|
|
||||||
|
|||||||
@@ -171,24 +171,6 @@ metrics:
|
|||||||
|
|
||||||
As it is not expected for certificate authority certificates to change often, the CA certificate will NOT be automatically reloaded when the respective file changes.
|
As it is not expected for certificate authority certificates to change often, the CA certificate will NOT be automatically reloaded when the respective file changes.
|
||||||
|
|
||||||
### HTTP basic authentication
|
|
||||||
|
|
||||||
Anubis' metrics server also supports setting HTTP basic auth as a lightweight protection against unauthorized users viewing metrics data. As the basic auth credentials are hardcoded in the configuration file, administrators SHOULD use randomly generated credentials, such as type-4 UUIDs or other high entropy strings. These credentials MUST NOT be sensitive or used to protect other high value systems.
|
|
||||||
|
|
||||||
Configure it with the `basicAuth` block under `metrics`:
|
|
||||||
|
|
||||||
```yaml
|
|
||||||
metrics:
|
|
||||||
bind: ":9090"
|
|
||||||
network: "tcp"
|
|
||||||
|
|
||||||
basicAuth:
|
|
||||||
username: azurediamond
|
|
||||||
password: hunter2
|
|
||||||
```
|
|
||||||
|
|
||||||
If you have Python installed, you can generate a high entropy password with `python -c 'import secrets; print(secrets.token_urlsafe(32))'`.
|
|
||||||
|
|
||||||
## Imprint / Impressum support
|
## Imprint / Impressum support
|
||||||
|
|
||||||
Anubis has support for showing imprint / impressum information. This is defined in the `impressum` block of your configuration. See [Imprint / Impressum configuration](./configuration/impressum.mdx) for more information.
|
Anubis has support for showing imprint / impressum information. This is defined in the `impressum` block of your configuration. See [Imprint / Impressum configuration](./configuration/impressum.mdx) for more information.
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"crypto/sha256"
|
|
||||||
"crypto/subtle"
|
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
// BasicAuth wraps next in HTTP Basic authentication using the provided
|
|
||||||
// credentials. If either username or password is empty, next is returned
|
|
||||||
// unchanged and a debug log line is emitted.
|
|
||||||
//
|
|
||||||
// Credentials are compared in constant time to avoid leaking information
|
|
||||||
// through timing side channels.
|
|
||||||
func BasicAuth(realm, username, password string, next http.Handler) http.Handler {
|
|
||||||
if username == "" || password == "" {
|
|
||||||
slog.Debug("skipping middleware, basic auth credentials are empty")
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
|
|
||||||
expectedUser := sha256.Sum256([]byte(username))
|
|
||||||
expectedPass := sha256.Sum256([]byte(password))
|
|
||||||
challenge := fmt.Sprintf("Basic realm=%q, charset=\"UTF-8\"", realm)
|
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
user, pass, ok := r.BasicAuth()
|
|
||||||
if !ok {
|
|
||||||
unauthorized(w, challenge)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gotUser := sha256.Sum256([]byte(user))
|
|
||||||
gotPass := sha256.Sum256([]byte(pass))
|
|
||||||
|
|
||||||
userMatch := subtle.ConstantTimeCompare(gotUser[:], expectedUser[:])
|
|
||||||
passMatch := subtle.ConstantTimeCompare(gotPass[:], expectedPass[:])
|
|
||||||
|
|
||||||
if userMatch&passMatch != 1 {
|
|
||||||
unauthorized(w, challenge)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
next.ServeHTTP(w, r)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func unauthorized(w http.ResponseWriter, challenge string) {
|
|
||||||
w.Header().Set("WWW-Authenticate", challenge)
|
|
||||||
http.Error(w, "Unauthorized", http.StatusUnauthorized)
|
|
||||||
}
|
|
||||||
@@ -1,138 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
"net/http/httptest"
|
|
||||||
"strings"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func okHandler() http.Handler {
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
w.WriteHeader(http.StatusOK)
|
|
||||||
_, _ = w.Write([]byte("ok"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasicAuth(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
const (
|
|
||||||
realm = "test-realm"
|
|
||||||
username = "admin"
|
|
||||||
password = "hunter2"
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, tt := range []struct {
|
|
||||||
name string
|
|
||||||
setAuth bool
|
|
||||||
user string
|
|
||||||
pass string
|
|
||||||
wantStatus int
|
|
||||||
wantBody string
|
|
||||||
wantChall bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid credentials",
|
|
||||||
setAuth: true,
|
|
||||||
user: username,
|
|
||||||
pass: password,
|
|
||||||
wantStatus: http.StatusOK,
|
|
||||||
wantBody: "ok",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing credentials",
|
|
||||||
setAuth: false,
|
|
||||||
wantStatus: http.StatusUnauthorized,
|
|
||||||
wantChall: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wrong username",
|
|
||||||
setAuth: true,
|
|
||||||
user: "nobody",
|
|
||||||
pass: password,
|
|
||||||
wantStatus: http.StatusUnauthorized,
|
|
||||||
wantChall: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wrong password",
|
|
||||||
setAuth: true,
|
|
||||||
user: username,
|
|
||||||
pass: "wrong",
|
|
||||||
wantStatus: http.StatusUnauthorized,
|
|
||||||
wantChall: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty supplied credentials",
|
|
||||||
setAuth: true,
|
|
||||||
user: "",
|
|
||||||
pass: "",
|
|
||||||
wantStatus: http.StatusUnauthorized,
|
|
||||||
wantChall: true,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
h := BasicAuth(realm, username, password, okHandler())
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
if tt.setAuth {
|
|
||||||
req.SetBasicAuth(tt.user, tt.pass)
|
|
||||||
}
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
if rec.Code != tt.wantStatus {
|
|
||||||
t.Errorf("status = %d, want %d", rec.Code, tt.wantStatus)
|
|
||||||
}
|
|
||||||
|
|
||||||
if tt.wantBody != "" && rec.Body.String() != tt.wantBody {
|
|
||||||
t.Errorf("body = %q, want %q", rec.Body.String(), tt.wantBody)
|
|
||||||
}
|
|
||||||
|
|
||||||
chall := rec.Header().Get("WWW-Authenticate")
|
|
||||||
if tt.wantChall {
|
|
||||||
if chall == "" {
|
|
||||||
t.Error("WWW-Authenticate header missing on 401")
|
|
||||||
}
|
|
||||||
if !strings.Contains(chall, realm) {
|
|
||||||
t.Errorf("WWW-Authenticate = %q, want realm %q", chall, realm)
|
|
||||||
}
|
|
||||||
} else if chall != "" {
|
|
||||||
t.Errorf("unexpected WWW-Authenticate header: %q", chall)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBasicAuthPassthrough(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
for _, tt := range []struct {
|
|
||||||
name string
|
|
||||||
username string
|
|
||||||
password string
|
|
||||||
}{
|
|
||||||
{name: "empty username", username: "", password: "hunter2"},
|
|
||||||
{name: "empty password", username: "admin", password: ""},
|
|
||||||
{name: "both empty", username: "", password: ""},
|
|
||||||
} {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
t.Parallel()
|
|
||||||
|
|
||||||
h := BasicAuth("realm", tt.username, tt.password, okHandler())
|
|
||||||
|
|
||||||
req := httptest.NewRequest(http.MethodGet, "/", nil)
|
|
||||||
rec := httptest.NewRecorder()
|
|
||||||
h.ServeHTTP(rec, req)
|
|
||||||
|
|
||||||
if rec.Code != http.StatusOK {
|
|
||||||
t.Errorf("status = %d, want %d (passthrough expected)", rec.Code, http.StatusOK)
|
|
||||||
}
|
|
||||||
if rec.Body.String() != "ok" {
|
|
||||||
t.Errorf("body = %q, want %q", rec.Body.String(), "ok")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -76,6 +76,13 @@ type Impl struct {
|
|||||||
affirmation, body, title spintax.Spintax
|
affirmation, body, title spintax.Spintax
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Impl) incrementUA(ctx context.Context, userAgent string) int {
|
||||||
|
result, _ := i.uaWeight.Get(ctx, internal.SHA256sum(userAgent))
|
||||||
|
result++
|
||||||
|
i.uaWeight.Set(ctx, internal.SHA256sum(userAgent), result, time.Hour)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Impl) incrementNetwork(ctx context.Context, network string) int {
|
func (i *Impl) incrementNetwork(ctx context.Context, network string) int {
|
||||||
result, _ := i.networkWeight.Get(ctx, internal.SHA256sum(network))
|
result, _ := i.networkWeight.Get(ctx, internal.SHA256sum(network))
|
||||||
result++
|
result++
|
||||||
@@ -83,19 +90,20 @@ func (i *Impl) incrementNetwork(ctx context.Context, network string) int {
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Impl) CheckUA() checker.Impl {
|
||||||
|
return checker.Func(func(r *http.Request) (bool, error) {
|
||||||
|
result, _ := i.uaWeight.Get(r.Context(), internal.SHA256sum(r.UserAgent()))
|
||||||
|
if result >= 25 {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
func (i *Impl) CheckNetwork() checker.Impl {
|
func (i *Impl) CheckNetwork() checker.Impl {
|
||||||
return checker.Func(func(r *http.Request) (bool, error) {
|
return checker.Func(func(r *http.Request) (bool, error) {
|
||||||
realIP, _ := internal.RealIP(r)
|
result, _ := i.uaWeight.Get(r.Context(), internal.SHA256sum(r.UserAgent()))
|
||||||
if !realIP.IsValid() {
|
|
||||||
realIP = netip.MustParseAddr(r.Header.Get("X-Real-Ip"))
|
|
||||||
}
|
|
||||||
|
|
||||||
network, ok := internal.ClampIP(realIP)
|
|
||||||
if !ok {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result, _ := i.networkWeight.Get(r.Context(), internal.SHA256sum(network.String()))
|
|
||||||
if result >= 25 {
|
if result >= 25 {
|
||||||
return true, nil
|
return true, nil
|
||||||
}
|
}
|
||||||
@@ -156,6 +164,7 @@ func (i *Impl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
networkCount := i.incrementNetwork(r.Context(), network.String())
|
networkCount := i.incrementNetwork(r.Context(), network.String())
|
||||||
|
uaCount := i.incrementUA(r.Context(), r.UserAgent())
|
||||||
|
|
||||||
stage := r.PathValue("stage")
|
stage := r.PathValue("stage")
|
||||||
|
|
||||||
@@ -163,8 +172,8 @@ func (i *Impl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
lg.Debug("found new entrance point", "id", id, "stage", stage, "userAgent", r.UserAgent(), "clampedIP", network)
|
lg.Debug("found new entrance point", "id", id, "stage", stage, "userAgent", r.UserAgent(), "clampedIP", network)
|
||||||
} else {
|
} else {
|
||||||
switch {
|
switch {
|
||||||
case networkCount%256 == 0:
|
case networkCount%256 == 0, uaCount%256 == 0:
|
||||||
lg.Warn("found possible crawler", "id", id, "network", network, "userAgent", r.UserAgent())
|
lg.Warn("found possible crawler", "id", id, "network", network)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -190,6 +190,14 @@ func New(opts Options) (*Server, error) {
|
|||||||
},
|
},
|
||||||
Name: "honeypot/network",
|
Name: "honeypot/network",
|
||||||
},
|
},
|
||||||
|
policy.Bot{
|
||||||
|
Rules: mazeGen.CheckUA(),
|
||||||
|
Action: config.RuleWeigh,
|
||||||
|
Weight: &config.Weight{
|
||||||
|
Adjust: 30,
|
||||||
|
},
|
||||||
|
Name: "honeypot/user-agent",
|
||||||
|
},
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
result.logger.Error("can't init honeypot subsystem", "err", err)
|
result.logger.Error("can't init honeypot subsystem", "err", err)
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ import (
|
|||||||
var (
|
var (
|
||||||
ErrInvalidMetricsConfig = errors.New("config: invalid metrics configuration")
|
ErrInvalidMetricsConfig = errors.New("config: invalid metrics configuration")
|
||||||
ErrInvalidMetricsTLSConfig = errors.New("config: invalid metrics TLS configuration")
|
ErrInvalidMetricsTLSConfig = errors.New("config: invalid metrics TLS configuration")
|
||||||
ErrInvalidMetricsBasicAuthConfig = errors.New("config: invalid metrics basic auth configuration")
|
|
||||||
ErrNoMetricsBind = errors.New("config.Metrics: must define bind")
|
ErrNoMetricsBind = errors.New("config.Metrics: must define bind")
|
||||||
ErrNoMetricsNetwork = errors.New("config.Metrics: must define network")
|
ErrNoMetricsNetwork = errors.New("config.Metrics: must define network")
|
||||||
ErrNoMetricsSocketMode = errors.New("config.Metrics: must define socket mode when using unix sockets")
|
ErrNoMetricsSocketMode = errors.New("config.Metrics: must define socket mode when using unix sockets")
|
||||||
@@ -23,8 +22,6 @@ var (
|
|||||||
ErrInvalidMetricsTLSKeypair = errors.New("config.Metrics.TLS: keypair is invalid")
|
ErrInvalidMetricsTLSKeypair = errors.New("config.Metrics.TLS: keypair is invalid")
|
||||||
ErrInvalidMetricsCACertificate = errors.New("config.Metrics.TLS: invalid CA certificate")
|
ErrInvalidMetricsCACertificate = errors.New("config.Metrics.TLS: invalid CA certificate")
|
||||||
ErrCantReadFile = errors.New("config: can't read required file")
|
ErrCantReadFile = errors.New("config: can't read required file")
|
||||||
ErrNoMetricsBasicAuthUsername = errors.New("config.Metrics.BasicAuth: must define username")
|
|
||||||
ErrNoMetricsBasicAuthPassword = errors.New("config.Metrics.BasicAuth: must define password")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type Metrics struct {
|
type Metrics struct {
|
||||||
@@ -32,7 +29,6 @@ type Metrics struct {
|
|||||||
Network string `json:"network" yaml:"network"`
|
Network string `json:"network" yaml:"network"`
|
||||||
SocketMode string `json:"socketMode" yaml:"socketMode"`
|
SocketMode string `json:"socketMode" yaml:"socketMode"`
|
||||||
TLS *MetricsTLS `json:"tls" yaml:"tls"`
|
TLS *MetricsTLS `json:"tls" yaml:"tls"`
|
||||||
BasicAuth *MetricsBasicAuth `json:"basicAuth" yaml:"basicAuth"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Metrics) Valid() error {
|
func (m *Metrics) Valid() error {
|
||||||
@@ -66,12 +62,6 @@ func (m *Metrics) Valid() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if m.BasicAuth != nil {
|
|
||||||
if err := m.BasicAuth.Valid(); err != nil {
|
|
||||||
errs = append(errs, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) != 0 {
|
if len(errs) != 0 {
|
||||||
return errors.Join(ErrInvalidMetricsConfig, errors.Join(errs...))
|
return errors.Join(ErrInvalidMetricsConfig, errors.Join(errs...))
|
||||||
}
|
}
|
||||||
@@ -141,26 +131,3 @@ func canReadFile(fname string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type MetricsBasicAuth struct {
|
|
||||||
Username string `json:"username" yaml:"username"`
|
|
||||||
Password string `json:"password" yaml:"password"`
|
|
||||||
}
|
|
||||||
|
|
||||||
func (mba *MetricsBasicAuth) Valid() error {
|
|
||||||
var errs []error
|
|
||||||
|
|
||||||
if mba.Username == "" {
|
|
||||||
errs = append(errs, ErrNoMetricsBasicAuthUsername)
|
|
||||||
}
|
|
||||||
|
|
||||||
if mba.Password == "" {
|
|
||||||
errs = append(errs, ErrNoMetricsBasicAuthPassword)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(errs) != 0 {
|
|
||||||
return errors.Join(ErrInvalidMetricsBasicAuthConfig, errors.Join(errs...))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -157,79 +157,6 @@ func TestMetricsValid(t *testing.T) {
|
|||||||
},
|
},
|
||||||
err: ErrInvalidMetricsCACertificate,
|
err: ErrInvalidMetricsCACertificate,
|
||||||
},
|
},
|
||||||
{
|
|
||||||
name: "basic auth credentials set",
|
|
||||||
input: &Metrics{
|
|
||||||
Bind: ":9090",
|
|
||||||
Network: "tcp",
|
|
||||||
BasicAuth: &MetricsBasicAuth{
|
|
||||||
Username: "admin",
|
|
||||||
Password: "hunter2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid basic auth config",
|
|
||||||
input: &Metrics{
|
|
||||||
Bind: ":9090",
|
|
||||||
Network: "tcp",
|
|
||||||
BasicAuth: &MetricsBasicAuth{},
|
|
||||||
},
|
|
||||||
err: ErrInvalidMetricsBasicAuthConfig,
|
|
||||||
},
|
|
||||||
} {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
if err := tt.input.Valid(); !errors.Is(err, tt.err) {
|
|
||||||
t.Logf("wanted error: %v", tt.err)
|
|
||||||
t.Logf("got error: %v", err)
|
|
||||||
t.Error("validation failed")
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestMetricsBasicAuthValid(t *testing.T) {
|
|
||||||
for _, tt := range []struct {
|
|
||||||
name string
|
|
||||||
input *MetricsBasicAuth
|
|
||||||
err error
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "both set",
|
|
||||||
input: &MetricsBasicAuth{
|
|
||||||
Username: "admin",
|
|
||||||
Password: "hunter2",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty username and password",
|
|
||||||
input: &MetricsBasicAuth{},
|
|
||||||
err: ErrInvalidMetricsBasicAuthConfig,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing username",
|
|
||||||
input: &MetricsBasicAuth{
|
|
||||||
Password: "hunter2",
|
|
||||||
},
|
|
||||||
err: ErrNoMetricsBasicAuthUsername,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing password",
|
|
||||||
input: &MetricsBasicAuth{
|
|
||||||
Username: "admin",
|
|
||||||
},
|
|
||||||
err: ErrNoMetricsBasicAuthPassword,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing both surfaces wrapper error",
|
|
||||||
input: &MetricsBasicAuth{},
|
|
||||||
err: ErrNoMetricsBasicAuthUsername,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "missing both surfaces password error",
|
|
||||||
input: &MetricsBasicAuth{},
|
|
||||||
err: ErrNoMetricsBasicAuthPassword,
|
|
||||||
},
|
|
||||||
} {
|
} {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
if err := tt.input.Valid(); !errors.Is(err, tt.err) {
|
if err := tt.input.Valid(); !errors.Is(err, tt.err) {
|
||||||
|
|||||||
@@ -97,13 +97,6 @@ func (s *Server) run(ctx context.Context, lg *slog.Logger) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Config.BasicAuth != nil {
|
|
||||||
var h http.Handler = mux
|
|
||||||
h = internal.BasicAuth("anubis-metrics", s.Config.BasicAuth.Username, s.Config.BasicAuth.Password, mux)
|
|
||||||
|
|
||||||
srv.Handler = h
|
|
||||||
}
|
|
||||||
|
|
||||||
lg.Debug("listening for metrics", "url", metricsURL)
|
lg.Debug("listening for metrics", "url", metricsURL)
|
||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ func (c *Client) GeoIPCheckerFor(countries []string) checker.Impl {
|
|||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
fmt.Fprintln(&sb, "GeoIPChecker")
|
fmt.Fprintln(&sb, "GeoIPChecker")
|
||||||
for _, cc := range countries {
|
for _, cc := range countries {
|
||||||
countryMap[strings.ToLower(cc)] = struct{}{}
|
countryMap[cc] = struct{}{}
|
||||||
fmt.Fprintln(&sb, cc)
|
fmt.Fprintln(&sb, cc)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user