Skip to content

Commit

Permalink
Uses Herd or Valet certificate when available (#46)
Browse files Browse the repository at this point in the history
* use herd or valet cert

* adds optional hostname

* update server start output

* rename parameter

* formatting

---------

Co-authored-by: Taylor Otwell <taylor@laravel.com>
  • Loading branch information
joedixon and taylorotwell authored Feb 25, 2024
1 parent 54ca735 commit 9ec4501
Showing 6 changed files with 89 additions and 5 deletions.
5 changes: 3 additions & 2 deletions config/reverb.php
Original file line number Diff line number Diff line change
@@ -29,8 +29,9 @@
'servers' => [

'reverb' => [
'host' => env('REVERB_HOST', '0.0.0.0'),
'port' => env('REVERB_PORT', 8080),
'host' => env('REVERB_SERVER_HOST', '0.0.0.0'),
'port' => env('REVERB_SERVER_PORT', 8080),
'hostname' => env('REVERB_SERVER_HOSTNAME'),
'options' => [
'tls' => [],
],
63 changes: 63 additions & 0 deletions src/Certificate.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
<?php

namespace Laravel\Reverb;

class Certificate
{
/**
* Determine if the certificate exists.
*/
public static function exists(string $url): bool
{
return static::resolve($url) !== null;
}

/**
* Resolve the certificate and key for the given URL.
*
* @return array<int, string>|null
*/
public static function resolve(string $url): ?array
{
$host = parse_url($url, PHP_URL_HOST) ?: $url;
$certificate = $host.'.crt';
$key = $host.'.key';

foreach (static::paths() as $path) {
if (file_exists($path.$certificate) && file_exists($path.$key)) {
return [$path.$certificate, $path.$key];
}
}

return null;
}

/**
* Get the certificate paths.
*
* @return array<int, string>
*/
public static function paths(): array
{
return [
static::herdPath(),
static::valetPath(),
];
}

/**
* Get the Herd certificate path.
*/
public static function herdPath(): string
{
return implode(DIRECTORY_SEPARATOR, [$_SERVER['HOME'], 'Library', 'Application Support', 'Herd', 'config', 'valet', 'Certificates', '']);
}

/**
* Get the Valet certificate path.
*/
public static function valetPath(): string
{
return implode(DIRECTORY_SEPARATOR, [$_SERVER['HOME'], '.config', 'valet', 'Certificates', '']);
}
}
5 changes: 4 additions & 1 deletion src/Servers/Reverb/Console/Commands/StartServer.php
Original file line number Diff line number Diff line change
@@ -5,6 +5,7 @@
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;
use Laravel\Reverb\Application;
use Laravel\Reverb\Certificate;
use Laravel\Reverb\Contracts\ApplicationProvider;
use Laravel\Reverb\Contracts\Logger;
use Laravel\Reverb\Jobs\PingInactiveConnections;
@@ -29,6 +30,7 @@ class StartServer extends Command implements SignalableCommandInterface
protected $signature = 'reverb:start
{--host= : The IP address the server should bind to}
{--port= : The port the server should listen on}
{--hostname= : The hostname the server is accessible from}
{--debug : Indicates whether debug messages should be displayed in the terminal}';

/**
@@ -54,6 +56,7 @@ public function handle(): void
$server = ServerFactory::make(
$host = $this->option('host') ?: $config['host'],
$port = $this->option('port') ?: $config['port'],
$hostname = $this->option('hostname') ?: $config['hostname'],
$config['options'] ?? [],
loop: $loop
);
@@ -63,7 +66,7 @@ public function handle(): void
$this->ensureRestartCommandIsRespected($server, $loop, $host, $port);
$this->ensurePulseEventsAreCollected($loop, $config['pulse_ingest_interval']);

$this->components->info("Starting server on {$host}:{$port}");
$this->components->info("Starting ".($server->isSecure() ? 'secure ' : '')."server on {$host}:{$port}".($hostname ? " ({$hostname})" : ''));

$server->start();
}
10 changes: 9 additions & 1 deletion src/Servers/Reverb/Factory.php
Original file line number Diff line number Diff line change
@@ -3,6 +3,7 @@
namespace Laravel\Reverb\Servers\Reverb;

use InvalidArgumentException;
use Laravel\Reverb\Certificate;
use Laravel\Reverb\Contracts\ApplicationProvider;
use Laravel\Reverb\Protocols\Pusher\Contracts\ChannelConnectionManager;
use Laravel\Reverb\Protocols\Pusher\Contracts\ChannelManager;
@@ -34,7 +35,7 @@ class Factory
/**
* Create a new WebSocket server instance.
*/
public static function make(string $host = '0.0.0.0', string $port = '8080', array $options = [], string $protocol = 'pusher', ?LoopInterface $loop = null): HttpServer
public static function make(string $host = '0.0.0.0', string $port = '8080', string $hostname = null, array $options = [], string $protocol = 'pusher', ?LoopInterface $loop = null): HttpServer
{
$loop = $loop ?: Loop::get();

@@ -43,6 +44,13 @@ public static function make(string $host = '0.0.0.0', string $port = '8080', arr
default => throw new InvalidArgumentException("Unsupported protocol [{$protocol}]."),
};

if (empty($options['tls']) && $hostname && Certificate::exists($hostname)) {
[$certificate, $key] = Certificate::resolve($hostname);

$options['tls']['local_cert'] = $certificate;
$options['tls']['local_pk'] = $key;
}

$uri = empty($options['tls']) ? "{$host}:{$port}" : "tls://{$host}:{$port}";

return new HttpServer(
9 changes: 9 additions & 0 deletions src/Servers/Reverb/Http/Server.php
Original file line number Diff line number Diff line change
@@ -2,6 +2,7 @@

namespace Laravel\Reverb\Servers\Reverb\Http;

use Illuminate\Support\Str;
use Laravel\Reverb\Servers\Reverb\Concerns\ClosesConnections;
use OverflowException;
use Psr\Http\Message\RequestInterface;
@@ -95,4 +96,12 @@ public function __invoke(ConnectionInterface $connection): void
$this->handleRequest($data, $connection);
});
}

/**
* Determine whether the server has TLS support.
*/
public function isSecure(): bool
{
return Str::startsWith($this->socket->getAddress(), 'tls://');
}
}
2 changes: 1 addition & 1 deletion tests/Unit/Servers/Reverb/FactoryTest.php
Original file line number Diff line number Diff line change
@@ -45,7 +45,7 @@

it('can create a server using tls on the given host and port', function () {
$this->app->config->set('reverb.servers.reverb.options.tls.local_cert', '/path/to/cert.pem');
$server = Factory::make('127.0.0.1', '8002', $this->app->config->get('reverb.servers.reverb.options'));
$server = Factory::make('127.0.0.1', '8002', options: $this->app->config->get('reverb.servers.reverb.options'));

$socket = (new ReflectionProperty($server, 'socket'))->getValue($server);
$socketServer = (new ReflectionProperty($socket, 'server'))->getValue($socket);

0 comments on commit 9ec4501

Please sign in to comment.