feat: add bounded admin WebSocket backend (Phase 8D.5)
- Require current admin cookie sessions, Origin checks and single-use tickets - Reuse the shared console with session-aware authorization and slot allocation - Add HTTPD-owned I/O, bounded buffering and revocation cleanup - Prevent LRU eviction of serial clients and stale admin socket closure - Reject unsupported web-shell mutations before side effects - Add host regressions, a smoke client and resource accounting Validated by user sign-off after a 15-minute full-client soak at 230400 baud, with a few broker drops under heavy output. Browser UI remains for Phase 8D.6; numeric memory reserves remain open.
This commit is contained in:
@@ -23,7 +23,11 @@ prelude = r'''
|
||||
#include <stdio.h>
|
||||
#define ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY 256U
|
||||
#define ADMIN_SSH_CONSOLE_MAX_ARGUMENTS 10U
|
||||
typedef struct { char line[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U]; } admin_request_t;
|
||||
#define ADMIN_CONSOLE_TRANSPORT_WEB 1U
|
||||
typedef struct {
|
||||
struct { uint8_t transport; } token;
|
||||
char line[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U];
|
||||
} admin_request_t;
|
||||
size_t esp_console_split_argv(char *, char **, size_t);
|
||||
static void secure_wipe(void *p, size_t n) {
|
||||
volatile unsigned char *bytes = p;
|
||||
@@ -48,7 +52,48 @@ int main(void) {
|
||||
assert(remote_command_allowed(&request) == cases[i].allowed);
|
||||
assert(!strcmp(request.line, cases[i].line));
|
||||
}
|
||||
puts("PASS: empty input/ordinary commands allowed; physical-only commands (including quoted forms) remain denied");
|
||||
const char *web_allowed[] = {
|
||||
"", " ", "help", "memory", "exit", "user", "user status", "user list",
|
||||
"user show admin", "\"user\" \"show\" \"bootstrap\"",
|
||||
"web status", "wifi status", "mdns status", "\"web\" \"status\"",
|
||||
"ssh status", "ssh sessions", "ssh counters", "ssh host-key info", "ssh start",
|
||||
};
|
||||
const char *web_denied[] = {
|
||||
"web", "web help", "web start", "web stop", "web counters", "web clear-counters",
|
||||
"web credentials show", "web credentials rotate --force", "web certificate info",
|
||||
"web certificate rotate --force", "web reset --force", "web status extra",
|
||||
"wifi", "wifi profiles", "wifi scan", "wifi start", "wifi stop", "wifi save",
|
||||
"wifi load", "wifi defaults", "wifi reset", "wifi ping example.org",
|
||||
"mdns", "mdns suffix test", "mdns save", "mdns load", "mdns defaults", "mdns reset",
|
||||
"reboot", "reboot --force", "user bootstrap", "user recover --force",
|
||||
"user add other admin --generate", "user delete other --force",
|
||||
"user role other user --force", "user password admin --generate",
|
||||
"user password other", "user key add admin", "user key clear admin --force",
|
||||
"user key delete admin 0 --force", "user list extra", "user show admin extra",
|
||||
"ssh stop", "ssh disconnect 7", "ssh host-key rotate --force", "ssh reset --force",
|
||||
" \"user\" \"password\" \"admin\" \"--generate\"",
|
||||
"\"web\" \"credentials\" \"show\"", "\"wifi\" \"stop\"",
|
||||
"\"mdns\" \"reset\"", "\"reboot\"", "\"ssh\" \"stop\"",
|
||||
"\"ssh\" \"host-key\" \"rotate\" --force", "\"user\" \"recover\" --force",
|
||||
};
|
||||
for (size_t i=0; i<sizeof(web_allowed)/sizeof(web_allowed[0]); ++i) {
|
||||
admin_request_t request={.token.transport=ADMIN_CONSOLE_TRANSPORT_WEB};
|
||||
strcpy(request.line,web_allowed[i]);
|
||||
assert(remote_command_allowed(&request));
|
||||
assert(!strcmp(request.line,web_allowed[i]));
|
||||
}
|
||||
for (size_t i=0; i<sizeof(web_denied)/sizeof(web_denied[0]); ++i) {
|
||||
admin_request_t request={.token.transport=ADMIN_CONSOLE_TRANSPORT_WEB};
|
||||
strcpy(request.line,web_denied[i]);
|
||||
if (remote_command_allowed(&request)) fprintf(stderr,"Unexpected allow: %s\n",request.line);
|
||||
assert(!remote_command_allowed(&request));
|
||||
assert(!strcmp(request.line,web_denied[i]));
|
||||
request.token.transport=0;
|
||||
/* SSH retains only the global bootstrap/recover dispatcher restriction. */
|
||||
assert(remote_command_allowed(&request) ==
|
||||
(strstr(request.line,"bootstrap")==NULL && strstr(request.line,"recover")==NULL));
|
||||
}
|
||||
puts("PASS: SSH policy unchanged; web read-only exceptions, mutations/lifecycle and quoted forms checked with actual IDF parser");
|
||||
}
|
||||
'''
|
||||
with tempfile.TemporaryDirectory(prefix="admin-ssh-policy-") as directory:
|
||||
|
||||
Reference in New Issue
Block a user