Replace Web Basic Auth With Cookie Sessions
Add bounded login challenges, CSRF/origin enforcement, logout, and session-bound WebSocket admission. Isolate private HTTPD access behind a version-guarded adapter and add focused host coverage. Also let empty admin SSH input reach the normal console handler.
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Production policy/store/adapter with bounded HTTPD and database doubles.
|
||||
|
||||
Getter and append-header functions are extracted verbatim from installed IDF,
|
||||
not reimplemented with convenient merging or overwrite semantics.
|
||||
"""
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
|
||||
sys.dont_write_bytecode = True
|
||||
os.environ["CCACHE_DISABLE"] = "1"
|
||||
HERE = pathlib.Path(__file__).resolve().parent
|
||||
ROOT = HERE.parents[1]
|
||||
sys.path.insert(0, str(HERE.parent / "web_session_store"))
|
||||
from run import HEADERS
|
||||
from serial_headers import SERIAL_HEADERS
|
||||
|
||||
HEADERS.update(SERIAL_HEADERS)
|
||||
HEADERS["esp_http_server.h"] += """
|
||||
#define ESP_ERR_HTTPD_INVALID_REQ 0x200
|
||||
#define ESP_ERR_HTTPD_RESULT_TRUNC 0x201
|
||||
#define ESP_ERR_HTTPD_RESP_HDR 0x202
|
||||
esp_err_t httpd_resp_sendstr(httpd_req_t *, const char *);
|
||||
int httpd_req_recv(httpd_req_t *, char *, size_t);
|
||||
"""
|
||||
HEADERS["esp_idf_version.h"] = """
|
||||
#define ESP_IDF_VERSION_VAL(a,b,c) ((a)*10000+(b)*100+(c))
|
||||
#define ESP_IDF_VERSION ESP_IDF_VERSION_VAL(5,5,0)
|
||||
"""
|
||||
HEADERS["esp_httpd_priv.h"] = """#pragma once
|
||||
#include "esp_http_server.h"
|
||||
struct sock_db { bool ws_handshake_done; esp_err_t (*ws_handler)(httpd_req_t *);
|
||||
bool ws_control_frames; void *ws_user_ctx; char pending_data[128]; size_t pending_len; };
|
||||
struct httpd_req_aux { struct sock_db *sd; char *scratch; size_t scratch_cur_size, remaining_len;
|
||||
unsigned req_hdrs_count, resp_hdrs_count; bool ws_handshake_detect;
|
||||
struct resp_hdr { const char *field, *value; } *resp_hdrs; };
|
||||
struct httpd_data { struct { unsigned max_resp_headers; } config; };
|
||||
esp_err_t httpd_ws_respond_server_handshake(httpd_req_t *, const char *);
|
||||
"""
|
||||
|
||||
def function(source, name):
|
||||
start = source.index(name + "(")
|
||||
start = source.rfind("\n", 0, start) + 1
|
||||
end = source.index("\n}", start) + 2
|
||||
return source[start:end]
|
||||
|
||||
idf = pathlib.Path(os.environ.get("IDF_PATH", str(pathlib.Path.home() / ".platformio/packages/framework-espidf")))
|
||||
parse = (idf / "components/esp_http_server/src/httpd_parse.c").read_text()
|
||||
txrx = (idf / "components/esp_http_server/src/httpd_txrx.c").read_text()
|
||||
extracted = """
|
||||
#pragma GCC diagnostic ignored "-Wsign-compare"
|
||||
#include <string.h>
|
||||
#include <strings.h>
|
||||
#include "esp_httpd_priv.h"
|
||||
#define ESP_LOGD(...) ((void)0)
|
||||
#define MIN(a,b) ((a) < (b) ? (a) : (b))
|
||||
static bool httpd_valid_req(httpd_req_t *r) { return r && r->aux; }
|
||||
static size_t strlcpy(char *d, const char *s, size_t n) {
|
||||
size_t len = strlen(s); if (n) { size_t m = len < n-1 ? len : n-1; memcpy(d,s,m); d[m]=0; } return len;
|
||||
}
|
||||
"""
|
||||
for name in ["httpd_req_get_hdr_value_len", "httpd_req_get_hdr_value_str"]:
|
||||
# The declarations start at line beginning; avoid earlier calls in parser.
|
||||
prefix = "size_t " if name.endswith("len") else "esp_err_t "
|
||||
extracted += function(parse[parse.index(prefix + name):], name) + "\n"
|
||||
extracted += function(txrx[txrx.index("esp_err_t httpd_resp_set_hdr"):], "httpd_resp_set_hdr")
|
||||
extracted += "\n" + function(txrx[txrx.index("static size_t httpd_recv_pending"):], "httpd_recv_pending")
|
||||
extracted += "\nsize_t host_read_pending(httpd_req_t *r, char *out, size_t n) { return httpd_recv_pending(r, out, n); }\n"
|
||||
|
||||
with tempfile.TemporaryDirectory(prefix="web-cookie-auth-") as directory:
|
||||
tmp = pathlib.Path(directory)
|
||||
for name, text in HEADERS.items():
|
||||
path = tmp / name
|
||||
path.parent.mkdir(parents=True, exist_ok=True)
|
||||
path.write_text(text)
|
||||
(tmp / "installed_httpd.c").write_text(extracted)
|
||||
sources = [HERE / "test.c", tmp / "installed_httpd.c"]
|
||||
sources += [ROOT / "src" / name for name in ["web_session_store.c", "web_auth_parse.c", "web_cookie_auth.c", "web_httpd_adapter.c"]]
|
||||
subprocess.run(["cc", "-std=c11", "-Wall", "-Wextra", "-Werror", "-g", "-DHOST_OPENSSL",
|
||||
"-I" + str(tmp), "-I" + str(ROOT / "src"), *map(str, sources), "-lcrypto",
|
||||
"-o", str(tmp / "test")], check=True, timeout=30)
|
||||
subprocess.run([str(tmp / "test")], check=True, timeout=20)
|
||||
Reference in New Issue
Block a user