Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a7bb770e60 | ||
|
|
89ab5378a1 |
@@ -279,9 +279,10 @@ Key settings in more detail:
|
|||||||
- `maintenance_mode` — while `true`, the image routes (`/v1/image/` and
|
- `maintenance_mode` — while `true`, the image routes (`/v1/image/` and
|
||||||
`/v1/e/`) answer every request with 503, a `Retry-After` header and a JSON
|
`/v1/e/`) answer every request with 503, a `Retry-After` header and a JSON
|
||||||
error body. The health check (`/.well-known/healthcheck.json`) still answers
|
error body. The health check (`/.well-known/healthcheck.json`) still answers
|
||||||
200 and reports `"maintenance_mode": true`: the image's Docker `HEALTHCHECK`
|
200 and reports `"maintenance_mode": true`. It stays 200 because the image's
|
||||||
and upaas both read it, and a 503 there would make upaas mark the deploy
|
Docker `HEALTHCHECK` requests it: a 503 there would make the container
|
||||||
failed. The login and URL generator pages and `/metrics` keep working
|
unhealthy, and upaas marks a deploy failed when its container is unhealthy.
|
||||||
|
The login and URL generator pages and `/metrics` keep working
|
||||||
|
|
||||||
See `config.example.yml` for all options with defaults.
|
See `config.example.yml` for all options with defaults.
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,10 @@ exhaustion
|
|||||||
`maintenance_mode` is on, `/v1/image/` and `/v1/e/` answer 503 with a
|
`maintenance_mode` is on, `/v1/image/` and `/v1/e/` answer 503 with a
|
||||||
`Retry-After` header and the JSON error body, from one middleware in
|
`Retry-After` header and the JSON error body, from one middleware in
|
||||||
`internal/server/routes.go`; the health check stays 200 and reports
|
`internal/server/routes.go`; the health check stays 200 and reports
|
||||||
`maintenance_mode`, as the image's Docker `HEALTHCHECK` and upaas read it; the
|
`maintenance_mode`, as the image's Docker `HEALTHCHECK` requests it and upaas
|
||||||
login and URL generator pages and `/metrics` keep working; documented in
|
marks a deploy failed when its container is unhealthy; the login and URL
|
||||||
`README.md` and `config.example.yml`.
|
generator pages and `/metrics` keep working; documented in `README.md` and
|
||||||
|
`config.example.yml`.
|
||||||
- 2026-09-29 `trusted_proxies` advice and signature padding in `README.md`
|
- 2026-09-29 `trusted_proxies` advice and signature padding in `README.md`
|
||||||
(closes #150): the login-limit paragraph, the `trusted_proxies` entry and
|
(closes #150): the login-limit paragraph, the `trusted_proxies` entry and
|
||||||
`config.example.yml` say to set `trusted_proxies` to the address pixa sees for
|
`config.example.yml` say to set `trusted_proxies` to the address pixa sees for
|
||||||
|
|||||||
+3
-2
@@ -15,8 +15,9 @@ debug: false
|
|||||||
|
|
||||||
# While true, the image routes (/v1/image/ and /v1/e/) answer every request
|
# While true, the image routes (/v1/image/ and /v1/e/) answer every request
|
||||||
# with 503 and a Retry-After header. The health check keeps answering 200 and
|
# with 503 and a Retry-After header. The health check keeps answering 200 and
|
||||||
# reports maintenance_mode: the image's Docker HEALTHCHECK and upaas read it,
|
# reports maintenance_mode as true. It stays 200 because the image's Docker
|
||||||
# and a 503 there would make upaas mark the deploy failed.
|
# HEALTHCHECK requests it: a 503 there would make the container unhealthy, and
|
||||||
|
# upaas marks a deploy failed when its container is unhealthy.
|
||||||
maintenance_mode: false
|
maintenance_mode: false
|
||||||
|
|
||||||
# Data directory for SQLite database and cache files
|
# Data directory for SQLite database and cache files
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import (
|
|||||||
"sneak.berlin/go/pixa/internal/healthcheck"
|
"sneak.berlin/go/pixa/internal/healthcheck"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// unsignedImagePath is an image URL that carries no signature.
|
||||||
|
const unsignedImagePath = "/v1/image/cdn.example.com/cat.jpg/100x100.jpeg"
|
||||||
|
|
||||||
// TestMaintenanceModeRefusesImageRequests verifies that while maintenance
|
// TestMaintenanceModeRefusesImageRequests verifies that while maintenance
|
||||||
// mode is on, both image routes answer 503 Service Unavailable with a
|
// mode is on, both image routes answer 503 Service Unavailable with a
|
||||||
// Retry-After header and the JSON error body the image handlers send.
|
// Retry-After header and the JSON error body the image handlers send.
|
||||||
@@ -23,8 +26,8 @@ func TestMaintenanceModeRefusesImageRequests(t *testing.T) {
|
|||||||
method string
|
method string
|
||||||
path string
|
path string
|
||||||
}{
|
}{
|
||||||
{http.MethodGet, "/v1/image/cdn.example.com/cat.jpg/100x100.jpeg"},
|
{http.MethodGet, unsignedImagePath},
|
||||||
{http.MethodHead, "/v1/image/cdn.example.com/cat.jpg/100x100.jpeg"},
|
{http.MethodHead, unsignedImagePath},
|
||||||
{http.MethodGet, "/v1/e/token/cat.jpg"},
|
{http.MethodGet, "/v1/e/token/cat.jpg"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,10 +78,49 @@ func TestMaintenanceModeRefusesImageRequests(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestImageRequestsServedWithoutMaintenanceMode verifies that while
|
||||||
|
// maintenance mode is off, image requests reach the image handlers instead
|
||||||
|
// of the 503. The handlers refuse an unsigned image URL with 401 and a token
|
||||||
|
// they cannot decrypt with 400, so either status shows a request got through.
|
||||||
|
func TestImageRequestsServedWithoutMaintenanceMode(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
s := newTestServer(t)
|
||||||
|
s.config.MaintenanceMode = false
|
||||||
|
|
||||||
|
requests := []struct {
|
||||||
|
method string
|
||||||
|
path string
|
||||||
|
want int
|
||||||
|
}{
|
||||||
|
{http.MethodGet, unsignedImagePath, http.StatusUnauthorized},
|
||||||
|
{http.MethodHead, unsignedImagePath, http.StatusUnauthorized},
|
||||||
|
{http.MethodGet, "/v1/e/token/cat.jpg", http.StatusBadRequest},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range requests {
|
||||||
|
t.Run(tc.method+" "+tc.path, func(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
s.ServeHTTP(rec, httptest.NewRequestWithContext(
|
||||||
|
t.Context(), tc.method, tc.path, nil))
|
||||||
|
t.Logf("status %d, body %s", rec.Code, rec.Body.String())
|
||||||
|
|
||||||
|
if rec.Code != tc.want {
|
||||||
|
t.Errorf("status = %d, want %d from the image handler",
|
||||||
|
rec.Code, tc.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestMaintenanceModeKeepsOtherRoutes verifies that while maintenance mode
|
// TestMaintenanceModeKeepsOtherRoutes verifies that while maintenance mode
|
||||||
// is on, the health check still answers 200 and reports it, since the
|
// is on, the health check still answers 200 and reports it, and the login
|
||||||
// image's Docker HEALTHCHECK and upaas read it, and the login page and
|
// page and /metrics still answer 200. The image's Docker HEALTHCHECK
|
||||||
// /metrics still answer 200.
|
// requests the health check: a 503 there would make the container
|
||||||
|
// unhealthy, and upaas marks a deploy failed when its container is
|
||||||
|
// unhealthy.
|
||||||
func TestMaintenanceModeKeepsOtherRoutes(t *testing.T) {
|
func TestMaintenanceModeKeepsOtherRoutes(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
@@ -75,8 +75,9 @@ func (s *Server) SetupRoutes() {
|
|||||||
s.router.Get("/logout", s.h.HandleLogout())
|
s.router.Get("/logout", s.h.HandleLogout())
|
||||||
|
|
||||||
// Image routes, refused while maintenance mode is on. Only these: the
|
// Image routes, refused while maintenance mode is on. Only these: the
|
||||||
// health check must stay 200, as the image's Docker HEALTHCHECK and
|
// image's Docker HEALTHCHECK requests the health check, a 503 there
|
||||||
// upaas read it, and a 503 there would make upaas fail the deploy.
|
// would make the container unhealthy, and upaas marks a deploy failed
|
||||||
|
// when its container is unhealthy.
|
||||||
s.router.Group(func(r chi.Router) {
|
s.router.Group(func(r chi.Router) {
|
||||||
r.Use(s.refuseDuringMaintenance)
|
r.Use(s.refuseDuringMaintenance)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user