Compare commits

..

9 Commits

Author SHA1 Message Date
Xe Iaso 9b3cce4d4f chore: spelling
Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-04-22 19:43:44 -04:00
Xe Iaso 56ec19d2da doc: document metrics TLS and mTLS
Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-04-22 19:41:18 -04:00
Xe Iaso 11ab78ec33 doc(default-config): document how to set up TLS and mTLS
Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-04-22 19:41:01 -04:00
Xe Iaso 11f944128f feat(metrics): enable mTLS support
Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-04-22 19:40:21 -04:00
Xe Iaso dfeb02b4ae feat(config): add CA certificate config value
Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-04-22 19:32:11 -04:00
Xe Iaso b66630df74 fix(metrics): properly surface errors with the metrics server
Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-04-22 19:15:22 -04:00
Xe Iaso 63e6a15280 feat(metrics): import keypairreloader from a private project
Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-04-22 13:52:40 -04:00
Xe Iaso 888c477933 feat(metrics): add naive TLS serving for metrics
Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-04-22 10:38:46 -04:00
Xe Iaso cda06f8c71 feat(config): add metrics TLS configuration
Signed-off-by: Xe Iaso <me@xeiaso.net>
2026-04-22 10:36:32 -04:00
22 changed files with 63 additions and 378 deletions
-1
View File
@@ -38,4 +38,3 @@ Samsung
wenet
qwertiko
setuplistener
mba
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y build-essential
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24.11.0"
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
+1 -1
View File
@@ -26,7 +26,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y build-essential
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24.11.0"
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
+1 -1
View File
@@ -36,7 +36,7 @@ jobs:
run: |
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:
node-version: "24.11.0"
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
+3 -3
View File
@@ -42,7 +42,7 @@ jobs:
- name: Build and push
id: build
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
context: ./docs
cache-to: type=gha
@@ -53,14 +53,14 @@ jobs:
push: true
- name: Apply k8s manifests to limsa lominsa
uses: actions-hub/kubectl@f8645c756533365a9fc1ae9aad8980b2a892d2c2 # v1.36.0
uses: actions-hub/kubectl@934aaa4354bbbc3d2176ae8d7cae92d515032dff # v1.35.3
env:
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
with:
args: apply -k docs/manifest
- name: Apply k8s manifests to limsa lominsa
uses: actions-hub/kubectl@f8645c756533365a9fc1ae9aad8980b2a892d2c2 # v1.36.0
uses: actions-hub/kubectl@934aaa4354bbbc3d2176ae8d7cae92d515032dff # v1.35.3
env:
KUBE_CONFIG: ${{ secrets.LIMSA_LOMINSA_KUBECONFIG }}
with:
+1 -1
View File
@@ -31,7 +31,7 @@ jobs:
- name: Build and push
id: build
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f # v7.1.0
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7.0.0
with:
context: ./docs
cache-to: type=gha
+2 -2
View File
@@ -24,7 +24,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y build-essential
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24.11.0"
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
@@ -32,7 +32,7 @@ jobs:
go-version: "stable"
- name: Cache playwright binaries
uses: actions/cache@27d5ce7f107fe9357f9df03efb73ab90386fccae # v5.0.5
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
id: playwright-cache
with:
path: |
+1 -1
View File
@@ -25,7 +25,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y build-essential
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24.11.0"
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
@@ -26,7 +26,7 @@ jobs:
sudo apt-get update
sudo apt-get install -y build-essential
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24.11.0"
- uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
@@ -41,7 +41,7 @@ jobs:
run: |
go tool yeet
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: packages
path: var/*
+2 -2
View File
@@ -34,7 +34,7 @@ jobs:
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
- uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
with:
node-version: "24.11.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
- name: Upload artifact
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
if: always()
with:
name: ${{ env.ARTIFACT_NAME }}
+2 -2
View File
@@ -21,7 +21,7 @@ jobs:
persist-credentials: false
- 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 🌈
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@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v4.35.2
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
sarif_file: results.sarif
category: zizmor
-7
View File
@@ -175,13 +175,6 @@ status_codes:
# bind: ":9090"
# 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
# # here. When the files change on disk, they will automatically be reloaded.
# #
-3
View File
@@ -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
- 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))
- 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)).
- 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
-18
View File
@@ -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.
### 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
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.
-52
View File
@@ -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)
}
-138
View File
@@ -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")
}
})
}
}
+22 -13
View File
@@ -76,6 +76,13 @@ type Impl struct {
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 {
result, _ := i.networkWeight.Get(ctx, internal.SHA256sum(network))
result++
@@ -83,19 +90,20 @@ func (i *Impl) incrementNetwork(ctx context.Context, network string) int {
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 {
return checker.Func(func(r *http.Request) (bool, error) {
realIP, _ := internal.RealIP(r)
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()))
result, _ := i.uaWeight.Get(r.Context(), internal.SHA256sum(r.UserAgent()))
if result >= 25 {
return true, nil
}
@@ -156,6 +164,7 @@ func (i *Impl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
}
networkCount := i.incrementNetwork(r.Context(), network.String())
uaCount := i.incrementUA(r.Context(), r.UserAgent())
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)
} else {
switch {
case networkCount%256 == 0:
lg.Warn("found possible crawler", "id", id, "network", network, "userAgent", r.UserAgent())
case networkCount%256 == 0, uaCount%256 == 0:
lg.Warn("found possible crawler", "id", id, "network", network)
}
}
+8
View File
@@ -190,6 +190,14 @@ func New(opts Options) (*Server, error) {
},
Name: "honeypot/network",
},
policy.Bot{
Rules: mazeGen.CheckUA(),
Action: config.RuleWeigh,
Weight: &config.Weight{
Adjust: 30,
},
Name: "honeypot/user-agent",
},
)
} else {
result.logger.Error("can't init honeypot subsystem", "err", err)
+16 -49
View File
@@ -10,29 +10,25 @@ import (
)
var (
ErrInvalidMetricsConfig = errors.New("config: invalid metrics 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")
ErrNoMetricsNetwork = errors.New("config.Metrics: must define network")
ErrNoMetricsSocketMode = errors.New("config.Metrics: must define socket mode when using unix sockets")
ErrInvalidMetricsSocketMode = errors.New("config.Metrics: invalid unix socket mode")
ErrInvalidMetricsNetwork = errors.New("config.Metrics: invalid metrics network")
ErrNoMetricsTLSCertificate = errors.New("config.Metrics.TLS: must define certificate file")
ErrNoMetricsTLSKey = errors.New("config.Metrics.TLS: must define key file")
ErrInvalidMetricsTLSKeypair = errors.New("config.Metrics.TLS: keypair is invalid")
ErrInvalidMetricsCACertificate = errors.New("config.Metrics.TLS: invalid CA certificate")
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")
ErrInvalidMetricsConfig = errors.New("config: invalid metrics configuration")
ErrInvalidMetricsTLSConfig = errors.New("config: invalid metrics TLS configuration")
ErrNoMetricsBind = errors.New("config.Metrics: must define bind")
ErrNoMetricsNetwork = errors.New("config.Metrics: must define network")
ErrNoMetricsSocketMode = errors.New("config.Metrics: must define socket mode when using unix sockets")
ErrInvalidMetricsSocketMode = errors.New("config.Metrics: invalid unix socket mode")
ErrInvalidMetricsNetwork = errors.New("config.Metrics: invalid metrics network")
ErrNoMetricsTLSCertificate = errors.New("config.Metrics.TLS: must define certificate file")
ErrNoMetricsTLSKey = errors.New("config.Metrics.TLS: must define key file")
ErrInvalidMetricsTLSKeypair = errors.New("config.Metrics.TLS: keypair is invalid")
ErrInvalidMetricsCACertificate = errors.New("config.Metrics.TLS: invalid CA certificate")
ErrCantReadFile = errors.New("config: can't read required file")
)
type Metrics struct {
Bind string `json:"bind" yaml:"bind"`
Network string `json:"network" yaml:"network"`
SocketMode string `json:"socketMode" yaml:"socketMode"`
TLS *MetricsTLS `json:"tls" yaml:"tls"`
BasicAuth *MetricsBasicAuth `json:"basicAuth" yaml:"basicAuth"`
Bind string `json:"bind" yaml:"bind"`
Network string `json:"network" yaml:"network"`
SocketMode string `json:"socketMode" yaml:"socketMode"`
TLS *MetricsTLS `json:"tls" yaml:"tls"`
}
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 {
return errors.Join(ErrInvalidMetricsConfig, errors.Join(errs...))
}
@@ -141,26 +131,3 @@ func canReadFile(fname string) error {
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
}
-73
View File
@@ -157,79 +157,6 @@ func TestMetricsValid(t *testing.T) {
},
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) {
if err := tt.input.Valid(); !errors.Is(err, tt.err) {
-7
View File
@@ -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)
go func() {
+1 -1
View File
@@ -18,7 +18,7 @@ func (c *Client) GeoIPCheckerFor(countries []string) checker.Impl {
var sb strings.Builder
fmt.Fprintln(&sb, "GeoIPChecker")
for _, cc := range countries {
countryMap[strings.ToLower(cc)] = struct{}{}
countryMap[cc] = struct{}{}
fmt.Fprintln(&sb, cc)
}