Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b09802967 |
@@ -162,6 +162,14 @@ public cloud metadata addresses: currently only `168.63.129.16`, Azure's
|
||||
WireServer, which serves an Azure VM its credentials. Because it is a
|
||||
public address, listing it in `ALLOWED_EGRESS_CIDRS` reopens it.
|
||||
|
||||
That is all the default blocklist covers: private and reserved space,
|
||||
plus public addresses that serve cloud credentials. A cloud provider's
|
||||
other services on public addresses are not refused — IBM Cloud's
|
||||
`161.26.0.0/16` and `166.8.0.0/14`, for example, which carry its DNS
|
||||
resolvers, time servers and package mirrors. They serve no credentials,
|
||||
reaching them can be a legitimate delivery, and every cloud has some, so
|
||||
a partial list would promise coverage it does not give.
|
||||
|
||||
That default is also inconvenient for the thing webhooker is mostly
|
||||
for: taking a public webhook and forwarding it to something on your own
|
||||
network. A container on the same Docker network, a box on `10.x`, a
|
||||
@@ -2721,7 +2729,7 @@ abuse limit later; they are tracked as future work.
|
||||
| ------ | --------------------------- | ----------- |
|
||||
| `GET` | `/` | Root redirect, 303 (authenticated → `/sources`, unauthenticated → `/pages/login`) |
|
||||
| `GET` | `/.well-known/healthcheck` | Health check (JSON: `status`, `now`, `uptimeSeconds`, `uptimeHuman`, `version`, `appname`, `maintenanceMode`) |
|
||||
| `GET`, `HEAD` | `/s/*` | Static file serving (embedded CSS, JS). `GET` and `HEAD` only — `POST`, `PUT`, `PATCH`, `DELETE`, `OPTIONS`, `TRACE` and `CONNECT` are answered `405 Method Not Allowed` with `Allow: GET, HEAD`. Any other method (such as `PROPFIND`) is refused by chi before it reaches this route, and gets `405` without an `Allow` header. Pinned by `TestStaticServesOnlyGetAndHead` |
|
||||
| any | `/s/*` | Static file serving (embedded CSS, JS). Mounted for every method, not just `GET`/`HEAD`: chi's `Mount` registers all methods and `http.FileServer` special-cases only `HEAD` (by omitting the body), so a `POST` or `DELETE` to an asset is answered `200` with the file. Pinned by `TestStaticServesEveryMethod` |
|
||||
| `POST` | `/webhook/{uuid}` | Webhook receiver endpoint. `POST` only — every other method is answered `405 Method Not Allowed` with `Allow: POST`. Rate limited (see [Rate Limiting](#rate-limiting)) |
|
||||
|
||||
#### Authentication Endpoints
|
||||
@@ -3280,5 +3288,3 @@ MIT
|
||||
## Author
|
||||
|
||||
[@sneak](https://sneak.berlin)
|
||||
|
||||
|
||||
|
||||
@@ -43,6 +43,12 @@ var (
|
||||
// permit specific blocks out of this set with
|
||||
// ALLOWED_EGRESS_CIDRS; see Guard.
|
||||
//
|
||||
// A public address belongs here only if it serves cloud
|
||||
// credentials; a provider's other services on public addresses,
|
||||
// such as its DNS resolvers or package mirrors, stay out, since
|
||||
// reaching them can be legitimate and no list of them could be
|
||||
// complete.
|
||||
//
|
||||
//nolint:gochecknoglobals // package-level network list is appropriate here
|
||||
var blockedNetworks []*net.IPNet
|
||||
|
||||
|
||||
@@ -92,25 +92,11 @@ func (s *Server) setupGlobalMiddleware() {
|
||||
func (s *Server) setupRoutes() {
|
||||
s.router.Get("/", s.h.HandleIndex())
|
||||
|
||||
// Static assets answer GET and HEAD only. chi's default 405
|
||||
// carries no Allow header, so this group supplies its own.
|
||||
staticFiles := http.StripPrefix(
|
||||
"/s", http.FileServer(http.FS(static.Static)),
|
||||
s.router.Mount(
|
||||
"/s",
|
||||
http.StripPrefix("/s", http.FileServer(http.FS(static.Static))),
|
||||
)
|
||||
|
||||
s.router.Route("/s", func(r chi.Router) {
|
||||
r.MethodNotAllowed(func(w http.ResponseWriter, _ *http.Request) {
|
||||
w.Header().Set("Allow", "GET, HEAD")
|
||||
http.Error(
|
||||
w,
|
||||
"Method Not Allowed",
|
||||
http.StatusMethodNotAllowed,
|
||||
)
|
||||
})
|
||||
r.Method(http.MethodGet, "/*", staticFiles)
|
||||
r.Method(http.MethodHead, "/*", staticFiles)
|
||||
})
|
||||
|
||||
s.router.Route("/api/v1", func(_ chi.Router) {
|
||||
// API routes will be added here.
|
||||
})
|
||||
|
||||
@@ -396,15 +396,13 @@ func (e *testEnv) storedHash(t *testing.T, username string) string {
|
||||
|
||||
// --- /s static group ---
|
||||
|
||||
// TestStaticServesOnlyGetAndHead pins the methods the static group
|
||||
// answers: GET and HEAD are served the asset, and the other methods
|
||||
// chi routes (POST, PUT, DELETE and the rest) are refused with 405
|
||||
// and an Allow header naming those two. A method chi does not route,
|
||||
// such as PROPFIND, is refused with 405 by the top-level router
|
||||
// before it reaches the static group, so it gets no Allow header.
|
||||
// The README documents this; the test is what keeps the two from
|
||||
// drifting.
|
||||
func TestStaticServesOnlyGetAndHead(t *testing.T) {
|
||||
// TestStaticServesEveryMethod pins what the static mount actually
|
||||
// answers. chi's Mount registers the handler for all methods and
|
||||
// http.FileServer only special-cases HEAD (by suppressing the body),
|
||||
// so a POST or a DELETE to an asset is served the file rather than
|
||||
// refused. The README documents this; the test is what keeps the two
|
||||
// from drifting.
|
||||
func TestStaticServesEveryMethod(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
env := newTestEnv(t)
|
||||
@@ -419,7 +417,6 @@ func TestStaticServesOnlyGetAndHead(t *testing.T) {
|
||||
http.MethodPost,
|
||||
http.MethodPut,
|
||||
http.MethodDelete,
|
||||
"PROPFIND",
|
||||
} {
|
||||
t.Run(method, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
@@ -431,38 +428,18 @@ func TestStaticServesOnlyGetAndHead(t *testing.T) {
|
||||
w := httptest.NewRecorder()
|
||||
env.router.ServeHTTP(w, req)
|
||||
|
||||
switch method {
|
||||
case http.MethodGet:
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, body, w.Body.Bytes(),
|
||||
"the asset itself is returned")
|
||||
case http.MethodHead:
|
||||
assert.Equal(t, http.StatusOK, w.Code)
|
||||
assert.Equal(t, http.StatusOK, w.Code,
|
||||
"static mount answers every method")
|
||||
|
||||
if method == http.MethodHead {
|
||||
assert.Empty(t, w.Body.Bytes(),
|
||||
"HEAD must not carry a body")
|
||||
case "PROPFIND":
|
||||
assert.Equal(
|
||||
t, http.StatusMethodNotAllowed, w.Code,
|
||||
)
|
||||
assert.Empty(t, w.Header().Get("Allow"),
|
||||
"chi refuses a method it does not route "+
|
||||
"before the static group runs")
|
||||
assert.NotContains(
|
||||
t, w.Body.String(), string(body),
|
||||
"a refused method must not get the asset",
|
||||
)
|
||||
default:
|
||||
assert.Equal(
|
||||
t, http.StatusMethodNotAllowed, w.Code,
|
||||
)
|
||||
assert.Equal(
|
||||
t, "GET, HEAD", w.Header().Get("Allow"),
|
||||
)
|
||||
assert.NotContains(
|
||||
t, w.Body.String(), string(body),
|
||||
"a refused method must not get the asset",
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
assert.Equal(t, body, w.Body.Bytes(),
|
||||
"the asset itself is returned")
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user