fixes 5-1-26
This commit is contained in:
parent
f1a245cc01
commit
e3f91454f8
@ -27,16 +27,16 @@ func Public(methods ...string) map[string]struct{} {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func Wrap(next http.Handler, policy Policy) http.Handler {
|
func Wrap(next http.Handler, policy Policy) http.Handler {
|
||||||
if strings.TrimSpace(os.Getenv(envToken)) == "" {
|
|
||||||
log.Printf("[auth] warning: %s not set; agent auth enforcement disabled", envToken)
|
|
||||||
return next
|
|
||||||
}
|
|
||||||
|
|
||||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if policy.IsPublic(r.Method, r.URL.Path) {
|
if policy.IsPublic(r.Method, r.URL.Path) {
|
||||||
next.ServeHTTP(w, r)
|
next.ServeHTTP(w, r)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if strings.TrimSpace(os.Getenv(envToken)) == "" {
|
||||||
|
log.Printf("[auth] warning: %s not set; rejecting protected request path=%s", envToken, r.URL.Path)
|
||||||
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
|
return
|
||||||
|
}
|
||||||
if !Authorized(r) {
|
if !Authorized(r) {
|
||||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||||
return
|
return
|
||||||
|
|||||||
@ -1,11 +1,129 @@
|
|||||||
package agenthttp
|
package agenthttp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/gorilla/websocket"
|
||||||
|
|
||||||
"zlh-agent/internal/state"
|
"zlh-agent/internal/state"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func TestProtectedRoutesRequireAgentToken(t *testing.T) {
|
||||||
|
t.Setenv("ZLH_AGENT_TOKEN", "test-token")
|
||||||
|
|
||||||
|
protected := []struct {
|
||||||
|
method string
|
||||||
|
path string
|
||||||
|
body string
|
||||||
|
}{
|
||||||
|
{http.MethodGet, "/status", ""},
|
||||||
|
{http.MethodGet, "/ready", ""},
|
||||||
|
{http.MethodPost, "/config", "{}"},
|
||||||
|
{http.MethodPost, "/start", ""},
|
||||||
|
{http.MethodPost, "/stop", ""},
|
||||||
|
{http.MethodPost, "/restart", ""},
|
||||||
|
{http.MethodGet, "/game/files/list", ""},
|
||||||
|
{http.MethodGet, "/game/files/read", ""},
|
||||||
|
{http.MethodGet, "/game/files/download", ""},
|
||||||
|
{http.MethodPost, "/game/files/upload", ""},
|
||||||
|
{http.MethodPost, "/game/files/revert", ""},
|
||||||
|
{http.MethodGet, "/game/files/stat", ""},
|
||||||
|
{http.MethodGet, "/game/backups", ""},
|
||||||
|
{http.MethodPost, "/game/backups", ""},
|
||||||
|
{http.MethodPost, "/game/backups/restore", `{"id":"backup-1"}`},
|
||||||
|
{http.MethodDelete, "/game/backups/backup-1", ""},
|
||||||
|
{http.MethodGet, "/game/mods", ""},
|
||||||
|
{http.MethodPost, "/game/mods/install", "{}"},
|
||||||
|
{http.MethodPost, "/game/mods/mod-1", "{}"},
|
||||||
|
{http.MethodPost, "/agent/update", "{}"},
|
||||||
|
{http.MethodGet, "/agent/update/status", ""},
|
||||||
|
{http.MethodGet, "/metrics/process", ""},
|
||||||
|
{http.MethodPost, "/dev/codeserver/start", ""},
|
||||||
|
{http.MethodPost, "/dev/codeserver/stop", ""},
|
||||||
|
{http.MethodPost, "/dev/codeserver/restart", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
mux := NewMux()
|
||||||
|
for _, tc := range protected {
|
||||||
|
t.Run(tc.method+" "+tc.path, func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(tc.method, tc.path, strings.NewReader(tc.body))
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("without token status = %d, want %d", rec.Code, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
|
||||||
|
req = httptest.NewRequest(tc.method, tc.path, strings.NewReader(tc.body))
|
||||||
|
req.Header.Set("Authorization", "Bearer test-token")
|
||||||
|
rec = httptest.NewRecorder()
|
||||||
|
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code == http.StatusUnauthorized {
|
||||||
|
t.Fatalf("with token status = %d, want authorized request to reach handler", rec.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProtectedRoutesFailClosedWhenAgentTokenMissing(t *testing.T) {
|
||||||
|
t.Setenv("ZLH_AGENT_TOKEN", "")
|
||||||
|
|
||||||
|
mux := NewMux()
|
||||||
|
req := httptest.NewRequest(http.MethodGet, "/status", nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("status = %d, want %d", rec.Code, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPublicRoutesDoNotRequireAgentToken(t *testing.T) {
|
||||||
|
t.Setenv("ZLH_AGENT_TOKEN", "")
|
||||||
|
|
||||||
|
mux := NewMux()
|
||||||
|
for _, path := range []string{"/health", "/version"} {
|
||||||
|
t.Run(path, func(t *testing.T) {
|
||||||
|
req := httptest.NewRequest(http.MethodGet, path, nil)
|
||||||
|
rec := httptest.NewRecorder()
|
||||||
|
|
||||||
|
mux.ServeHTTP(rec, req)
|
||||||
|
|
||||||
|
if rec.Code == http.StatusUnauthorized {
|
||||||
|
t.Fatalf("status = %d, want public route", rec.Code)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConsoleWebSocketRequiresAgentToken(t *testing.T) {
|
||||||
|
t.Setenv("ZLH_AGENT_TOKEN", "test-token")
|
||||||
|
|
||||||
|
server := httptest.NewServer(NewMux())
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
conn, resp, err := websocket.DefaultDialer.Dial(strings.Replace(server.URL, "http://", "ws://", 1)+"/console/stream", nil)
|
||||||
|
if conn != nil {
|
||||||
|
_ = conn.Close()
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("websocket dial without token succeeded")
|
||||||
|
}
|
||||||
|
if resp == nil {
|
||||||
|
t.Fatalf("websocket dial response is nil: %v", err)
|
||||||
|
}
|
||||||
|
if resp.StatusCode != http.StatusUnauthorized {
|
||||||
|
t.Fatalf("status = %d, want %d", resp.StatusCode, http.StatusUnauthorized)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestEnsureDevCodeServerInstallsAndStartsWhenRequested(t *testing.T) {
|
func TestEnsureDevCodeServerInstallsAndStartsWhenRequested(t *testing.T) {
|
||||||
restoreCodeServerTestHooks(t)
|
restoreCodeServerTestHooks(t)
|
||||||
|
|
||||||
|
|||||||
@ -305,3 +305,10 @@
|
|||||||
[2026-04-30T20:08:10Z] action=archive_write step=add_path status=complete backup_id=safe path=world files=1 bytes=6
|
[2026-04-30T20:08:10Z] action=archive_write step=add_path status=complete backup_id=safe path=world files=1 bytes=6
|
||||||
[2026-04-30T20:08:10Z] action=archive_write status=complete backup_id=safe files=1 bytes=6
|
[2026-04-30T20:08:10Z] action=archive_write status=complete backup_id=safe files=1 bytes=6
|
||||||
[2026-04-30T20:08:10Z] action=archive_restore status=begin archive_path=/tmp/TestRestoreArchiveRejectsUnsafeManifestPathBeforeDeleting3515028932/003/safe.tar.gz server_root=/tmp/TestRestoreArchiveRejectsUnsafeManifestPathBeforeDeleting3515028932/001 paths=["../world"]
|
[2026-04-30T20:08:10Z] action=archive_restore status=begin archive_path=/tmp/TestRestoreArchiveRejectsUnsafeManifestPathBeforeDeleting3515028932/003/safe.tar.gz server_root=/tmp/TestRestoreArchiveRejectsUnsafeManifestPathBeforeDeleting3515028932/001 paths=["../world"]
|
||||||
|
[2026-04-30T21:43:50Z] action=archive_write status=begin backup_id=safe archive_path=/tmp/TestRestoreArchiveRejectsUnsafeManifestPathBeforeDeleting2034890975/003/safe.tar.gz server_root=/tmp/TestRestoreArchiveRejectsUnsafeManifestPathBeforeDeleting2034890975/002 paths=["world"]
|
||||||
|
[2026-04-30T21:43:50Z] action=archive_write step=add_path status=begin backup_id=safe path=world
|
||||||
|
[2026-04-30T21:43:50Z] action=archive_write step=entry backup_id=safe type=dir path=world
|
||||||
|
[2026-04-30T21:43:50Z] action=archive_write step=entry backup_id=safe type=file path=world/level.dat bytes=6 total_files=1 total_bytes=6
|
||||||
|
[2026-04-30T21:43:50Z] action=archive_write step=add_path status=complete backup_id=safe path=world files=1 bytes=6
|
||||||
|
[2026-04-30T21:43:50Z] action=archive_write status=complete backup_id=safe files=1 bytes=6
|
||||||
|
[2026-04-30T21:43:50Z] action=archive_restore status=begin archive_path=/tmp/TestRestoreArchiveRejectsUnsafeManifestPathBeforeDeleting2034890975/003/safe.tar.gz server_root=/tmp/TestRestoreArchiveRejectsUnsafeManifestPathBeforeDeleting2034890975/001 paths=["../world"]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"status": "available",
|
"status": "noop",
|
||||||
"current": "0.0.0-dev",
|
"current": "1.0.70",
|
||||||
"target": "1.0.70",
|
"target": "1.0.70",
|
||||||
"checked_at_utc": "2026-04-30T20:48:03Z"
|
"checked_at_utc": "2026-05-01T14:05:18Z"
|
||||||
}
|
}
|
||||||
Loading…
Reference in New Issue
Block a user