Implemented certbot helper utility
This commit is contained in:
parent
1e2469c9b5
commit
ffaa0394db
6 changed files with 224 additions and 14 deletions
|
@ -342,6 +342,7 @@ endmacro()
|
||||||
makeUtil(Stats stats)
|
makeUtil(Stats stats)
|
||||||
makeUtil(RAX rax)
|
makeUtil(RAX rax)
|
||||||
makeUtil(AMF amf)
|
makeUtil(AMF amf)
|
||||||
|
makeUtil(Certbot certbot)
|
||||||
if (DEFINED LOAD_BALANCE )
|
if (DEFINED LOAD_BALANCE )
|
||||||
makeUtil(Load load)
|
makeUtil(Load load)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
@ -652,6 +652,10 @@ void Controller::handleAPICommands(JSON::Value & Request, JSON::Value & Response
|
||||||
if (Request["updateprotocol"].isArray() && Request["updateprotocol"].size() == 2){
|
if (Request["updateprotocol"].isArray() && Request["updateprotocol"].size() == 2){
|
||||||
jsonForEach(Controller::Storage["config"]["protocols"], it){
|
jsonForEach(Controller::Storage["config"]["protocols"], it){
|
||||||
if ((*it).compareExcept(Request["updateprotocol"][0u], ignores)){
|
if ((*it).compareExcept(Request["updateprotocol"][0u], ignores)){
|
||||||
|
//If the connector type didn't change, mark it as needing a reload
|
||||||
|
if ((*it)["connector"] == Request["updateprotocol"][1u]["connector"]){
|
||||||
|
reloadProtocol(it.num());
|
||||||
|
}
|
||||||
(*it) = Request["updateprotocol"][1u];
|
(*it) = Request["updateprotocol"][1u];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,13 @@
|
||||||
///\brief Holds everything unique to the controller.
|
///\brief Holds everything unique to the controller.
|
||||||
namespace Controller {
|
namespace Controller {
|
||||||
|
|
||||||
|
static std::set<size_t> needsReload; ///< List of connector indices that needs a reload
|
||||||
static std::map<std::string, pid_t> currentConnectors; ///<The currently running connectors.
|
static std::map<std::string, pid_t> currentConnectors; ///<The currently running connectors.
|
||||||
|
|
||||||
|
void reloadProtocol(size_t indice){
|
||||||
|
needsReload.insert(indice);
|
||||||
|
}
|
||||||
|
|
||||||
/// Updates the shared memory page with active connectors
|
/// Updates the shared memory page with active connectors
|
||||||
void saveActiveConnectors(bool forceOverride){
|
void saveActiveConnectors(bool forceOverride){
|
||||||
IPC::sharedPage f("MstCnns", 4096, forceOverride, false);
|
IPC::sharedPage f("MstCnns", 4096, forceOverride, false);
|
||||||
|
@ -91,18 +96,6 @@ namespace Controller {
|
||||||
currentConnectors.clear();
|
currentConnectors.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
///\brief Checks if the binary mentioned in the protocol argument is currently active, if so, restarts it.
|
|
||||||
///\param protocol The protocol to check.
|
|
||||||
void UpdateProtocol(std::string protocol){
|
|
||||||
std::map<std::string, pid_t>::iterator iter;
|
|
||||||
for (iter = currentConnectors.begin(); iter != currentConnectors.end(); iter++){
|
|
||||||
if (iter->first.substr(0, protocol.size()) == protocol){
|
|
||||||
Log("CONF", "Killing connector for update: " + iter->first);
|
|
||||||
Util::Procs::Stop(iter->second);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static inline void builPipedPart(JSON::Value & p, char * argarr[], int & argnum, const JSON::Value & argset){
|
static inline void builPipedPart(JSON::Value & p, char * argarr[], int & argnum, const JSON::Value & argset){
|
||||||
jsonForEachConst(argset, it) {
|
jsonForEachConst(argset, it) {
|
||||||
if (it->isMember("option")){
|
if (it->isMember("option")){
|
||||||
|
@ -214,6 +207,11 @@ namespace Controller {
|
||||||
runningConns.insert(myCmd);
|
runningConns.insert(myCmd);
|
||||||
if (currentConnectors.count(myCmd) && Util::Procs::isActive(currentConnectors[myCmd])){
|
if (currentConnectors.count(myCmd) && Util::Procs::isActive(currentConnectors[myCmd])){
|
||||||
( *ait)["online"] = 1;
|
( *ait)["online"] = 1;
|
||||||
|
//Reload connectors that need it
|
||||||
|
if (needsReload.count(ait.num())){
|
||||||
|
kill(currentConnectors[myCmd], SIGUSR1);
|
||||||
|
needsReload.erase(ait.num());
|
||||||
|
}
|
||||||
}else{
|
}else{
|
||||||
( *ait)["online"] = 0;
|
( *ait)["online"] = 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
namespace Controller {
|
namespace Controller {
|
||||||
|
|
||||||
/// Checks if the binary mentioned in the protocol argument is currently active, if so, restarts it.
|
/// Marks the given protocol as needing a reload (signal USR1) on next check
|
||||||
void UpdateProtocol(std::string protocol);
|
void reloadProtocol(size_t indice);
|
||||||
|
|
||||||
/// Checks current protocol configuration, updates state of enabled connectors if neccesary.
|
/// Checks current protocol configuration, updates state of enabled connectors if neccesary.
|
||||||
bool CheckProtocols(JSON::Value & p, const JSON::Value & capabilities);
|
bool CheckProtocols(JSON::Value & p, const JSON::Value & capabilities);
|
||||||
|
|
|
@ -131,6 +131,7 @@ namespace Mist {
|
||||||
capa["url_match"].append("/embed_$.js");
|
capa["url_match"].append("/embed_$.js");
|
||||||
capa["url_match"].append("/flashplayer.swf");
|
capa["url_match"].append("/flashplayer.swf");
|
||||||
capa["url_match"].append("/oldflashplayer.swf");
|
capa["url_match"].append("/oldflashplayer.swf");
|
||||||
|
capa["url_prefix"] = "/.well-known/";
|
||||||
capa["optional"]["wrappers"]["name"] = "Active players";
|
capa["optional"]["wrappers"]["name"] = "Active players";
|
||||||
capa["optional"]["wrappers"]["help"] = "Which players are attempted and in what order.";
|
capa["optional"]["wrappers"]["help"] = "Which players are attempted and in what order.";
|
||||||
capa["optional"]["wrappers"]["default"] = "";
|
capa["optional"]["wrappers"]["default"] = "";
|
||||||
|
@ -142,6 +143,12 @@ namespace Mist {
|
||||||
capa["optional"]["wrappers"]["allowed"].append("flash_strobe");
|
capa["optional"]["wrappers"]["allowed"].append("flash_strobe");
|
||||||
capa["optional"]["wrappers"]["option"] = "--wrappers";
|
capa["optional"]["wrappers"]["option"] = "--wrappers";
|
||||||
capa["optional"]["wrappers"]["short"] = "w";
|
capa["optional"]["wrappers"]["short"] = "w";
|
||||||
|
capa["optional"]["certbot"]["name"] = "Certbot validation token";
|
||||||
|
capa["optional"]["certbot"]["help"] = "Automatically set by the MistUtilCertbot authentication hook for certbot. Not intended to be set manually.";
|
||||||
|
capa["optional"]["certbot"]["default"] = "";
|
||||||
|
capa["optional"]["certbot"]["type"] = "str";
|
||||||
|
capa["optional"]["certbot"]["option"] = "--certbot";
|
||||||
|
capa["optional"]["certbot"]["short"] = "C";
|
||||||
cfg->addConnectorOptions(8080, capa);
|
cfg->addConnectorOptions(8080, capa);
|
||||||
/*LTS-START*/
|
/*LTS-START*/
|
||||||
cfg->addOption("nostreamtext", JSON::fromString("{\"arg\":\"string\", \"default\":\"\", \"short\":\"t\",\"long\":\"nostreamtext\",\"help\":\"Text or HTML to display when streams are unavailable.\"}"));
|
cfg->addOption("nostreamtext", JSON::fromString("{\"arg\":\"string\", \"default\":\"\", \"short\":\"t\",\"long\":\"nostreamtext\",\"help\":\"Text or HTML to display when streams are unavailable.\"}"));
|
||||||
|
@ -492,6 +499,31 @@ namespace Mist {
|
||||||
|
|
||||||
void OutHTTP::onHTTP(){
|
void OutHTTP::onHTTP(){
|
||||||
std::string method = H.method;
|
std::string method = H.method;
|
||||||
|
|
||||||
|
//Handle certbot validations
|
||||||
|
if (H.url.substr(0, 28) == "/.well-known/acme-challenge/"){
|
||||||
|
std::string cbToken = H.url.substr(28);
|
||||||
|
jsonForEach(config->getOption("certbot",true),it){
|
||||||
|
if (it->asStringRef().substr(0, cbToken.size()+1) == cbToken+":"){
|
||||||
|
H.Clean();
|
||||||
|
H.SetHeader("Content-Type", "text/plain");
|
||||||
|
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
|
||||||
|
H.setCORSHeaders();
|
||||||
|
H.SetBody(it->asStringRef().substr(cbToken.size()+1));
|
||||||
|
H.SendResponse("200", "OK", myConn);
|
||||||
|
H.Clean();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
H.Clean();
|
||||||
|
H.SetHeader("Content-Type", "text/plain");
|
||||||
|
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
|
||||||
|
H.setCORSHeaders();
|
||||||
|
H.SetBody("No matching validation found for token '" + cbToken + "'");
|
||||||
|
H.SendResponse("404", "Not found", myConn);
|
||||||
|
H.Clean();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (H.url == "/crossdomain.xml"){
|
if (H.url == "/crossdomain.xml"){
|
||||||
H.Clean();
|
H.Clean();
|
||||||
|
|
175
src/utils/util_certbot.cpp
Normal file
175
src/utils/util_certbot.cpp
Normal file
|
@ -0,0 +1,175 @@
|
||||||
|
/// \file util_certbot.cpp
|
||||||
|
/// Certbot integration utility
|
||||||
|
/// Intended to be ran like so:
|
||||||
|
//certbot certonly --manual --preferred-challenges=http --manual-auth-hook MistUtilCertbot --deploy-hook MistUtilCertbot -d yourdomain.example.com
|
||||||
|
|
||||||
|
|
||||||
|
//When called from --deploy-hook:
|
||||||
|
//RENEWED_LINEAGE: directory with the certificate
|
||||||
|
//RENEWED_DOMAINS: space-delimited list of domains
|
||||||
|
|
||||||
|
//When called from --manual-auth-hook:
|
||||||
|
//CERTBOT_DOMAIN: The domain being authenticated
|
||||||
|
//CERTBOT_VALIDATION: The validation string
|
||||||
|
//CERTBOT_TOKEN: Resource name part of the HTTP-01 challenge
|
||||||
|
|
||||||
|
#include <mist/defines.h>
|
||||||
|
#include <mist/stream.h>
|
||||||
|
#include <mist/config.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
///Checks if port 80 is HTTP, returns the indice number (>= 0) if it is.
|
||||||
|
///Returns -1 if nothing is running on port 80.
|
||||||
|
///Returns -2 if the port is taken by another protocol (and prints a FAIL-level message).
|
||||||
|
///If found, sets currConf to the current configuration of the HTTP protocol.
|
||||||
|
int checkPort80(JSON::Value & currConf){
|
||||||
|
Util::DTSCShmReader rCapa(SHM_CAPA);
|
||||||
|
DTSC::Scan conns = rCapa.getMember("connectors");
|
||||||
|
Util::DTSCShmReader rProto(SHM_PROTO);
|
||||||
|
DTSC::Scan prtcls = rProto.getScan();
|
||||||
|
unsigned int pro_cnt = prtcls.getSize();
|
||||||
|
for (unsigned int i = 0; i < pro_cnt; ++i){
|
||||||
|
std::string ctor = prtcls.getIndice(i).getMember("connector").asString();
|
||||||
|
DTSC::Scan capa = conns.getMember(ctor);
|
||||||
|
uint16_t port = prtcls.getIndice(i).getMember("port").asInt();
|
||||||
|
//get the default port if none is set
|
||||||
|
if (!port){
|
||||||
|
port = capa.getMember("optional").getMember("port").getMember("default").asInt();
|
||||||
|
}
|
||||||
|
//Found a port 80 entry?
|
||||||
|
if (port == 80){
|
||||||
|
if (ctor == "HTTP" || ctor == "HTTP.exe"){
|
||||||
|
currConf = prtcls.getIndice(i).asJSON();
|
||||||
|
return i;
|
||||||
|
}else{
|
||||||
|
FAIL_MSG("Found non-HTTP protocol %s on port 80; aborting! Please free up port 80 for HTTP", ctor.c_str());
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv){
|
||||||
|
Util::redirectLogsIfNeeded();
|
||||||
|
Util::Config conf(argv[0]);
|
||||||
|
conf.parseArgs(argc, argv);
|
||||||
|
|
||||||
|
//Handle --manual-auth-hook
|
||||||
|
if (getenv("CERTBOT_VALIDATION") && getenv("CERTBOT_TOKEN")){
|
||||||
|
INFO_MSG("Detected '--manual-auth-hook' calling. Performing authentication.");
|
||||||
|
//Store certbot variables for later use
|
||||||
|
std::string cbValidation = getenv("CERTBOT_VALIDATION");
|
||||||
|
std::string cbToken = getenv("CERTBOT_TOKEN");
|
||||||
|
std::string cbCombo = cbToken+":"+cbValidation;
|
||||||
|
//Check Mist config, find HTTP output, check config
|
||||||
|
JSON::Value currConf;
|
||||||
|
int foundHTTP80 = checkPort80(currConf);
|
||||||
|
if (foundHTTP80 == -2){return 1;}//abort if port already taken by non-HTTP process
|
||||||
|
if (foundHTTP80 == -1){
|
||||||
|
INFO_MSG("Nothing on port 80 found - adding HTTP connector on port 80 with correct config for certbot");
|
||||||
|
JSON::Value cmd;
|
||||||
|
cmd["addprotocol"]["connector"] = "HTTP";
|
||||||
|
cmd["addprotocol"]["port"] = 80;
|
||||||
|
cmd["addprotocol"]["certbot"] = cbCombo;
|
||||||
|
Socket::UDPConnection uSock;
|
||||||
|
uSock.SetDestination(UDP_API_HOST, UDP_API_PORT);
|
||||||
|
uSock.SendNow(cmd.toString());
|
||||||
|
Util::wait(1000);
|
||||||
|
int counter = 10;
|
||||||
|
while (--counter && ((foundHTTP80 = checkPort80(currConf)) == -1 || currConf["certbot"].asStringRef() != cbCombo)){
|
||||||
|
INFO_MSG("Waiting for Controller to pick up new config...");
|
||||||
|
uSock.SendNow(cmd.toString());
|
||||||
|
Util::wait(1000);
|
||||||
|
}
|
||||||
|
if (!counter){
|
||||||
|
FAIL_MSG("Timed out!");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
INFO_MSG("Success!");
|
||||||
|
Util::wait(5000);
|
||||||
|
}else{
|
||||||
|
if (currConf["certbot"].asStringRef() == cbCombo){
|
||||||
|
INFO_MSG("Config already good - no changes needed");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
INFO_MSG("Found HTTP on port 80; updating config...");
|
||||||
|
JSON::Value cmd;
|
||||||
|
cmd["updateprotocol"].append(currConf);
|
||||||
|
cmd["updateprotocol"].append(currConf);
|
||||||
|
cmd["updateprotocol"][1u]["certbot"] = cbCombo;
|
||||||
|
Socket::UDPConnection uSock;
|
||||||
|
uSock.SetDestination(UDP_API_HOST, UDP_API_PORT);
|
||||||
|
uSock.SendNow(cmd.toString());
|
||||||
|
Util::wait(1000);
|
||||||
|
int counter = 10;
|
||||||
|
while (--counter && ((foundHTTP80 = checkPort80(currConf)) == -1 || currConf["certbot"].asStringRef() != cbCombo)){
|
||||||
|
INFO_MSG("Waiting for Controller to pick up new config...");
|
||||||
|
uSock.SendNow(cmd.toString());
|
||||||
|
Util::wait(1000);
|
||||||
|
}
|
||||||
|
if (!counter){
|
||||||
|
FAIL_MSG("Timed out!");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
INFO_MSG("Success!");
|
||||||
|
Util::wait(5000);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Handle --deploy-hook
|
||||||
|
if (getenv("RENEWED_LINEAGE")){
|
||||||
|
INFO_MSG("Detected '--deploy-hook' calling. Installing certificate.");
|
||||||
|
std::string cbPath = getenv("RENEWED_LINEAGE");
|
||||||
|
std::string cbCert = cbPath + "/fullchain.pem";
|
||||||
|
std::string cbKey = cbPath + "/privkey.pem";
|
||||||
|
Socket::UDPConnection uSock;
|
||||||
|
uSock.SetDestination(UDP_API_HOST, UDP_API_PORT);
|
||||||
|
Util::DTSCShmReader rProto(SHM_PROTO);
|
||||||
|
DTSC::Scan prtcls = rProto.getScan();
|
||||||
|
unsigned int pro_cnt = prtcls.getSize();
|
||||||
|
bool found = false;
|
||||||
|
for (unsigned int i = 0; i < pro_cnt; ++i){
|
||||||
|
std::string ctor = prtcls.getIndice(i).getMember("connector").asString();
|
||||||
|
if (ctor == "HTTPS"){
|
||||||
|
found = true;
|
||||||
|
JSON::Value currConf = prtcls.getIndice(i).asJSON();
|
||||||
|
JSON::Value cmd;
|
||||||
|
cmd["updateprotocol"].append(currConf);
|
||||||
|
cmd["updateprotocol"].append(currConf);
|
||||||
|
cmd["updateprotocol"][1u]["cert"] = cbCert;
|
||||||
|
cmd["updateprotocol"][1u]["key"] = cbKey;
|
||||||
|
INFO_MSG("Executing: %s", cmd.toString().c_str());
|
||||||
|
uSock.SendNow(cmd.toString());
|
||||||
|
Util::wait(500);
|
||||||
|
uSock.SendNow(cmd.toString());
|
||||||
|
Util::wait(500);
|
||||||
|
uSock.SendNow(cmd.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!found){
|
||||||
|
INFO_MSG("No HTTPS active; enabling on port 443");
|
||||||
|
JSON::Value cmd;
|
||||||
|
cmd["addprotocol"]["connector"] = "HTTPS";
|
||||||
|
cmd["addprotocol"]["port"] = 443;
|
||||||
|
cmd["addprotocol"]["cert"] = cbCert;
|
||||||
|
cmd["addprotocol"]["key"] = cbKey;
|
||||||
|
INFO_MSG("Executing: %s", cmd.toString().c_str());
|
||||||
|
uSock.SendNow(cmd.toString());
|
||||||
|
Util::wait(500);
|
||||||
|
uSock.SendNow(cmd.toString());
|
||||||
|
Util::wait(500);
|
||||||
|
uSock.SendNow(cmd.toString());
|
||||||
|
}
|
||||||
|
Util::wait(5000);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Print usage message to help point users in the right direction
|
||||||
|
FAIL_MSG("This utility is meant to be ran by certbot, not by hand.");
|
||||||
|
FAIL_MSG("Sample usage: certbot certonly --manual --preferred-challenges=http --manual-auth-hook MistUtilCertbot --deploy-hook MistUtilCertbot -d yourdomain.example.com");
|
||||||
|
WARN_MSG("Note: This utility will alter your MistServer configuration. If ran as deploy hook it will install the certificate generated by certbot to any already enabled HTTPS output or enable HTTPS on port 443 (if it was disabled). If ran as auth hook it will change the HTTP port to 80 (and enable HTTP if it wasn't enabled already) in order to perform the validation.");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue