diff --git a/README.md b/README.md index 82891278..c05add20 100644 --- a/README.md +++ b/README.md @@ -396,15 +396,38 @@ Tenant migrations are located in `database/migrations/tenant`, so you should mov ## HTTPS certificates -todo +HTTPS certificates are very easy to deal with if you use the `yourclient1.yourapp.com`, `yourclient2.yourapp.com` model. You can use a wildcard HTTPS certificate. -```sh -sudo mkdir /var/lib/letsencrypt_tenancy /var/log/letsencrypt_tenancy /etc/letsencrypt_tenancy -# replace ubuntu with your username -sudo chown -R ubuntu:www-data /var/lib/letsencrypt_tenancy /var/log/letsencrypt_tenancy /etc/letsencrypt_tenancy -sudo chmod -R 775 /var/lib/letsencrypt_tenancy /var/log/letsencrypt_tenancy /etc/letsencrypt_tenancy +If you use the model where second level domains are used, there are multiple ways you can solve this. + +This guide focuses on nginx. + +### 1. Use nginx with the lua module + +Specifically, you're interested in the [`ssl_certificate_by_lua_block`](https://github.com/openresty/lua-nginx-module#ssl_certificate_by_lua_block) directive. Nginx doesn't support using variables such as the hostname in the `ssl_certificate` directive, which is why the lua module is needed. + +This approach lets you use one server block for all tenants. + +### 2. Add a simple server block for each tenant + +You can store most of your config in a file, such as `/etc/nginx/includes/tenant`, and include this file into tenant server blocks. + +```nginx +server { + include includes/tenant; + server_name foo.bar; + # ssl_certificate /etc/foo/...; +} ``` +### Generating certificates + +You can generate a certificate using certbot. If you use the `--nginx` flag, you will need to run certbot as root. If you use the `--webroot` flag, you only need the user that runs it to have write access to the webroot directory (or perhaps webroot/.well-known is enough) and some certbot files (you can specify these using --work-dir, --config-dir and --logs-dir). + +Creating this config dynamically from PHP is not easy, but is probably feasible. Giving `www-data` write access to `/etc/nginx/sites-available/tenants.conf` should work. + +However, you still need to reload nginx configuration to apply the changes to configuration. This is problematic and I'm not sure if there is a simple and secure way to do this from PHP. + ## Testing If you run the tests of this package, please make sure you don't store anything in Redis @ 127.0.0.1:6379 db#14. The contents of this database are flushed everytime the tests are run. diff --git a/src/Interfaces/ServerConfigManager.php b/src/Interfaces/ServerConfigManager.php deleted file mode 100644 index 8216c538..00000000 --- a/src/Interfaces/ServerConfigManager.php +++ /dev/null @@ -1,9 +0,0 @@ -getVhostText($domain)); - $fc = fclose($f); - return $fw && $fc; - } - - public function getVhostText(string $domain) - { - return str_replace('%host%', $domain, config('tenancy.server.nginx.vhost')); - } - - public function deployCertificate(string $domain): bool - { - $process = new Process(array_merge([ - config('tenancy.server.certbot_path'), - '-n', - '--webroot', - '-d', $domain, - '--agree-tos', - '--preferred-challenges', 'http', - '--webroot-path', config('tenancy.server.nginx.webroot'), - ], config('tenancy.server.nginx.extra_certbot_args'))); - - $process->run(); - - return $process->isSuccessful(); - } -} diff --git a/src/ServerManager.php b/src/ServerManager.php deleted file mode 100644 index 8503823d..00000000 --- a/src/ServerManager.php +++ /dev/null @@ -1,38 +0,0 @@ -serverConfigManager = $serverConfigManager; - $this->tenantManager = $tenantManager; - } - - public function getConfigFilePath() - { - if (config('tenancy.server.file.single')) { - return config('tenancy.server.file.path'); - } - - return config('tenancy.server.file.path.prefix') . $this->tenantManager->tenant['uuid'] . config('tenancy.server.file.path.suffix'); - } - - public function createVhost(string $domain) - { - // todo symlink - $this->serverConfigManager->addVhost($domain, $this->getConfigFilePath()); - $this->serverConfigManager->deployCertificate($domain); - if (method_exists($this->serverConfigManager, 'postCertDeploymentChanges')) { - $this->serverConfigManager->postCertDeploymentChanges(); - } - } - - public function deleteVhost() - { - // todo - } -} diff --git a/src/config/tenancy.php b/src/config/tenancy.php index 332c8aca..50b6ad75 100644 --- a/src/config/tenancy.php +++ b/src/config/tenancy.php @@ -30,35 +30,4 @@ return [ // 's3', ], ], - 'server' => [ - 'manager' => 'Stancl\Tenancy\ServerConfigManagers\NginxConfigManager', - 'file' => [ - 'single' => true, // single file for all tenant vhosts - 'path' => '/etc/nginx/sites-available/tenants.conf', - /* - 'single' => false, - 'path' => [ - 'prefix' => '/etc/nginx/sites-available/tenants/tenant', - 'suffix' => '.conf', - // results in: '/etc/nginx/sites-available/tenants/tenant' . $uuid . '.conf' - ] - */ - ], - 'nginx' => [ - 'webroot' => '/var/www/html', - 'vhost' => " - server { - include includes/tenancy; - server_name %host%; - }", - 'extra_certbot_args' => [ - '--must-staple', - '--config-dir', '/etc/letsencrypt_tenancy', - '--work-dir', '/var/lib/letsencrypt_tenancy', - '--logs-dir', '/var/log/letsencrypt_tenancy', - '--staging', // obtains a fake cert intended for testing certbot - // '--email', 'your@email', // if you haven't created an account in certbot yet - ], - ], - ] ]; diff --git a/tests/ServerManagerTest.php b/tests/ServerManagerTest.php deleted file mode 100644 index a4d999ea..00000000 --- a/tests/ServerManagerTest.php +++ /dev/null @@ -1,91 +0,0 @@ -serverManager = app(ServerManager::class); - } - - /** @test */ - public function getConfigFilePath_works_when_single_file_mode_is_used() - { - config([ - 'tenancy.server.file.single' => true, - 'tenancy.server.file.path' => '/foo/bar', - ]); - - $this->assertSame('/foo/bar', $this->serverManager->getConfigFilePath()); - } - - /** @test */ - public function getConfigFilePath_works_when_multiple_files_mode_is_used() - { - config([ - 'tenancy.server.file.single' => false, - 'tenancy.server.file.path' => [ - 'prefix' => '/etc/foo', - 'suffix' => 'bar' - ], - ]); - - $uuid = tenant('uuid'); - - $this->assertSame("/etc/foo{$uuid}bar", $this->serverManager->getConfigFilePath()); - } - - /** @test */ - public function vhost_is_written() - { - [$tmpfile, $path, $vhost] = $this->setupCreateVhost(); - - $this->serverManager->createVhost('localhost'); - - $vhost = str_replace('%host%', 'localhost', $vhost); - - $this->assertContains($vhost, fread($tmpfile, filesize($path))); - } - - /** @test */ - public function cert_is_deployed() - { - [$tmpfile, $path, $vhost] = $this->setupCreateVhost(); - - $this->serverManager->createVhost('localhost'); - - dump(fread($tmpfile, filesize($path))); - // todo - } - - public function setupCreateVhost() - { - $tmpfile = tmpfile(); - $path = stream_get_meta_data($tmpfile)['uri']; - - $vhost = "server { - include includes/tenancy; - server_name %host%; -}"; - - config([ - 'tenancy.server.nginx' => [ - 'vhost' => $vhost, - 'extra_certbot_args' => [ - '--staging' - ], - ], - 'tenancy.server.file' => [ - 'single' => true, - 'path' => $path, - ], - ]); - - return [$tmpfile, $path, $vhost]; - } -}