Add HTTPS identity rotation support
This commit is contained in:
@@ -11,6 +11,7 @@
|
||||
#include "web_cookie_auth.h"
|
||||
#include "web_httpd_adapter.h"
|
||||
#include "web_server.h"
|
||||
#include "web_security.h"
|
||||
|
||||
#if CONFIG_HTTPD_QUEUE_WORK_BLOCKING
|
||||
#error "Lifecycle ACK handoff requires nonblocking HTTPD work submission"
|
||||
@@ -18,9 +19,9 @@
|
||||
|
||||
enum { IDLE, PENDING, EXECUTING, OK, FAILED, CANCELLED };
|
||||
static const char *const s_states[] = {"idle", "pending", "pending", "ok", "failed", "cancelled"};
|
||||
static const char *const s_actions[] = {"stop", "restart", "reboot"};
|
||||
static const char *const s_actions[] = {"stop", "restart", "reboot", "rotate"};
|
||||
typedef struct {
|
||||
uint32_t id, generation;
|
||||
uint32_t id, generation, identity_generation;
|
||||
web_session_id_t session;
|
||||
user_principal_t principal;
|
||||
int64_t ack_deadline, deadline;
|
||||
@@ -46,33 +47,34 @@ static void expire_locked(int64_t now)
|
||||
cancel_locked();
|
||||
}
|
||||
|
||||
/* Exactly action + generation; no escapes, duplicates, coercions or extra fields. */
|
||||
/* Exactly action + service generation, plus identity generation only for rotate.
|
||||
* No escapes, duplicates, coercions or extra fields. */
|
||||
static bool parse(const char *body, size_t length, lifecycle_operation_t *operation)
|
||||
{
|
||||
const char *keys[] = {"action", "generation"};
|
||||
const char *keys[] = {"action", "generation", "identity_generation"};
|
||||
unsigned seen = 0;
|
||||
size_t pos = 0;
|
||||
#define SPACE() while (pos < length && (body[pos] == ' ' || body[pos] == '\t' || body[pos] == '\r' || body[pos] == '\n')) ++pos
|
||||
#define TAKE(c) do { SPACE(); if (pos == length || body[pos++] != (c)) return false; } while (0)
|
||||
TAKE('{');
|
||||
for (unsigned field = 0; field < 2; ++field) {
|
||||
for (unsigned field = 0; field < 3; ++field) {
|
||||
if (field) { TAKE(','); }
|
||||
TAKE('"'); size_t start = pos;
|
||||
while (pos < length && body[pos] != '"') ++pos;
|
||||
if (pos == length) return false;
|
||||
unsigned key = 0;
|
||||
for (; key < 2; ++key)
|
||||
for (; key < 3; ++key)
|
||||
if (strlen(keys[key]) == pos - start && !memcmp(body + start, keys[key], pos - start)) break;
|
||||
if (key == 2 || (seen & (1U << key))) return false;
|
||||
if (key == 3 || (seen & (1U << key))) return false;
|
||||
++pos; TAKE(':'); SPACE();
|
||||
if (key == 0) {
|
||||
TAKE('"'); start = pos;
|
||||
while (pos < length && body[pos] != '"') ++pos;
|
||||
if (pos == length) return false;
|
||||
unsigned action = 0;
|
||||
for (; action < 3; ++action)
|
||||
for (; action < 4; ++action)
|
||||
if (strlen(s_actions[action]) == pos - start && !memcmp(body + start, s_actions[action], pos - start)) break;
|
||||
if (action == 3) return false;
|
||||
if (action == 4) return false;
|
||||
operation->action = action; ++pos;
|
||||
} else {
|
||||
uint32_t number = 0; start = pos;
|
||||
@@ -82,14 +84,19 @@ static bool parse(const char *body, size_t length, lifecycle_operation_t *operat
|
||||
number = number * 10U + digit;
|
||||
}
|
||||
if (pos == start || (pos - start > 1 && body[start] == '0')) return false;
|
||||
operation->generation = number;
|
||||
if (key == 1) operation->generation = number;
|
||||
else operation->identity_generation = number;
|
||||
}
|
||||
seen |= 1U << key;
|
||||
SPACE();
|
||||
if (pos < length && body[pos] == '}') break;
|
||||
}
|
||||
TAKE('}'); SPACE();
|
||||
#undef TAKE
|
||||
#undef SPACE
|
||||
return pos == length && seen == 3 && operation->generation && operation->generation != UINT32_MAX;
|
||||
return pos == length && seen == (operation->action == 3 ? 7U : 3U) &&
|
||||
operation->generation && operation->generation != UINT32_MAX &&
|
||||
(operation->action != 3 || (operation->identity_generation && operation->identity_generation != UINT32_MAX));
|
||||
}
|
||||
|
||||
/* Runs on HTTPD after its synchronous response handler returns. No socket IO,
|
||||
@@ -143,9 +150,11 @@ void web_lifecycle_settings_execute(uint32_t id)
|
||||
unsigned state = CANCELLED;
|
||||
if (error == ESP_OK && current && operation.principal.role == USER_ROLE_ADMIN &&
|
||||
esp_timer_get_time() < operation.deadline) {
|
||||
bool committed = false;
|
||||
error = operation.action == 0 ? web_server_stop_current(operation.generation) :
|
||||
operation.action == 1 ? web_server_restart_current(operation.generation) :
|
||||
web_server_reboot_current(operation.generation);
|
||||
operation.action == 2 ? web_server_reboot_current(operation.generation) :
|
||||
web_server_replace_identity(operation.generation, operation.identity_generation, false, &committed);
|
||||
/* Even INVALID_STATE can be a detach failure after stop admission. */
|
||||
state = error == ESP_OK ? OK : FAILED;
|
||||
}
|
||||
@@ -250,11 +259,22 @@ esp_err_t web_lifecycle_settings_handler(httpd_req_t *request)
|
||||
if (web_server_get_management_snapshot(&snapshot) != ESP_OK) {
|
||||
error = respond(request, "503 Service Unavailable", "{\"error\":\"lifecycle_unavailable\"}"); goto done;
|
||||
}
|
||||
char response[128];
|
||||
web_security_identity_snapshot_t identity = {0};
|
||||
bool available = web_security_get_identity_snapshot(&identity) == ESP_OK;
|
||||
char fingerprint[65] = {0};
|
||||
if (available) {
|
||||
for (size_t i = 0; i < sizeof(identity.fingerprint); ++i)
|
||||
snprintf(fingerprint + i * 2, 3, "%02x", identity.fingerprint[i]);
|
||||
}
|
||||
char response[320];
|
||||
int written = snprintf(response, sizeof(response),
|
||||
"{\"generation\":%" PRIu32 ",\"running\":%s,\"transitioning\":%s,\"controllable\":%s}",
|
||||
"{\"generation\":%" PRIu32 ",\"running\":%s,\"transitioning\":%s,\"controllable\":%s,"
|
||||
"\"identity_generation\":%" PRIu32 ",\"fingerprint\":\"%s\",\"rotatable\":%s}",
|
||||
snapshot.generation, snapshot.running ? "true" : "false",
|
||||
snapshot.transitioning ? "true" : "false", snapshot.controllable ? "true" : "false");
|
||||
snapshot.transitioning ? "true" : "false", snapshot.controllable ? "true" : "false",
|
||||
available ? identity.generation : 0, fingerprint,
|
||||
available && snapshot.controllable && !identity.busy && identity.generation &&
|
||||
identity.generation != UINT32_MAX ? "true" : "false");
|
||||
error = written < 0 || (size_t)written >= sizeof(response) ? ESP_FAIL : respond(request, "200 OK", response);
|
||||
done:
|
||||
secure_wipe(&view, sizeof(view));
|
||||
|
||||
Reference in New Issue
Block a user