Add SSH Console Ownership Boundary (Phase 8D.4)

Implement transport-qualified session identity and immutable owner
adapters
for SSH console lifecycle and output-drain operations. Add focused host
tests
covering admission, stale identities, deferred actions, completion
races,
prompts, backpressure, and slot reuse. Update Phase 8D documentation and
current-state tracking.
This commit is contained in:
2026-09-06 09:07:11 +02:00
parent f9ee6eec9c
commit 117c694cd4
13 changed files with 488 additions and 60 deletions
+58
View File
@@ -12,6 +12,7 @@
#include "admin_ssh_console.h"
#include "esp_heap_caps.h"
#include "esp_log.h"
#include "esp_system.h"
#include "esp_timer.h"
#include "freertos/FreeRTOS.h"
#include "freertos/semphr.h"
@@ -179,6 +180,63 @@ static void publish_slot(const ssh_slot_t *slot, size_t slot_index)
taskEXIT_CRITICAL(&s_lock);
}
/* Control-task adapter: copied snapshots only, no runtime wolfSSH calls. */
static bool admin_console_drained(const admin_ssh_console_token_t *token)
{
if (token->transport != 0U || token->slot_index >= SSH_TRANSPORT_MAX_SESSIONS) {
return false;
}
taskENTER_CRITICAL(&s_lock);
const ssh_transport_session_snapshot_t *slot = &s_session_snapshots[token->slot_index];
bool drained = slot->active && slot->session_id == token->session_id &&
slot->generation == token->slot_generation && !slot->tx_pending;
taskEXIT_CRITICAL(&s_lock);
return drained;
}
static esp_err_t admin_console_perform(const admin_ssh_console_token_t *token,
admin_ssh_deferred_action_type_t action,
uint32_t argument)
{
if (!admin_console_drained(token)) {
return ESP_ERR_NOT_FOUND;
}
switch (action) {
case ADMIN_SSH_DEFER_REBOOT:
esp_restart();
return ESP_OK;
case ADMIN_SSH_DEFER_STOP:
return ssh_transport_stop();
case ADMIN_CONSOLE_DEFER_SELF_CLOSE:
return ssh_transport_disconnect(token->session_id);
case ADMIN_SSH_DEFER_DISCONNECT:
return ssh_transport_disconnect(argument);
case ADMIN_SSH_DEFER_HOST_KEY_ROTATE:
return ssh_transport_replace_host_key(false);
case ADMIN_SSH_DEFER_HOST_KEY_RESET:
return ssh_transport_replace_host_key(true);
default:
return ESP_ERR_NOT_SUPPORTED;
}
}
esp_err_t admin_ssh_console_open(const admin_ssh_console_token_t *token,
const user_principal_t *principal)
{
static const admin_console_owner_t owner = {
.supported_actions = (1U << ADMIN_SSH_DEFER_REBOOT) |
(1U << ADMIN_SSH_DEFER_STOP) | (1U << ADMIN_SSH_DEFER_DISCONNECT) |
(1U << ADMIN_SSH_DEFER_HOST_KEY_ROTATE) |
(1U << ADMIN_SSH_DEFER_HOST_KEY_RESET) | (1U << ADMIN_CONSOLE_DEFER_SELF_CLOSE),
.drained = admin_console_drained,
.perform = admin_console_perform,
};
if (token == NULL || token->transport != 0U) {
return ESP_ERR_INVALID_ARG;
}
return admin_ssh_console_open_owned(token, principal, &owner);
}
static bool consume_external_close(const ssh_slot_t *slot, size_t slot_index)
{
taskENTER_CRITICAL(&s_lock);