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 {
|
||||
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) {
|
||||
if policy.IsPublic(r.Method, r.URL.Path) {
|
||||
next.ServeHTTP(w, r)
|
||||
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) {
|
||||
http.Error(w, "unauthorized", http.StatusUnauthorized)
|
||||
return
|
||||
|
||||
@ -1,11 +1,129 @@
|
||||
package agenthttp
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gorilla/websocket"
|
||||
|
||||
"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) {
|
||||
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 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-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",
|
||||
"current": "0.0.0-dev",
|
||||
"status": "noop",
|
||||
"current": "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