From b3476881e11131940c563064e3b38bed004f6731 Mon Sep 17 00:00:00 2001 From: Christopher Vagnetoft Date: Tue, 12 Mar 2024 01:13:19 +0100 Subject: [PATCH] Configuration fixes, makefile tweaks * The PHAR now gets tagged with version and buildtime * WebSocket support can now be disabled --- Makefile | 1 + bin/mercureactd | 24 ++++++++++++++++-------- doc/SecurityNotes.md | 3 +++ mercureactd.conf.dist | 7 +++---- src/Configuration.php | 11 +++++++++++ src/Daemon.php | 21 +++++++++++++++++++-- src/Http/Server.php | 43 ++++++++++++++++++++++++------------------- 7 files changed, 77 insertions(+), 33 deletions(-) diff --git a/Makefile b/Makefile index 6973e36..90eccf2 100755 --- a/Makefile +++ b/Makefile @@ -13,6 +13,7 @@ phar: ## Build .phar using pharlite git clone $(REPODIR) $(BUILDDIR) && \ cd $(BUILDDIR) && \ composer install --no-dev && \ + echo " '$(shell git describe --tags)', 'buildtime' => '$(shell date)' ];" > src/meta && \ pharlite mv $(BUILDDIR)/*.phar $(REPODIR) && \ rm -rf $(BUILDDIR) diff --git a/bin/mercureactd b/bin/mercureactd index 4532d1c..8987e4e 100755 --- a/bin/mercureactd +++ b/bin/mercureactd @@ -3,15 +3,25 @@ use NoccyLabs\Mercureact\Configuration; use NoccyLabs\Mercureact\Daemon; -use PHPUnit\TextUI\Help; -require_once __DIR__."/../vendor/autoload.php"; +file_exists(__DIR__."/../vendor") && require_once __DIR__."/../vendor/autoload.php"; +file_exists(__DIR__."/../../../autoload.php") && require_once __DIR__."/../../../autoload.php"; + +if (file_exists(__DIR__."/../src/meta")) { + $meta = require_once(__DIR__."/../src/meta"); + define("MERCUREACT_VERSION", $meta['version']??'0.0.0'); + define("MERCUREACT_BUILDTIME", $meta['buildtime']); +} else { + define("MERCUREACT_VERSION", "DEV"); + define("MERCUREACT_BUILDTIME", null); +} $opts = getopt("c:C:h"); if (isset($opts['h'])) { + $info = "v".MERCUREACT_VERSION.(MERCUREACT_BUILDTIME?("\nBuilt on ".MERCUREACT_BUILDTIME):""); fwrite(STDERR, <<start(); \ No newline at end of file +$daemon->start(); diff --git a/doc/SecurityNotes.md b/doc/SecurityNotes.md index 82465bb..6a1b242 100644 --- a/doc/SecurityNotes.md +++ b/doc/SecurityNotes.md @@ -2,3 +2,6 @@ This is alpha quality software. Expect, and please report, any bugs encountered. +## Authentication + +When using the query thing for authentication, keep in mind that the authentication details may be saved to logs. `mercureactd` itself doese not log more than the path by default, but any downstream proxies may not be as caring. This is part of the reason why the Mercure specification doesn't care much for WebSockets -- they are hard to secure. diff --git a/mercureactd.conf.dist b/mercureactd.conf.dist index 20228d4..e621256 100644 --- a/mercureactd.conf.dist +++ b/mercureactd.conf.dist @@ -2,7 +2,10 @@ # Please make a copy of me before editing server: + # Listen address address: 0.0.0.0:9000 + # Enable websocket support (experimental) + websockets: false # Setup CORS headers cors: @@ -16,10 +19,6 @@ server: cert: foo.pem key: foo.key -# Enable websockets -websocket: - enable: true - publish: # Assign a UUID to published messages even if one is already set in the message overwrite_id: false diff --git a/src/Configuration.php b/src/Configuration.php index 6c7ade1..92cc390 100644 --- a/src/Configuration.php +++ b/src/Configuration.php @@ -97,5 +97,16 @@ class Configuration return $this; } + public function getEnableWebSockets(): bool + { + return (bool)($this->config['server.websockets']??false); + } + + public function setEnableWebSockets(bool $enable): self + { + $this->config['server.websockets'] = $enable; + return $this; + } + } diff --git a/src/Daemon.php b/src/Daemon.php index 5fa7d70..b2d911b 100644 --- a/src/Daemon.php +++ b/src/Daemon.php @@ -2,7 +2,10 @@ namespace NoccyLabs\Mercureact; +use Monolog\Handler\StreamHandler; +use Monolog\Logger; use NoccyLabs\Mercureact\Http\Server; +use Psr\Log\LoggerInterface; use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Socket\SocketServer; @@ -15,19 +18,33 @@ class Daemon private Server $server; + private LoggerInterface $logger; + public function __construct(Configuration $config, ?LoopInterface $loop=null) { $this->config = $config; $this->loop = $loop??Loop::get(); + $this->logger = $this->createLogger(); + } + + private function createLogger(): Logger + { + $handlers = [ + new StreamHandler(STDOUT) + ]; + $logger = new Logger("main", $handlers); + return $logger; } public function start(): void { - $this->server = new Server($this->config, $this->loop); + $this->server = new Server($this->config, $this->logger, $this->loop); + + $this->logger->info("NoccyLabs Mercureact Daemon v".MERCUREACT_VERSION); $listenAddress = $this->config->getListenAddress(); if (!$listenAddress) { - fwrite(STDERR, "Warning: Empty listening address. You won't make it far.\n"); + $this->logger->warning("Empty listening address. You won't make it far."); return; } $socket = new SocketServer("tcp://".$listenAddress); diff --git a/src/Http/Server.php b/src/Http/Server.php index b5a46d8..10df1e2 100644 --- a/src/Http/Server.php +++ b/src/Http/Server.php @@ -13,9 +13,11 @@ use NoccyLabs\Mercureact\Http\Middleware\ResponseMiddleware; use NoccyLabs\Mercureact\Http\Middleware\SecurityMiddleware; use NoccyLabs\Mercureact\Http\Middleware\WebSocketHandler; use Psr\Log\LoggerInterface; +use Psr\Log\NullLogger; use React\EventLoop\Loop; use React\EventLoop\LoopInterface; use React\Http\HttpServer; +use React\Socket\SecureServer; use React\Socket\ServerInterface; use SplObjectStorage; @@ -45,13 +47,13 @@ class Server * * */ - public function __construct(Configuration $config, ?LoopInterface $loop=null) + public function __construct(Configuration $config, ?LoggerInterface $logger, ?LoopInterface $loop=null) { $this->loop = $loop??Loop::get(); $this->config = $config; - $this->logger = $this->createLogger(); + $this->logger = $logger ?? new NullLogger(); $this->topicManager = new TopicManager(); $this->loop->addPeriodicTimer(30, function () { @@ -70,17 +72,11 @@ class Server */ public function listen(ServerInterface $socket): void { - $this->logger->info("Listening on ".$socket->getAddress()."\n"); $this->server->listen($socket); - } - - private function createLogger(): Logger - { - $handlers = [ - new StreamHandler(STDOUT) - ]; - $logger = new Logger("main", $handlers); - return $logger; + $this->logger->info(sprintf( + "Listening on %s", + str_replace("tcp://",($socket instanceof SecureServer?"https://":"http://"),$socket->getAddress()) + )); } /** @@ -89,7 +85,7 @@ class Server */ private function createHttpServer(): HttpServer { - return new HttpServer( + $stack = [ $this->responseMiddleware = new ResponseMiddleware( config: $this->config, logger: $this->logger->withName("http"), @@ -97,11 +93,18 @@ class Server $this->securityMiddleware = new SecurityMiddleware( config: $this->config ), - $this->webSocketHandler = new WebSocketHandler( - config: $this->config, - webSocketClients: $this->webSocketClients, - topicManager: $this->topicManager - ), + ]; + if ($this->config->getEnableWebSockets()) { + $stack = [ ...$stack, + $this->webSocketHandler = new WebSocketHandler( + config: $this->config, + webSocketClients: $this->webSocketClients, + topicManager: $this->topicManager + ), + ]; + $this->logger->warning("The WebSocket support is incomplete and insecure, but enabling it as requested."); + } + $stack = [ ...$stack, $this->mercureHandler = new MercureHandler( config: $this->config, topicManager: $this->topicManager @@ -111,7 +114,9 @@ class Server topicManager: $this->topicManager ), $this->notFoundHandler = new NotFoundHandler() - ); + ]; + + return new HttpServer(...$stack); }