Wire action-group variables, sudo, and command path resolution through the API and UI.

Completes run-if/set_variable and requires_sudo editing, sudoers path resolve helpers, and execute permission checks so node action groups can gate upgrades and elevate safely.
This commit is contained in:
2026-07-19 22:00:14 +02:00
parent 0e06063c1d
commit 7631591f30
22 changed files with 2685 additions and 274 deletions
@@ -357,6 +357,63 @@ func TestNodesTestSSHReportsFailureForUnreachableHost(t *testing.T) {
}
}
func TestNodesResolveCommandsRejectsInvalidNames(t *testing.T) {
configDir := t.TempDir()
cookie := seedCompletedSetup(t, configDir)
router := NewRouter(configDir)
createBody := []byte(`{
"id":"dddddddd-bbbb-4ccc-8ddd-eeeeeeeeeeee",
"kind":"container",
"name":"resolve-test",
"host_ip":"127.0.0.1",
"group_name":"Administrators",
"generate":{"algorithm":"ed25519"}
}`)
createRequest := withSession(
httptest.NewRequest(http.MethodPost, "/api/v1/nodes", bytes.NewReader(createBody)),
cookie,
)
createRecorder := httptest.NewRecorder()
router.ServeHTTP(createRecorder, createRequest)
if createRecorder.Code != http.StatusCreated {
t.Fatalf("create status = %d body=%s", createRecorder.Code, createRecorder.Body.String())
}
badBody := []byte(`{"names":["/usr/bin/apt"]}`)
badRequest := withSession(
httptest.NewRequest(
http.MethodPost,
"/api/v1/nodes/dddddddd-bbbb-4ccc-8ddd-eeeeeeeeeeee/resolve-commands",
bytes.NewReader(badBody),
),
cookie,
)
badRecorder := httptest.NewRecorder()
router.ServeHTTP(badRecorder, badRequest)
if badRecorder.Code != http.StatusBadRequest {
t.Fatalf("status = %d body=%s", badRecorder.Code, badRecorder.Body.String())
}
if !strings.Contains(badRecorder.Body.String(), "basename") {
t.Fatalf("expected basename error, got %s", badRecorder.Body.String())
}
emptyBody := []byte(`{"names":[]}`)
emptyRequest := withSession(
httptest.NewRequest(
http.MethodPost,
"/api/v1/nodes/dddddddd-bbbb-4ccc-8ddd-eeeeeeeeeeee/resolve-commands",
bytes.NewReader(emptyBody),
),
cookie,
)
emptyRecorder := httptest.NewRecorder()
router.ServeHTTP(emptyRecorder, emptyRequest)
if emptyRecorder.Code != http.StatusBadRequest {
t.Fatalf("empty status = %d body=%s", emptyRecorder.Code, emptyRecorder.Body.String())
}
}
func TestNodesDeleteRequiresPermissionAndInlineReauth(t *testing.T) {
configDir := t.TempDir()
cookie := seedCompletedSetup(t, configDir)