Extend browser admin lifecycle actions
Support browser reboot and HTTPS stop through deferred control, plus exact `web certificate rotate --force` handoff to the dispatcher. Add typed request validation and focused boundary and lifecycle coverage.
This commit is contained in:
+82
-14
@@ -70,9 +70,17 @@ typedef struct {
|
||||
uint8_t output[ADMIN_SSH_CONSOLE_OUTPUT_CAPACITY];
|
||||
} admin_session_t;
|
||||
|
||||
typedef struct {
|
||||
admin_ssh_deferred_action_type_t action;
|
||||
admin_ssh_console_token_t token;
|
||||
const admin_console_owner_t *owner;
|
||||
uint32_t argument;
|
||||
} admin_control_request_t;
|
||||
|
||||
typedef enum {
|
||||
ADMIN_REQUEST_SSH = 0,
|
||||
ADMIN_REQUEST_UART0,
|
||||
ADMIN_REQUEST_DEFERRED,
|
||||
} admin_request_origin_t;
|
||||
|
||||
typedef struct {
|
||||
@@ -80,16 +88,12 @@ typedef struct {
|
||||
admin_ssh_console_token_t token;
|
||||
user_principal_t principal;
|
||||
TaskHandle_t completion_task;
|
||||
uint8_t line[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U];
|
||||
union {
|
||||
uint8_t line[ADMIN_SSH_CONSOLE_COMMAND_LINE_CAPACITY + 1U];
|
||||
admin_control_request_t deferred;
|
||||
};
|
||||
} admin_request_t;
|
||||
|
||||
typedef struct {
|
||||
admin_ssh_deferred_action_type_t action;
|
||||
admin_ssh_console_token_t token;
|
||||
const admin_console_owner_t *owner;
|
||||
uint32_t argument;
|
||||
} admin_control_request_t;
|
||||
|
||||
static portMUX_TYPE s_lock = portMUX_INITIALIZER_UNLOCKED;
|
||||
static admin_session_t s_sessions[ADMIN_SSH_CONSOLE_MAX_SESSIONS];
|
||||
/* Claimed under s_lock, used outside it; competing TAB input is backpressured. */
|
||||
@@ -122,6 +126,12 @@ bool admin_ssh_console_dispatch_is_remote(void)
|
||||
return xTaskGetCurrentTaskHandle() == s_task && s_dispatch_remote;
|
||||
}
|
||||
|
||||
bool admin_ssh_console_dispatch_is_web(void)
|
||||
{
|
||||
return admin_ssh_console_dispatch_is_remote() &&
|
||||
s_dispatch_token.transport == ADMIN_CONSOLE_TRANSPORT_WEB;
|
||||
}
|
||||
|
||||
const user_principal_t *admin_ssh_console_dispatch_principal(void)
|
||||
{
|
||||
return admin_ssh_console_dispatch_is_remote() ? &s_dispatch_principal : NULL;
|
||||
@@ -401,7 +411,7 @@ esp_err_t admin_ssh_console_dispatch_defer(
|
||||
bool valid = token_matches(session, &s_dispatch_token) &&
|
||||
!session->deferred_action_pending;
|
||||
const admin_console_owner_t *owner = valid ? session->owner : NULL;
|
||||
if (valid && ((unsigned)action > ADMIN_CONSOLE_DEFER_SELF_CLOSE ||
|
||||
if (valid && ((unsigned)action > ADMIN_CONSOLE_DEFER_WEB_CERTIFICATE_ROTATE ||
|
||||
!(owner->supported_actions & (1U << action)))) {
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
return ESP_ERR_NOT_SUPPORTED;
|
||||
@@ -496,8 +506,12 @@ static bool remote_command_allowed(const admin_request_t *request)
|
||||
* User mutations remain available through UART0/SSH, subject to their policy.
|
||||
*/
|
||||
if (request->token.transport == ADMIN_CONSOLE_TRANSPORT_WEB && argc > 0U) {
|
||||
if (strcmp(argv[0], "web") == 0 || strcmp(argv[0], "wifi") == 0 ||
|
||||
strcmp(argv[0], "mdns") == 0) {
|
||||
if (strcmp(argv[0], "web") == 0) {
|
||||
allowed = (argc == 2U && (strcmp(argv[1], "status") == 0 ||
|
||||
strcmp(argv[1], "stop") == 0)) ||
|
||||
(argc == 4U && strcmp(argv[1], "certificate") == 0 &&
|
||||
strcmp(argv[2], "rotate") == 0 && strcmp(argv[3], "--force") == 0);
|
||||
} else if (strcmp(argv[0], "wifi") == 0 || strcmp(argv[0], "mdns") == 0) {
|
||||
allowed = argc == 2U && strcmp(argv[1], "status") == 0;
|
||||
} else if (strcmp(argv[0], "user") == 0) {
|
||||
allowed = argc == 1U ||
|
||||
@@ -505,9 +519,9 @@ static bool remote_command_allowed(const admin_request_t *request)
|
||||
strcmp(argv[1], "list") == 0)) ||
|
||||
(argc == 3U && strcmp(argv[1], "show") == 0);
|
||||
} else if (strcmp(argv[0], "reboot") == 0) {
|
||||
allowed = false;
|
||||
allowed = argc == 1U;
|
||||
} else if (strcmp(argv[0], "ssh") == 0 && argc >= 2U) {
|
||||
/* These handlers defer for every remote; WEB supports SELF_CLOSE only. */
|
||||
/* SSH-specific deferred actions are not yet supported by WEB. */
|
||||
if (strcmp(argv[1], "stop") == 0 || strcmp(argv[1], "disconnect") == 0 ||
|
||||
strcmp(argv[1], "reset") == 0 ||
|
||||
(strcmp(argv[1], "host-key") == 0 &&
|
||||
@@ -598,6 +612,8 @@ static void dispatch_registered_command(admin_request_t *request)
|
||||
}
|
||||
}
|
||||
|
||||
static void dispatch_deferred_request(admin_request_t *request);
|
||||
|
||||
static void worker_task(void *context)
|
||||
{
|
||||
(void)context;
|
||||
@@ -606,6 +622,11 @@ static void worker_task(void *context)
|
||||
if (xQueueReceive(s_request_queue, &request, portMAX_DELAY) != pdTRUE) {
|
||||
continue;
|
||||
}
|
||||
if (request.origin == ADMIN_REQUEST_DEFERRED) {
|
||||
dispatch_deferred_request(&request);
|
||||
secure_wipe(&request, sizeof(request));
|
||||
continue;
|
||||
}
|
||||
if (request.origin == ADMIN_REQUEST_UART0) {
|
||||
dispatch_registered_command(&request);
|
||||
if (request.completion_task != NULL) {
|
||||
@@ -683,6 +704,34 @@ static void finish_deferred_request(const admin_control_request_t *request,
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
}
|
||||
|
||||
static void dispatch_deferred_request(admin_request_t *request)
|
||||
{
|
||||
const admin_control_request_t *action = &request->deferred;
|
||||
bool current = session_is_current(&action->token, &request->principal);
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
admin_session_t *session = &s_sessions[action->token.slot_index];
|
||||
bool active = current && token_matches(session, &action->token) &&
|
||||
session->owner == action->owner && session->deferred_action_pending &&
|
||||
!session->command_pending && !session->executing;
|
||||
if (active) session->executing = true;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
|
||||
/* Keep the slot reserved across lifecycle callbacks, including self-detach.
|
||||
* Recheck after reservation just as the canonical runner does. */
|
||||
esp_err_t result = ESP_ERR_NOT_FOUND;
|
||||
if (active && session_is_current(&action->token, &request->principal)) {
|
||||
result = action->owner->perform(&action->token, action->action, action->argument);
|
||||
}
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
session = &s_sessions[action->token.slot_index];
|
||||
if (active && token_identity_matches(session, &action->token)) {
|
||||
if (session->active) session->executing = false;
|
||||
else secure_wipe(session, sizeof(*session));
|
||||
}
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
finish_deferred_request(action, result, false);
|
||||
}
|
||||
|
||||
static void control_task(void *context)
|
||||
{
|
||||
(void)context;
|
||||
@@ -718,8 +767,26 @@ static void control_task(void *context)
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(200U));
|
||||
taskENTER_CRITICAL(&s_lock);
|
||||
bool current = token_matches(&s_sessions[request.token.slot_index], &request.token);
|
||||
admin_session_t *session = &s_sessions[request.token.slot_index];
|
||||
bool current = token_matches(session, &request.token) &&
|
||||
session->owner == request.owner && session->deferred_action_pending;
|
||||
admin_request_t queued = {
|
||||
.origin = ADMIN_REQUEST_DEFERRED,
|
||||
.deferred = request,
|
||||
};
|
||||
if (current) queued.principal = session->principal;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
if (current && (request.owner->dispatcher_actions & (1U << request.action))) {
|
||||
/* Nonblocking handoff: a full dispatcher queue fails before mutation.
|
||||
* Pending remains set until execution completes, not merely enqueue. */
|
||||
if (xQueueSend(s_request_queue, &queued, 0U) != pdTRUE) {
|
||||
finish_deferred_request(&request, ESP_ERR_TIMEOUT, false);
|
||||
}
|
||||
secure_wipe(&queued, sizeof(queued));
|
||||
secure_wipe(&request, sizeof(request));
|
||||
continue;
|
||||
}
|
||||
secure_wipe(&queued, sizeof(queued));
|
||||
esp_err_t result = current ? request.owner->perform(
|
||||
&request.token, request.action, request.argument) : ESP_ERR_NOT_FOUND;
|
||||
finish_deferred_request(&request, result, false);
|
||||
@@ -1266,6 +1333,7 @@ esp_err_t admin_ssh_console_get_session_snapshot(
|
||||
snapshot->command_pending = session->command_pending;
|
||||
snapshot->input_pending = session->input_length != 0U;
|
||||
snapshot->output_pending = session->output_length != 0U;
|
||||
snapshot->deferred_action_pending = session->deferred_action_pending;
|
||||
snapshot->input_length = session->input_length;
|
||||
snapshot->output_length = session->output_length;
|
||||
taskEXIT_CRITICAL(&s_lock);
|
||||
|
||||
Reference in New Issue
Block a user