mirror of
https://github.com/TecharoHQ/anubis.git
synced 2026-05-21 21:47:48 +00:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8480175eac | |||
| c082cd89dc | |||
| 03bf695dff | |||
| 51ae340a7b | |||
| 430e262c84 | |||
| a47efe31b0 | |||
| 763c896b63 | |||
| a426230698 | |||
| 6c3fc188fb | |||
| a0589d3c7a |
@@ -318,7 +318,6 @@ screenshots
|
||||
searchbot
|
||||
searx
|
||||
sebest
|
||||
seccomp
|
||||
secretplans
|
||||
selfsigned
|
||||
Semrush
|
||||
|
||||
@@ -52,15 +52,6 @@ jobs:
|
||||
platforms: linux/amd64
|
||||
push: true
|
||||
|
||||
- name: Pin docs image to built digest
|
||||
working-directory: docs/manifest
|
||||
env:
|
||||
DIGEST: ${{ steps.build.outputs.digest }}
|
||||
run: |
|
||||
KUSTOMIZE_VERSION=v5.4.3
|
||||
curl -fsSL "https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize/${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_amd64.tar.gz" | tar -xz
|
||||
./kustomize edit set image "ghcr.io/techarohq/anubis/docs=ghcr.io/techarohq/anubis/docs@${DIGEST}"
|
||||
|
||||
- name: Apply k8s manifests to limsa lominsa
|
||||
uses: actions-hub/kubectl@934aaa4354bbbc3d2176ae8d7cae92d515032dff # v1.35.3
|
||||
env:
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
apiVersion: tekton.dev/v1
|
||||
kind: PipelineRun
|
||||
metadata:
|
||||
generateName: anubis-m-
|
||||
namespace: ci
|
||||
|
||||
spec:
|
||||
params:
|
||||
- name: commit
|
||||
value: "Xe/tekton"
|
||||
- name: branch
|
||||
value: main
|
||||
pipelineRef:
|
||||
name: anubis-build-test
|
||||
taskRunTemplate:
|
||||
serviceAccountName: anubis-k3k
|
||||
timeouts:
|
||||
pipeline: 1h0m0s
|
||||
workspaces:
|
||||
- name: repo
|
||||
volumeClaimTemplate:
|
||||
spec:
|
||||
accessModes: ["ReadWriteOnce"]
|
||||
resources:
|
||||
requests:
|
||||
storage: 4Gi
|
||||
- name: go-mod-cache
|
||||
persistentVolumeClaim:
|
||||
claimName: go-mod-cache
|
||||
- name: dockerconfig-atcr
|
||||
secret:
|
||||
secretName: atcr
|
||||
- name: dockerconfig-ghcr
|
||||
secret:
|
||||
secretName: ghcr
|
||||
@@ -0,0 +1,217 @@
|
||||
apiVersion: tekton.dev/v1beta1
|
||||
kind: Pipeline
|
||||
metadata:
|
||||
name: anubis-build-test
|
||||
namespace: ci
|
||||
|
||||
spec:
|
||||
description: |
|
||||
The CI/CD pipeline for Anubis
|
||||
params:
|
||||
- name: repo-url
|
||||
type: string
|
||||
description: "Git repo to clone"
|
||||
default: "https://github.com/TecharoHQ/anubis"
|
||||
- name: "branch"
|
||||
type: string
|
||||
description: "Git branch to operate against"
|
||||
- name: "commit"
|
||||
type: string
|
||||
description: "Git revision to check out"
|
||||
- name: "actor"
|
||||
type: string
|
||||
description: "Tangled actor"
|
||||
default: "did:web:anubis.techaro.lol"
|
||||
- name: docker-image-base
|
||||
type: string
|
||||
description: string prefix for production docker images
|
||||
default: "registry.int.xeserv.us/techarohq"
|
||||
- name: docker-cache
|
||||
type: string
|
||||
description: docker repo to store cache files
|
||||
default: "registry.int.xeserv.us/techarohq/anubis/cache"
|
||||
- name: go-version
|
||||
type: string
|
||||
description: "Go version to use"
|
||||
default: "1.26.3"
|
||||
workspaces:
|
||||
- name: repo
|
||||
description: |
|
||||
Cloned repo files.
|
||||
- name: dockerconfig-atcr
|
||||
description: |
|
||||
Docker config for pushing images to atcr
|
||||
- name: dockerconfig-ghcr
|
||||
description: |
|
||||
Docker config for pushing images to ghcr
|
||||
tasks:
|
||||
- name: fix-permissions
|
||||
taskRef:
|
||||
name: fix-permissions
|
||||
workspaces:
|
||||
- name: dir
|
||||
workspace: repo
|
||||
- name: clone-repo
|
||||
runAfter: ["fix-permissions"]
|
||||
taskRef:
|
||||
name: git-clone-naive
|
||||
workspaces:
|
||||
- name: output
|
||||
workspace: repo
|
||||
params:
|
||||
- name: url
|
||||
value: $(params.repo-url)
|
||||
- name: revision
|
||||
value: $(params.commit)
|
||||
- name: docker-build-ci
|
||||
runAfter: ["clone-repo"]
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: repo
|
||||
taskRef:
|
||||
name: kaniko
|
||||
params:
|
||||
- name: IMAGE
|
||||
value: $(params.docker-image-base)/anubis/ci:$(tasks.clone-repo.results.version)
|
||||
- name: DOCKERFILE
|
||||
value: ./test/ssh-ci/Dockerfile
|
||||
- name: EXTRA_ARGS
|
||||
value:
|
||||
[
|
||||
"--build-arg=GO_VERSION=$(params.go-version)",
|
||||
"--cache",
|
||||
"--cache-copy-layers",
|
||||
"--cache-run-layers",
|
||||
"--cache-repo=$(params.docker-cache)",
|
||||
"--label=org.tangled.actor=$(params.actor)",
|
||||
"--snapshot-mode=redo",
|
||||
"--use-new-run",
|
||||
]
|
||||
- name: provision-test-cluster
|
||||
runAfter: ["docker-build-ci"]
|
||||
taskSpec:
|
||||
workspaces:
|
||||
- name: repo
|
||||
mountPath: /src
|
||||
results:
|
||||
- name: cluster-name
|
||||
description: "k3k cluster name object in k8s"
|
||||
steps:
|
||||
- name: create-cluster
|
||||
image: $(tasks.docker-build-ci.results.IMAGE_URL)@$(tasks.docker-build-ci.results.IMAGE_DIGEST)
|
||||
workingDir: $(workspaces.repo.path)/repo
|
||||
env:
|
||||
- name: NAMESPACE
|
||||
value: $(context.pipelineRun.namespace)
|
||||
- name: PIPELINE_NAME
|
||||
value: $(context.pipeline.name)
|
||||
- name: PIPELINERUN_NAME
|
||||
value: $(context.pipelineRun.name)
|
||||
- name: PIPELINERUN_UID
|
||||
value: $(context.pipelineRun.uid)
|
||||
- name: KUBECONFIG_OUT
|
||||
value: $(workspaces.repo.path)/kube/config
|
||||
script: |
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
./test/k3k/create-cluster.sh > "$(results.cluster-name.path)"
|
||||
- name: build-assets
|
||||
runAfter: ["docker-build-ci"]
|
||||
taskSpec:
|
||||
workspaces:
|
||||
- name: repo
|
||||
mountPath: /src
|
||||
steps:
|
||||
- name: test
|
||||
image: $(tasks.docker-build-ci.results.IMAGE_URL)@$(tasks.docker-build-ci.results.IMAGE_DIGEST)
|
||||
workingDir: $(workspaces.repo.path)/repo
|
||||
script: |
|
||||
npm ci
|
||||
npm run assets
|
||||
workspaces:
|
||||
- name: repo
|
||||
workspace: repo
|
||||
- name: go-test
|
||||
runAfter: ["build-assets"]
|
||||
taskSpec:
|
||||
workspaces:
|
||||
- name: repo
|
||||
mountPath: /src
|
||||
steps:
|
||||
- name: test
|
||||
image: $(tasks.docker-build-ci.results.IMAGE_URL)@$(tasks.docker-build-ci.results.IMAGE_DIGEST)
|
||||
workingDir: $(workspaces.repo.path)/repo
|
||||
script: |
|
||||
SKIP_INTEGRATION=1 go test ./...
|
||||
workspaces:
|
||||
- name: repo
|
||||
workspace: repo
|
||||
- name: test-anubis
|
||||
runAfter: ["build-assets"]
|
||||
taskRef:
|
||||
name: ko
|
||||
workspaces:
|
||||
- name: source
|
||||
workspace: repo
|
||||
params:
|
||||
- name: VERSION
|
||||
value: $(tasks.clone-repo.results.version)
|
||||
- name: SOURCE_DATE_EPOCH
|
||||
value: $(tasks.clone-repo.results.source-date-epoch)
|
||||
- name: KO_DOCKER_REPO
|
||||
value: $(params.docker-image-base)
|
||||
- name: extra-args
|
||||
value:
|
||||
[
|
||||
"--platform=all",
|
||||
"--base-import-paths",
|
||||
"--tags=$(tasks.clone-repo.results.version)",
|
||||
"--image-label=org.tangled.actor=$(params.actor)",
|
||||
]
|
||||
- name: packages
|
||||
value:
|
||||
- ./cmd/anubis
|
||||
- name: integration
|
||||
runAfter:
|
||||
- "provision-test-cluster"
|
||||
- "build-assets"
|
||||
- "test-anubis"
|
||||
matrix:
|
||||
params:
|
||||
- name: test-case
|
||||
value:
|
||||
- default-config-macro
|
||||
- i18n
|
||||
- robots_txt
|
||||
taskSpec:
|
||||
params:
|
||||
- name: test-case
|
||||
type: string
|
||||
workspaces:
|
||||
- name: repo
|
||||
mountPath: /src
|
||||
steps:
|
||||
- name: exec
|
||||
image: $(tasks.docker-build-ci.results.IMAGE_URL)@$(tasks.docker-build-ci.results.IMAGE_DIGEST)
|
||||
workingDir: $(workspaces.repo.path)/repo/test/$(params.test-case)
|
||||
script: |
|
||||
./tekton.sh
|
||||
env:
|
||||
- name: KUBECONFIG
|
||||
value: "$(workspaces.repo.path)/kube/config"
|
||||
finally:
|
||||
- name: teardown-cluster
|
||||
when:
|
||||
- input: "$(tasks.provision-test-cluster.status)"
|
||||
operator: in
|
||||
values: ["Succeeded"]
|
||||
taskSpec:
|
||||
workspaces:
|
||||
- name: repo
|
||||
mountPath: /src
|
||||
steps:
|
||||
- name: delete
|
||||
image: $(tasks.docker-build-ci.results.IMAGE_URL)@$(tasks.docker-build-ci.results.IMAGE_DIGEST)
|
||||
workingDir: $(workspaces.repo.path)/repo
|
||||
script: |
|
||||
kubectl delete --ignore-not-found -n $(context.pipelineRun.namespace) clusters.k3k.io/"$(tasks.provision-test-cluster.results.cluster-name)"
|
||||
@@ -0,0 +1,4 @@
|
||||
namespace: ci
|
||||
resources:
|
||||
- anubis-test.yaml
|
||||
- rbac.yaml
|
||||
@@ -0,0 +1,32 @@
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: anubis-k3k
|
||||
namespace: ci
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: anubis-k3k
|
||||
namespace: ci
|
||||
rules:
|
||||
- apiGroups: ["k3k.io"]
|
||||
resources: ["clusters"]
|
||||
verbs: ["*"]
|
||||
- apiGroups: [""]
|
||||
resources: ["secrets"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: anubis-k3k
|
||||
namespace: ci
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: anubis-k3k
|
||||
namespace: ci
|
||||
roleRef:
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
kind: Role
|
||||
name: anubis-k3k
|
||||
+2
-2
@@ -1,11 +1,11 @@
|
||||
FROM docker.io/library/node:22.22.3-alpine AS build
|
||||
FROM docker.io/library/node:lts AS build
|
||||
|
||||
WORKDIR /app
|
||||
COPY . .
|
||||
|
||||
RUN npm ci && npm run build
|
||||
|
||||
FROM ghcr.io/xe/nginx-micro:v1.29.0
|
||||
FROM ghcr.io/xe/nginx-micro
|
||||
COPY --from=build /app/build /www
|
||||
COPY ./manifest/cfg/nginx/nginx.conf /conf
|
||||
LABEL org.opencontainers.image.source="https://github.com/TecharoHQ/anubis"
|
||||
@@ -28,16 +28,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- 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).
|
||||
- Add config option to add ASN to logs/metrics.
|
||||
- Log weight when issuing challenge.
|
||||
- Gate pprof endpoints behind `metrics.debug` in the policy file.
|
||||
- Limit naive honeypot r9k delay to one second.
|
||||
- Fix an obscure case where adding query values to a subrequest match could cause an invalid rule match when using path based matching for protected resources.
|
||||
- Fix an edge case where load average expression values could nil pointer dereference when Anubis just started up.
|
||||
- Fix an obscure case where Anubis in subrequest mode could allow redirects to invalid domains with strange instructions.
|
||||
- Log weight when issuing challenge
|
||||
- Fix `path_regex` and CEL `path` rules not matching when using Traefik `forwardAuth` middleware. Anubis now checks `X-Forwarded-Uri` (Traefik) in addition to `X-Original-URI` (nginx) when resolving the request path in subrequest mode ([#1628](https://github.com/TecharoHQ/anubis/issues/1628)).
|
||||
- Validate bounds in the CEL `randInt` helper so non-positive or platform-overflowing arguments surface a typed CEL error instead of an evaluator panic.
|
||||
- Harden the public docs deployment: add a pod-level security context to the nginx container (non-root uid 101, dropped capabilities, read-only root filesystem, `RuntimeDefault` seccomp) and rebind it to unprivileged port `8080`.
|
||||
- Pin docs deployment images to immutable digests with `imagePullPolicy: IfNotPresent`, and have the docs-deploy workflow overlay the just-built digest via `kustomize edit set image` so each rollout references an auditable artifact instead of a floating `:main` tag. The docs `Dockerfile` now pins `node` and `nginx-micro` base images to specific versions.
|
||||
|
||||
## v1.25.0: Necron
|
||||
|
||||
|
||||
@@ -138,24 +138,6 @@ metrics:
|
||||
socketMode: "0700" # must be a string
|
||||
```
|
||||
|
||||
### Debug routes
|
||||
|
||||
Anubis' metrics server supports [pprof](https://pkg.go.dev/runtime/pprof), the Go standard library tool for profiling Go applications. This is very useful for debugging how Anubis works in the wild with regards to CPU, multicore, and RAM usage. pprof is very powerful and can expose command line arguments as part of the debugging setup (inside Google, everything is done with command line flags).
|
||||
|
||||
Prior versions of Anubis exposed pprof endpoints on all TCP bindhosts by default. This means that machines with incorrectly configured firewalls can expose command line arguments to the public internet in the right conditions.
|
||||
|
||||
In order to enable pprof profiling endpoints on the Metrics server, set the `debug` flag under the `metrics` block:
|
||||
|
||||
```yaml
|
||||
metrics:
|
||||
bind: ":9090"
|
||||
network: "tcp"
|
||||
|
||||
debug: true
|
||||
```
|
||||
|
||||
To err on the side of caution, this defaults to disabled. If this defaults migration breaks your configuration, please let us know in a ticket.
|
||||
|
||||
### TLS
|
||||
|
||||
If you want to serve the metrics server over TLS, use the `tls` block:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
user nginx;
|
||||
worker_processes 2;
|
||||
error_log /dev/stdout warn;
|
||||
pid /tmp/nginx.pid;
|
||||
pid /nginx.pid;
|
||||
|
||||
events {
|
||||
worker_connections 1024;
|
||||
@@ -12,17 +12,11 @@ http {
|
||||
default_type application/octet-stream;
|
||||
access_log /dev/stdout;
|
||||
|
||||
client_body_temp_path /tmp/client_body;
|
||||
proxy_temp_path /tmp/proxy;
|
||||
fastcgi_temp_path /tmp/fastcgi;
|
||||
uwsgi_temp_path /tmp/uwsgi;
|
||||
scgi_temp_path /tmp/scgi;
|
||||
|
||||
sendfile on;
|
||||
keepalive_timeout 65;
|
||||
|
||||
server {
|
||||
listen 8080 default_server;
|
||||
listen 80 default_server;
|
||||
server_name _;
|
||||
|
||||
error_page 404 /404.html;
|
||||
|
||||
@@ -20,12 +20,10 @@ spec:
|
||||
name: nginx-cfg
|
||||
- name: temporary-data
|
||||
emptyDir: {}
|
||||
- name: nginx-tmp
|
||||
emptyDir: {}
|
||||
containers:
|
||||
- name: anubis-docs
|
||||
image: ghcr.io/techarohq/anubis/docs@sha256:f13a7c862bbcba8e19feba9f157120c6f03e23b03367ace4ca55da69dc894e12
|
||||
imagePullPolicy: IfNotPresent
|
||||
image: ghcr.io/techarohq/anubis/docs:main
|
||||
imagePullPolicy: Always
|
||||
resources:
|
||||
limits:
|
||||
memory: "128Mi"
|
||||
@@ -36,36 +34,23 @@ spec:
|
||||
volumeMounts:
|
||||
- name: nginx
|
||||
mountPath: /conf
|
||||
- name: nginx-tmp
|
||||
mountPath: /tmp
|
||||
ports:
|
||||
- containerPort: 8080
|
||||
- containerPort: 80
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8080
|
||||
port: 80
|
||||
initialDelaySeconds: 1
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: 8080
|
||||
port: 80
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 20
|
||||
securityContext:
|
||||
runAsUser: 101
|
||||
runAsGroup: 101
|
||||
runAsNonRoot: true
|
||||
allowPrivilegeEscalation: false
|
||||
readOnlyRootFilesystem: true
|
||||
capabilities:
|
||||
drop:
|
||||
- ALL
|
||||
seccompProfile:
|
||||
type: RuntimeDefault
|
||||
- name: anubis
|
||||
image: ghcr.io/techarohq/anubis@sha256:533e57956ae3afd1612dab16f02dd4db937ca14fad5399208f403686e14feed5
|
||||
imagePullPolicy: IfNotPresent
|
||||
image: ghcr.io/techarohq/anubis:main
|
||||
imagePullPolicy: Always
|
||||
env:
|
||||
- name: "BIND"
|
||||
value: ":8081"
|
||||
@@ -80,7 +65,7 @@ spec:
|
||||
- name: "SERVE_ROBOTS_TXT"
|
||||
value: "false"
|
||||
- name: "TARGET"
|
||||
value: "http://localhost:8080"
|
||||
value: "http://localhost:80"
|
||||
# - name: "SLOG_LEVEL"
|
||||
# value: "debug"
|
||||
volumeMounts:
|
||||
|
||||
@@ -7,7 +7,7 @@ spec:
|
||||
app: anubis-docs
|
||||
ports:
|
||||
- port: 80
|
||||
targetPort: 8080
|
||||
targetPort: 80
|
||||
name: http
|
||||
- port: 8081
|
||||
targetPort: 8081
|
||||
|
||||
@@ -169,7 +169,7 @@ func (i *Impl) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
}
|
||||
|
||||
millisecondAmount := min(math.Pow(float64(networkCount), 2), 1000)
|
||||
millisecondAmount := math.Pow(float64(networkCount), 2)
|
||||
time.Sleep(time.Duration(millisecondAmount) * time.Millisecond)
|
||||
|
||||
spins := i.makeSpins()
|
||||
|
||||
@@ -32,7 +32,6 @@ type Metrics struct {
|
||||
Network string `json:"network" yaml:"network"`
|
||||
SocketMode string `json:"socketMode" yaml:"socketMode"`
|
||||
TLS *MetricsTLS `json:"tls" yaml:"tls"`
|
||||
Debug bool `json:"debug" yaml:"debug"`
|
||||
BasicAuth *MetricsBasicAuth `json:"basicAuth" yaml:"basicAuth"`
|
||||
}
|
||||
|
||||
|
||||
+7
-8
@@ -403,15 +403,14 @@ func (s *Server) ServeHTTPNext(w http.ResponseWriter, r *http.Request) {
|
||||
localizer := localization.GetLocalizer(r)
|
||||
|
||||
redir := r.FormValue("redir")
|
||||
urlParsed, err := url.Parse(redir)
|
||||
urlParsed, err := url.ParseRequestURI(redir)
|
||||
if err != nil {
|
||||
s.respondWithStatus(w, r, localizer.T("redirect_not_parseable"), makeCode(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
if urlParsed.Opaque != "" || (urlParsed.Scheme == "" && strings.HasPrefix(redir, "//")) {
|
||||
s.respondWithStatus(w, r, localizer.T("invalid_redirect"), "", http.StatusBadRequest)
|
||||
return
|
||||
// if ParseRequestURI fails, try as relative URL
|
||||
urlParsed, err = r.URL.Parse(redir)
|
||||
if err != nil {
|
||||
s.respondWithStatus(w, r, localizer.T("redirect_not_parseable"), makeCode(err), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// validate URL scheme to prevent javascript:, data:, file:, tel:, etc.
|
||||
|
||||
@@ -223,17 +223,3 @@ func TestNoCacheOnError(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectsHostlessRedirect(t *testing.T) {
|
||||
pol := loadPolicies(t, "testdata/useragent.yaml", 0)
|
||||
srv := spawnAnubis(t, Options{Policy: pol, RedirectDomains: []string{"allowed.example"}})
|
||||
req := httptest.NewRequest(http.MethodGet, "https://anubis.example/.within.website/?redir=%2f%2fevil.example%2fphish", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
srv.ServeHTTPNext(rr, req)
|
||||
if rr.Code != http.StatusBadRequest {
|
||||
t.Fatalf("expected hostless redirect to be rejected, got HTTP %d body %q", rr.Code, rr.Body.String())
|
||||
}
|
||||
if got := rr.Header().Get("Location"); got != "" {
|
||||
t.Fatalf("expected no Location header on rejected redirect, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,15 +34,11 @@ func (s *Server) Run(ctx context.Context, done func()) {
|
||||
|
||||
func (s *Server) run(ctx context.Context, lg *slog.Logger) error {
|
||||
mux := http.NewServeMux()
|
||||
|
||||
if s.Config.Debug {
|
||||
mux.HandleFunc("GET /debug/pprof/", pprof.Index)
|
||||
mux.HandleFunc("GET /debug/pprof/cmdline", pprof.Cmdline)
|
||||
mux.HandleFunc("GET /debug/pprof/profile", pprof.Profile)
|
||||
mux.HandleFunc("GET /debug/pprof/symbol", pprof.Symbol)
|
||||
mux.HandleFunc("GET /debug/pprof/trace", pprof.Trace)
|
||||
}
|
||||
|
||||
mux.HandleFunc("GET /debug/pprof/", pprof.Index)
|
||||
mux.HandleFunc("GET /debug/pprof/cmdline", pprof.Cmdline)
|
||||
mux.HandleFunc("GET /debug/pprof/profile", pprof.Profile)
|
||||
mux.HandleFunc("GET /debug/pprof/symbol", pprof.Symbol)
|
||||
mux.HandleFunc("GET /debug/pprof/trace", pprof.Trace)
|
||||
mux.Handle("/metrics", promhttp.Handler())
|
||||
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
||||
st, ok := internal.GetHealth("anubis")
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package metrics
|
||||
|
||||
import (
|
||||
"context"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/TecharoHQ/anubis/lib/config"
|
||||
)
|
||||
|
||||
func TestMetricsPprofCmdlineExposedWithoutAuthentication(t *testing.T) {
|
||||
ln, err := net.Listen("tcp", "127.0.0.1:0")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
addr := ln.Addr().String()
|
||||
_ = ln.Close()
|
||||
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
defer cancel()
|
||||
done := make(chan struct{})
|
||||
srv := &Server{
|
||||
Config: &config.Metrics{Network: "tcp", Bind: addr},
|
||||
Log: slog.Default(),
|
||||
}
|
||||
go srv.Run(ctx, func() { close(done) })
|
||||
|
||||
url := "http://" + addr + "/debug/pprof/cmdline"
|
||||
var body []byte
|
||||
resp, err := http.Get(url)
|
||||
if err == nil {
|
||||
body, err = io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
t.Fatalf("can't read body: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
}
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
if strings.Contains(string(body), "metrics.test") {
|
||||
t.Fatalf("pprof is enabled by default, cmdline process arguments: %q", string(body))
|
||||
}
|
||||
cancel()
|
||||
<-done
|
||||
}
|
||||
@@ -5,7 +5,6 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@@ -115,9 +114,6 @@ func (pc *PathChecker) Check(r *http.Request) (bool, error) {
|
||||
originalUrl = r.Header.Get("X-Forwarded-Uri")
|
||||
}
|
||||
if originalUrl != "" {
|
||||
if parsed, err := url.ParseRequestURI(originalUrl); err == nil {
|
||||
originalUrl = parsed.Path
|
||||
}
|
||||
if pc.regexp.MatchString(originalUrl) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
@@ -222,16 +222,7 @@ func New(opts ...cel.EnvOption) (*cel.Env, error) {
|
||||
return types.ValOrErr(val, "value is not an integer, but is %T", val)
|
||||
}
|
||||
|
||||
if n <= 0 {
|
||||
return types.NewErr("randInt bound must be positive, got %d", int64(n))
|
||||
}
|
||||
|
||||
bound := int(n)
|
||||
if types.Int(bound) != n {
|
||||
return types.NewErr("randInt bound %d overflows platform int", int64(n))
|
||||
}
|
||||
|
||||
return types.Int(rand.IntN(bound))
|
||||
return types.Int(rand.IntN(int(n)))
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
@@ -9,7 +9,6 @@ import (
|
||||
|
||||
"github.com/TecharoHQ/anubis/internal/dns"
|
||||
"github.com/TecharoHQ/anubis/lib/store/memory"
|
||||
"github.com/google/cel-go/cel"
|
||||
"github.com/google/cel-go/common/types"
|
||||
"github.com/google/cel-go/common/types/ref"
|
||||
)
|
||||
@@ -689,14 +688,6 @@ func TestNewEnvironment(t *testing.T) {
|
||||
description: "should return values in correct range",
|
||||
shouldCompile: true,
|
||||
},
|
||||
{
|
||||
name: "randInt-large-bound",
|
||||
expression: `randInt(2147483647) >= 0`,
|
||||
variables: map[string]any{},
|
||||
expectBool: boolPtr(true),
|
||||
description: "should accept int32-max bounds without overflow",
|
||||
shouldCompile: true,
|
||||
},
|
||||
{
|
||||
name: "strings-extension-size",
|
||||
expression: `"hello".size() == 5`,
|
||||
@@ -759,65 +750,3 @@ func TestNewEnvironment(t *testing.T) {
|
||||
func boolPtr(b bool) *bool {
|
||||
return &b
|
||||
}
|
||||
|
||||
func TestRandIntInvalidBounds(t *testing.T) {
|
||||
env, err := New(cel.Variable("contentLength", cel.IntType))
|
||||
if err != nil {
|
||||
t.Fatalf("failed to create environment: %v", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expression string
|
||||
variables map[string]any
|
||||
wantErrText string
|
||||
description string
|
||||
}{
|
||||
{
|
||||
name: "zero-bound-literal",
|
||||
expression: `randInt(0)`,
|
||||
variables: map[string]any{},
|
||||
wantErrText: "randInt bound must be positive",
|
||||
description: "randInt(0) should return a CEL error, not panic",
|
||||
},
|
||||
{
|
||||
name: "negative-bound-literal",
|
||||
expression: `randInt(-5)`,
|
||||
variables: map[string]any{},
|
||||
wantErrText: "randInt bound must be positive",
|
||||
description: "randInt(-5) should return a CEL error, not panic",
|
||||
},
|
||||
{
|
||||
name: "zero-bound-from-variable",
|
||||
expression: `randInt(contentLength)`,
|
||||
variables: map[string]any{"contentLength": 0},
|
||||
wantErrText: "randInt bound must be positive",
|
||||
description: "attacker-controlled zero contentLength should error gracefully",
|
||||
},
|
||||
{
|
||||
name: "negative-bound-from-variable",
|
||||
expression: `randInt(contentLength)`,
|
||||
variables: map[string]any{"contentLength": -1},
|
||||
wantErrText: "randInt bound must be positive",
|
||||
description: "attacker-controlled negative contentLength should error gracefully",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
prog, err := Compile(env, tt.expression)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to compile expression %q: %v", tt.expression, err)
|
||||
}
|
||||
|
||||
result, _, err := prog.Eval(tt.variables)
|
||||
if err == nil {
|
||||
t.Fatalf("%s: expected an evaluation error, got result %v", tt.description, result)
|
||||
}
|
||||
|
||||
if !strings.Contains(err.Error(), tt.wantErrText) {
|
||||
t.Errorf("%s: expected error containing %q, got %q", tt.description, tt.wantErrText, err.Error())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
globalLoadAvg = &loadAvg{data: &load.AvgStat{}}
|
||||
globalLoadAvg = &loadAvg{}
|
||||
go globalLoadAvg.updateThread(context.Background())
|
||||
}
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package policy
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -87,27 +85,3 @@ func TestBadConfigs(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPathCheckerStripsForwardedURIQuery(t *testing.T) {
|
||||
checker, err := NewPathChecker("^/admin$", true)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
req := httptest.NewRequest(http.MethodGet, "https://anubis.local/.within.website/x/cmd/anubis/api/check", nil)
|
||||
req.Header.Set("X-Forwarded-Uri", "/admin?x=1")
|
||||
matched, err := checker.Check(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !matched {
|
||||
t.Fatalf("expected exact path checker to match forwarded URI when query string is appended")
|
||||
}
|
||||
req.Header.Set("X-Forwarded-Uri", "/admin")
|
||||
matched, err = checker.Check(req)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !matched {
|
||||
t.Fatalf("expected exact path checker to match forwarded URI without query string")
|
||||
}
|
||||
}
|
||||
|
||||
+3
-1
@@ -15,7 +15,9 @@
|
||||
"package": "go tool yeet",
|
||||
"lint": "make lint",
|
||||
"prepare": "husky && go mod download",
|
||||
"format": "prettier -w . 2>&1 >/dev/null && go run goimports -w ."
|
||||
"format": "prettier -w . 2>&1 >/dev/null && go run goimports -w .",
|
||||
"deploy:ci": "kubectl apply -k .tekton -n ci --context admin@alrest",
|
||||
"deploy:ci:invoke": "npm run deploy:ci && kubectl create -f .tekton/anubis-pipelinerun.yaml -n ci --context admin@alrest"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
.env
|
||||
Executable
+7
@@ -0,0 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
exec ./test.sh
|
||||
@@ -3,5 +3,10 @@
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
python3 -m venv .env
|
||||
source .env/bin/activate
|
||||
pip install pyyaml
|
||||
|
||||
python3 -c 'import yaml'
|
||||
python3 ./compare_bots.py
|
||||
python3 ./compare_bots.py
|
||||
|
||||
@@ -104,5 +104,6 @@ require (
|
||||
|
||||
tool (
|
||||
github.com/TecharoHQ/anubis/cmd/anubis
|
||||
github.com/TecharoHQ/anubis/utils/cmd/backoff-retry
|
||||
github.com/jsha/minica
|
||||
)
|
||||
|
||||
Executable
+20
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
function cleanup() {
|
||||
pkill -P $$
|
||||
}
|
||||
|
||||
trap cleanup EXIT SIGINT
|
||||
|
||||
go tool anubis --help 2>/dev/null || :
|
||||
|
||||
go run ../cmd/unixhttpd &
|
||||
|
||||
go tool anubis \
|
||||
--policy-fname ./anubis.yaml \
|
||||
--use-remote-address \
|
||||
--target=unix://$(pwd)/unixhttpd.sock &
|
||||
|
||||
go tool backoff-retry node ./test.mjs
|
||||
Executable
+49
@@ -0,0 +1,49 @@
|
||||
#!/usr/bin/env bash
|
||||
# Create a k3k cluster, wait for it to be Ready, and write its kubeconfig.
|
||||
# Prints the generated cluster name to stdout on success.
|
||||
#
|
||||
# Required env:
|
||||
# NAMESPACE Kubernetes namespace to create the cluster in
|
||||
# KUBECONFIG_OUT Path to write the resulting kubeconfig
|
||||
#
|
||||
# Optional env (set under Tekton to enable ownerReference-based GC + labels):
|
||||
# PIPELINE_NAME Tekton Pipeline name
|
||||
# PIPELINERUN_NAME Tekton PipelineRun name
|
||||
# PIPELINERUN_UID Tekton PipelineRun UID
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
: "${NAMESPACE:?NAMESPACE must be set}"
|
||||
: "${KUBECONFIG_OUT:?KUBECONFIG_OUT must be set}"
|
||||
|
||||
script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)
|
||||
|
||||
cluster_name=$(kubectl create -n "${NAMESPACE}" -f "${script_dir}/test-cluster.yaml" -ojson | jq -r '.metadata.name')
|
||||
|
||||
if [[ -n "${PIPELINERUN_NAME:-}" && -n "${PIPELINERUN_UID:-}" ]]; then
|
||||
owner_ref=$(jo \
|
||||
apiVersion=tekton.dev/v1 \
|
||||
kind=PipelineRun \
|
||||
name="${PIPELINERUN_NAME}" \
|
||||
uid="${PIPELINERUN_UID}" \
|
||||
blockOwnerDeletion=false)
|
||||
patch=$(jo metadata=$(jo "ownerReferences[]=${owner_ref}"))
|
||||
|
||||
kubectl patch -n "${NAMESPACE}" "clusters.k3k.io/${cluster_name}" --type=merge -p "${patch}" >&2
|
||||
|
||||
kubectl label -n "${NAMESPACE}" "clusters.k3k.io/${cluster_name}" \
|
||||
"tekton.dev/memberOf=tasks" \
|
||||
"tekton.dev/pipeline=${PIPELINE_NAME:-}" \
|
||||
"tekton.dev/pipelineRun=${PIPELINERUN_NAME}" \
|
||||
"tekton.dev/pipelineRunUID=${PIPELINERUN_UID}" >&2
|
||||
fi
|
||||
|
||||
kubectl wait --for=condition=Ready "clusters.k3k.io/${cluster_name}" -n "${NAMESPACE}" --timeout 5m >&2
|
||||
kubectl wait --for=create "secret/k3k-${cluster_name}-kubeconfig" -n "${NAMESPACE}" --timeout 5m >&2
|
||||
|
||||
mkdir -p "$(dirname "${KUBECONFIG_OUT}")"
|
||||
kubectl get -ojson -n "${NAMESPACE}" "secret/k3k-${cluster_name}-kubeconfig" \
|
||||
| jq -r '.data["kubeconfig.yaml"]' \
|
||||
| base64 -d > "${KUBECONFIG_OUT}"
|
||||
|
||||
echo "${cluster_name}"
|
||||
@@ -0,0 +1,5 @@
|
||||
apiVersion: k3k.io/v1beta1
|
||||
kind: Cluster
|
||||
metadata:
|
||||
generateName: anubis-test-
|
||||
namespace: ci
|
||||
Executable
+23
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
function cleanup() {
|
||||
pkill -P $$
|
||||
}
|
||||
|
||||
trap cleanup EXIT SIGINT
|
||||
|
||||
go tool anubis --help 2>/dev/null || :
|
||||
|
||||
go run ../cmd/unixhttpd &
|
||||
|
||||
go tool anubis \
|
||||
--policy-fname ./anubis.yaml \
|
||||
--use-remote-address \
|
||||
--serve-robots-txt \
|
||||
--target=unix://$(pwd)/unixhttpd.sock &
|
||||
|
||||
go tool backoff-retry node ./test.mjs
|
||||
+12
-2
@@ -1,5 +1,15 @@
|
||||
ARG ALPINE_VERSION=3.22
|
||||
ARG GO_VERSION=1.26.3
|
||||
|
||||
# Go toolchain bootstrapper
|
||||
FROM golang:${GO_VERSION} AS go
|
||||
|
||||
RUN CGO_ENABLED=0 go install golang.org/dl/go1.23.6@latest \
|
||||
&& mkdir -p /app/bin \
|
||||
&& mv /go/bin/go1.23.6 /app/bin/go
|
||||
|
||||
FROM alpine:${ALPINE_VERSION}
|
||||
RUN apk add -U go nodejs git build-base git npm bash zstd brotli gzip
|
||||
LABEL org.opencontainers.image.source="https://github.com/TecharoHQ/anubis"
|
||||
COPY --from=go /app/bin/go /usr/local/bin/go
|
||||
|
||||
RUN apk add -U nodejs git build-base git npm bash zstd brotli gzip jq jo kubectl python3 py3-pip py3-virtualenv \
|
||||
&& go download
|
||||
Reference in New Issue
Block a user