Coredump and Duo Auth

This commit is contained in:
iranl
2025-01-21 21:41:43 +01:00
parent 6c3a0f078d
commit 2964f54e96
13 changed files with 356 additions and 52 deletions

View File

@@ -16,6 +16,7 @@
#endif
#include <Update.h>
#include <DuoAuthLib.h>
#include "driver/gpio.h"
extern const uint8_t x509_crt_imported_bundle_bin_start[] asm("_binary_x509_crt_bundle_start");
extern const uint8_t x509_crt_imported_bundle_bin_end[] asm("_binary_x509_crt_bundle_end");
@@ -60,6 +61,15 @@ WebCfgServer::WebCfgServer(NukiNetwork* network, Preferences* preferences, bool
{
_duoEnabled = false;
}
else if (_preferences->getBool(preference_cred_bypass_boot_btn_enabled, false) || _preferences->getInt(preference_cred_bypass_gpio_high, -1) > -1 || _preferences->getInt(preference_cred_bypass_gpio_low, -1) > -1)
{
if (_preferences->getBool(preference_cred_bypass_boot_btn_enabled, false))
{
_bypassGPIO = true;
}
_bypassGPIOHigh = _preferences->getInt(preference_cred_bypass_gpio_high, -1);
_bypassGPIOLow = _preferences->getInt(preference_cred_bypass_gpio_low, -1);
}
}
if(str.length() > 0)
@@ -288,6 +298,30 @@ int WebCfgServer::doAuthentication(PsychicRequest *request)
}
else if (_duoEnabled && !isAuthenticated(request, true))
{
if (_bypassGPIO)
{
if (digitalRead(BOOT_BUTTON_GPIO) == LOW)
{
Log->print("Duo bypassed because boot button pressed");
return 4;
}
}
if (_bypassGPIOHigh > -1)
{
if (digitalRead(_bypassGPIOHigh) == HIGH)
{
Log->print("Duo bypassed because bypass GPIO pin pulled high");
return 4;
}
}
if (_bypassGPIOLow > -1)
{
if (digitalRead(_bypassGPIOLow) == LOW)
{
Log->print("Duo bypassed because bypass GPIO pin pulled low");
return 4;
}
}
return 3;
}
}
@@ -299,6 +333,30 @@ int WebCfgServer::doAuthentication(PsychicRequest *request)
}
else if (_duoEnabled && !isAuthenticated(request, true))
{
if (_bypassGPIO)
{
if (digitalRead(BOOT_BUTTON_GPIO) == LOW)
{
Log->print("Duo bypassed because boot button pressed");
return 4;
}
}
if (_bypassGPIOHigh > -1)
{
if (digitalRead(_bypassGPIOHigh) == HIGH)
{
Log->print("Duo bypassed because bypass GPIO pin pulled high");
return 4;
}
}
if (_bypassGPIOLow > -1)
{
if (digitalRead(_bypassGPIOLow) == LOW)
{
Log->print("Duo bypassed because bypass GPIO pin pulled low");
return 4;
}
}
return 3;
}
}
@@ -307,7 +365,7 @@ int WebCfgServer::doAuthentication(PsychicRequest *request)
return 4;
}
bool WebCfgServer::startDuoAuth()
bool WebCfgServer::startDuoAuth(char* pushType)
{
int64_t timeout = esp_timer_get_time() - (30 * 1000 * 1000L);
if(!_duoActiveRequest || timeout > _duoRequestTS)
@@ -320,6 +378,7 @@ bool WebCfgServer::startDuoAuth()
DuoAuthLib duoAuth;
bool duoRequestResult;
duoAuth.begin(duo_host, duo_ikey, duo_skey, &timeinfo);
duoAuth.setPushType(pushType);
duoRequestResult = duoAuth.pushAuth((char*)duo_user, true);
if(duoRequestResult == true)
@@ -345,25 +404,10 @@ int WebCfgServer::checkDuoAuth(PsychicRequest *request)
const char* duo_ikey = _duoIkey.c_str();
const char* duo_skey = _duoSkey.c_str();
const char* duo_user = _duoUser.c_str();
Log->println("Checking Duo auth");
if (request->hasParam("id")) {
Log->println("Found Duo ID");
const PsychicWebParameter* p = request->getParam("id");
String cookie2 = p->value();
Log->print("_duoActiveRequest: ");
Log->println(_duoActiveRequest ? "Yes" : "No");
Log->print("_duoCheckIP: ");
Log->println(_duoCheckIP);
Log->print("clientIP: ");
Log->println(request->client()->localIP().toString());
Log->print("cookie2: ");
Log->println(cookie2);
Log->print("_duoCheckId: ");
Log->println(_duoCheckId);
DuoAuthLib duoAuth;
if(_duoActiveRequest && _duoCheckIP == request->client()->localIP().toString() && cookie2 == _duoCheckId)
{
@@ -642,6 +686,10 @@ void WebCfgServer::initialize()
{
return buildDuoCheckHtml(request, resp);
}
else if (value == "coredump")
{
return buildCoredumpHtml(request, resp);
}
else if (value == "reboot")
{
String value = "";
@@ -686,6 +734,26 @@ void WebCfgServer::initialize()
}
else if (value == "export")
{
if(_preferences->getBool(preference_cred_duo_approval, false))
{
if (startDuoAuth((char*)"Approve Nuki Hub export"))
{
int duoResult = 2;
while (duoResult == 2)
{
duoResult = checkDuoApprove();
delay(2000);
esp_task_wdt_reset();
}
if (duoResult != 1)
{
return buildConfirmHtml(request, resp, "Duo approval failed, redirecting to main menu", 3, true);
}
}
}
return sendSettings(request, resp);
}
else if (value == "impexpcfg")
@@ -881,22 +949,20 @@ void WebCfgServer::initialize()
if(_preferences->getBool(preference_cred_duo_approval, false))
{
if (startDuoAuth())
if (startDuoAuth((char*)"Approve Nuki Hub setting change"))
{
int duoResult = 2;
while (duoResult == 2)
{
duoResult = checkDuoApprove();
delay(2000);
esp_task_wdt_reset();
}
if (duoResult != 1)
{
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
return buildConfirmHtml(request, resp, "Duo approval failed, redirecting to main menu", 3, true);
}
}
}
@@ -1856,9 +1922,38 @@ esp_err_t WebCfgServer::buildDuoCheckHtml(PsychicRequest *request, PsychicRespon
return resp->send();
}
esp_err_t WebCfgServer::buildCoredumpHtml(PsychicRequest *request, PsychicResponse* resp)
{
if (!SPIFFS.begin(true))
{
Log->println("SPIFFS Mount Failed");
}
else
{
File file = SPIFFS.open("/coredump.hex", "r");
if (!file || file.isDirectory()) {
Log->println("coredump.hex not found");
}
else
{
PsychicFileResponse response(resp, file, "coredump.hex");
String name = "coredump.txt";
char buf[26 + name.length()];
snprintf(buf, sizeof(buf), "attachment; filename=\"%s\"", name.c_str());
response.addHeader("Content-Disposition", buf);
return response.send();
}
}
resp->setCode(302);
resp->addHeader("Cache-Control", "no-cache");
return resp->redirect("/");
}
esp_err_t WebCfgServer::buildDuoHtml(PsychicRequest *request, PsychicResponse* resp)
{
bool duo = startDuoAuth();
bool duo = startDuoAuth((char*)"Approve Nuki Hub login");
if (!duo)
{
@@ -1891,14 +1986,14 @@ esp_err_t WebCfgServer::buildDuoHtml(PsychicRequest *request, PsychicResponse* r
_duoCheckIP = request->client()->localIP().toString();
_duoCheckId = buffer;
response.beginSend();
response.print("<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">");
response.print("<style>.container{border:3px solid #f1f1f1; max-width: 400px; padding:16px}</style>");
response.print((String)"<script>let intervalId; window.onload = function() { updateInfo(); intervalId = setInterval(updateInfo, 2000); }; function updateInfo() { var request = new XMLHttpRequest(); request.open('GET', '/get?page=duocheck&id=" + _duoCheckId + "', true); request.onload = () => { const obj = request.responseText; if (obj == \"1\" || obj == \"0\") { clearInterval(intervalId); window.location.href = \"/\"; } }; request.send(); }</script>");
response.print((String)"<script>let intervalId; window.onload = function() { updateInfo(); intervalId = setInterval(updateInfo, 2000); }; function updateInfo() { var request = new XMLHttpRequest(); request.open('GET', '/get?page=duocheck&id=" + _duoCheckId + "', true); request.onload = () => { const obj = request.responseText; if (obj == \"1\" || obj == \"0\") { clearInterval(intervalId); if (obj == \"1\") { document.getElementById('duoresult').innerHTML = 'Login approved<br>Redirecting...'; setTimeout(function() { window.location.href = \"/\"; }, 2000); } else { document.getElementById('duoresult').innerHTML = 'Login failed<br>Refresh to retry'; } } }; request.send(); }</script>");
response.print("</head><body><center><h2>Nuki Hub login</h2>");
response.print("<div class=\"container\">Duo Push sent<br><br>");
response.print("Please confirm login in the Duo app<br><br></div>");
response.print("Please confirm login in the Duo app<br><br><div id=\"duoresult\"></div></div>");
response.print("</div>");
response.print("</center></body></html>");
@@ -2847,6 +2942,36 @@ bool WebCfgServer::processArgs(PsychicRequest *request, PsychicResponse* resp, S
newMFA = true;
}
}
else if(key == "DUOBYPASS")
{
if(_preferences->getBool(preference_cred_bypass_boot_btn_enabled, false) != (value == "1"))
{
_preferences->putBool(preference_cred_bypass_boot_btn_enabled, (value == "1"));
Log->print(("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
else if(key == "DUOBYPASSHIGH")
{
if(_preferences->getInt(preference_cred_bypass_gpio_high, -1) != value.toInt())
{
_preferences->putInt(preference_cred_bypass_gpio_high, value.toInt());
Log->print(("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
else if(key == "DUOBYPASSLOW")
{
if(_preferences->getInt(preference_cred_bypass_gpio_low, -1) != value.toInt())
{
_preferences->putInt(preference_cred_bypass_gpio_low, value.toInt());
Log->print(("Setting changed: "));
Log->println(key);
configChanged = true;
}
}
else if(key == "DUOAPPROVAL")
{
if(_preferences->getBool(preference_cred_duo_approval, false) != (value == "1"))
@@ -4761,6 +4886,7 @@ esp_err_t WebCfgServer::buildImportExportHtml(PsychicRequest *request, PsychicRe
}
#endif
response.print("<br><br><button title=\"Export MQTT SSL CA, client certificate and client key\" onclick=\" window.open('/get?page=export&type=mqtts'); return false;\">Export MQTT SSL CA, client certificate and client key</button>");
response.print("<br><br><button title=\"Export Coredump\" onclick=\" window.open('/get?page=coredump'); return false;\">Export Coredump</button>");
response.print("</div></body></html>");
return response.endSend();
}
@@ -4919,6 +5045,9 @@ esp_err_t WebCfgServer::buildCredHtml(PsychicRequest *request, PsychicResponse*
printInputField(&response, "CREDTRUSTPROXY", "Bypass authentication for reverse proxy with IP", _preferences->getString(preference_bypass_proxy, "").c_str(), 255, "");
printCheckBox(&response, "DUOENA", "Duo Push authentication enabled", _preferences->getBool(preference_cred_duo_enabled, false), "");
printCheckBox(&response, "DUOAPPROVAL", "Require Duo Push authentication for all sensitive Nuki Hub operations (changing/exporting settings)", _preferences->getBool(preference_cred_duo_approval, false), "");
printCheckBox(&response, "DUOBYPASS", "Bypass Duo Push authentication by pressing the BOOT button while logging in", _preferences->getBool(preference_cred_bypass_boot_btn_enabled, false), "");
printInputField(&response, "DUOBYPASSHIGH", "Bypass Duo Push authentication by pulling GPIO High", _preferences->getInt(preference_cred_bypass_gpio_high, -1), 2, "");
printInputField(&response, "DUOBYPASSLOW", "Bypass Duo Push authentication by pulling GPIO Low", _preferences->getInt(preference_cred_bypass_gpio_low, -1), 2, "");
printInputField(&response, "DUOHOST", "Duo API hostname", "*", 255, "", true, false);
printInputField(&response, "DUOIKEY", "Duo integration key", "*", 255, "", true, false);
printInputField(&response, "DUOSKEY", "Duo secret key", "*", 255, "", true, false);